how to remove duplicated columns and group column - php

Hello guys i currently have a MySQL query that works perfectly fine, but the only thing i don't like is that when I get returned data, some rows come duplicated except for the last column of the row .
I guess the reason why is because when I'm selecting everything including the left join, the commenttext column contains the data for a post submitted by a user.
For example) If I post a post with id of 1 and 5 people comment on that post MySQL query will bring up 5 rows with all the data in every column the same except for the last column containing the different comment pertaining to the post.
So far here is my MySQL query. How can I make it where it doesn't bring back duplicated data but only the comments grouped with the id of the post or how can i store all the comments to an array so when I run a foreach loop I can then run the comments array inside with a while loop. The reason I would use a foreach loop is to export the data with html .
SELECT
b.id
, b.from_user
, b.dateadded
, b.posttype
, b.posttext
, b.photoname
, b.blahchoice
, b.commentschoice
, b.mood
, c.defaultphoto
, d.firstn
, d.lastn
, e.status
, f.topostid
, f.commenttext
FROM
t_board b
INNER JOIN
t_userprofiles c ON b.from_user = c.user_id
INNER JOIN
t_users d ON b.from_user = d.id
INNER JOIN
t_friendship e ON e.friend_ids = b.from_user
LEFT JOIN
t_postcomments f ON f.topostid = b.id
WHERE
e.status = 'Friend'
AND e.user_ids = :id
ORDER BY
b.id DESC

One way to do it is group_concat() the comments and while you run inside the loop explode() the comments and then do foreach to display the the comments under the id
SELECT b.id,
b.from_user,
b.dateadded,
b.posttype,
b.posttext,
b.photoname,
b.blahchoice,
b.commentschoice,
b.mood,
c.defaultphoto,
d.firstn,
d.lastn,
e.status,
f.topostid,
group_concat(f.commenttext) as commenttext
FROM t_board b
INNER JOIN t_userprofiles c ON b.from_user = c.user_id
INNER JOIN t_users d ON b.from_user = d.id
INNER JOIN t_friendship e ON e.friend_ids = b.from_user
LEFT JOIN t_postcomments f ON f.topostid = b.id
WHERE e.status = 'Friend'
AND e.user_ids = :id
group by b.id
ORDER BY b.id DESC
NOTE that while you execute the query on PHP commenttext will all be comma separated and inside the loop to fetch the data for each b.id you will have all the comments for that id as comma - separated string , you can explode it to generate another array and display them.
Also some of your comments may have comma in it so you may need to provide a separator for group_concat something as below or can choose any custom operator.
group_concat(f.commenttext SEPARATOR '||' ) as commenttext
AND explode the data with ||
NOTE : The result is truncated to the maximum length that is given by
the group_concat_max_len system variable, which has a default value of
1024
http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html#function_group-concat

Combine the entries for the same post in your PHP fetch loop:
$results = array();
while ($row = $stmt->fetch()) {
if (isset($results[$id]) {
$results[$id]['comments'][] = $row['commenttext'];
} else {
$row[$id] = $row;
$row['comments'] = array($row['commenttext']);
}
}

Related

Moving the workload from PHP to MySQL

I currently have two queries that are executed in order. The first query pulls a few rows from a database and the 2nd query is executed using one value from those rows. I am only interested in the rows from the 1st query where I know the 2nd query is going to return a value higher than 0. I could potentially have thousands of rows in my first table, so I rather do the filtering in MySQL and not in PHP, I would assume it is faster.
The first query is as following
SELECT
*
FROM
wp_invoices
WHERE
deleted = 0
The 2nd query is as following:
SELECT COUNT(*) FROM wp_users A
INNER JOIN wp_usermeta B ON (A.ID = B.user_id)
LEFT JOIN wp_invoices_records C ON (A.ID = C.uid
AND C.invoice_id = %d
AND C.status != 100)
WHERE (B.meta_key = 'wp_capabilities' AND CAST(B.meta_value AS CHAR) LIKE '%%\"subscriber\"%%')
AND (C.uid IS NOT NULL)
Both queries work separately, this is not the problem. Currently, the %d in the 2nd query is replaced by PHP, which inserts the wp_invoices.id value for each row.
Ideally, I would like a query that returns only the rows from wp_invoices, where the 2nd query would return a value higher than 0. At this point I am not interested in the rows of the 2nd query, only the amount of rows found.
My table structures are as following: http://sqlfiddle.com/#!9/466cb
This is what I do in PHP, currently, to handle all this. Please keep in mind that this code was written to clarify this post, I am using deprecated MySQL functions because I don't write PHP that often anymore and haven't really looked into mysqli and I haven't checked if this code actually runs. It should give you a better indication of what I am currently doing.
<?php
/* Example code. I am not using mysql_ functions in my own solution, I just
know these functions by heart. Assume a database connection is already
established */
/* Refer to this as QUERY 1 */
$result = mysql_query("SELECT * FROM wp_invoices WHERE deleted = 0");
/* Loop over all invoices (this is not an invoice 'record'. These are
the invoice definitions, not the actual instances that people received */
while($row = mysql_fetch_assoc($result)) {
/* Refer to this as QUERY 2 */
$count_result = mysql_query(sprintf("
SELECT
COUNT(*) AS count
FROM wp_users A
INNER JOIN wp_usermeta B ON (A.ID = B.user_id)
LEFT JOIN wp_invoices_records C ON (A.ID = C.uid
AND C.invoice_id = %d
AND C.status != 100)
WHERE (B.meta_key = 'wp_capabilities'
AND CAST(B.meta_value AS CHAR) LIKE '%%\"subscriber\"%%')
AND (C.uid IS NOT NULL)", $row['id']));
if(mysql_result($count_result, 0) > 0) {
/* Here I would pull all data I would need for this invoice. $row
contains all the information about the invoice, $invoice_record
will contain all the information for this particular user about
this invoice*/
$invoice_records = mysql_query(sprintf("
SELECT
A.ID AS id,
A.display_name,
C.id AS received,
DATE_FORMAT(C.received_on, '%%d-%%m-%%Y') AS received_on,
C.status,
A.user_email,
C.price,
(SELECT meta_value FROM wp_usermeta WHERE meta_key = 'first_name'
AND user_id = A.ID) AS first_name,
(SELECT meta_value FROM wp_usermeta WHERE meta_key = 'tssnvgsl'
AND user_id = A.ID) AS insertion,
(SELECT meta_value FROM wp_usermeta WHERE meta_key = 'last_name'
AND user_id = A.ID) AS last_name
FROM wp_users A
INNER JOIN wp_usermeta B ON (A.ID = B.user_id)
LEFT JOIN wp_invoices_records C ON (A.ID = C.uid
AND C.invoice_id = %d
AND C.status != 100)
WHERE (B.meta_key = 'wp_capabilities'
AND CAST(B.meta_value AS CHAR) LIKE '%%\"subscriber\"%%')
AND (C.uid IS NOT NULL)", $row['id']));
while($invoice_record = mysql_fetch_assoc($invoice_records)) {
/* Perform logic to see if this person should receive a reminder */
}
}
}
?>
What I attempt to achieve, though:
<?php
$result = mysql_query(/* here a query that pulls all rows from QUERY 1
where QUERY 2 would have returned a count higher than 2 */);
while($row = mysql_fetch_assoc($result)) {
/* Now here I should be 100% sure that every $row has at least one
"pending" invoice record, that has not yet been paid and has a
status of < 100 */
}
?>
Alright. I have been racking my brain staring at your process and I think it is starting to make sense. If I am not mistaken you should be able to eliminate both query1 and query2 and move the logic into the LEFT JOIN of your third query like such.
SELECT
A.ID AS id,
A.display_name,
C.id AS received,
DATE_FORMAT(C.received_on, '%%d-%%m-%%Y') AS received_on,
C.status,
A.user_email,
C.price,
(SELECT meta_value FROM wp_usermeta WHERE meta_key = 'first_name'
AND user_id = A.ID) AS first_name,
(SELECT meta_value FROM wp_usermeta WHERE meta_key = 'tssnvgsl'
AND user_id = A.ID) AS insertion,
(SELECT meta_value FROM wp_usermeta WHERE meta_key = 'last_name'
AND user_id = A.ID) AS last_name
FROM wp_users A
INNER JOIN wp_usermeta B ON (A.ID = B.user_id)
LEFT JOIN wp_invoices_records C ON (A.ID = C.uid
AND C.invoice_id IN (SELECT id FROM wp_invoices WHERE deleted = 0)
AND C.status != 100)
WHERE (B.meta_key = 'wp_capabilities'
AND CAST(B.meta_value AS CHAR) LIKE '%%\"subscriber\"%%')
AND (C.uid IS NOT NULL)"
This query, instead of having to be ran every time for every record and the $row['id'] added via php within your LEFT JOIN I am using MySQL IN functionality with a subselect. This gives you the ability to join only on records from wp_invoices which where deleted.

missing column data when retrieving rows from a JOIN query

In php and mysql I use a join query on several tables. Each table has the field name title. I want to get the value of title from each JOINed table, but when I retrieve each row of my result set with php, I only get the last value of title, This is my query.
SELECT UC.user_id,
UC.courses_id,
UC.semester_id,
UC.batch_id,
UC.department_id,
U.title,
U.firstname,
U.lastname,
B.title ,
CO.title,
SE.title,
DEP.title
FROM tbl_user_courses AS UC
INNER JOIN tbl_user AS U ON UC.user_id = U.id
INNER JOIN tbl_batch AS B ON UC.user_id = B.id
INNER JOIN tbl_courses AS CO ON UC.user_id = CO.id
INNER JOIN tbl_semester AS SE ON UC.user_id = SE.id
INNER JOIN tbl_departments AS DEP ON UC.user_id = DEP.id
where UC.trash=0
order by UC.user_id desc
Its php code
<?php
if($rec)
foreach( $rec as $value => $k){
?>
<?php echo $k['title'];
<?php echo $k['title'];?>
<?php echo $k['title'];?>
<?php echo $k['title'];?>
}
Now How can i get each table title its a field name.
You can give the fields an alias, by adding AS <alias> after the field.
SELECT UC.user_id,
UC.courses_id,
UC.semester_id,
UC.batch_id,
UC.department_id,
U.title as user_title,
U.firstname,
U.lastname,
B.title AS batch_title,
CO.title AS course_title,
SE.title AS semester_title,
DEP.title AS department_title
FROM tbl_user_courses AS UC
INNER JOIN tbl_user AS U ON UC.user_id = U.id
INNER JOIN tbl_batch AS B ON UC.user_id = B.id
INNER JOIN tbl_courses AS CO ON UC.user_id = CO.id
INNER JOIN tbl_semester AS SE ON UC.user_id = SE.id
INNER JOIN tbl_departments AS DEP ON UC.user_id = DEP.id
where UC.trash=0
order by UC.user_id desc
If you run this query, you can use the aliases instead of the field name, so use $k['department_title'] to get the name of the department.
I think that 'title' is a rather odd term to use for the name of department or course, but if they would all be named 'name' you would have the same issue again. :D
By the way, you could use a naming convention to reduce collisions like this. Just like you have department_id (and not just id) you could also use department_name or just department. Still, it's good to know about aliases, because you will need them sooner or later.
When you use fetch_assoc() functions in PHP to retrieve rows from your result set, if some of your columns have the same name you'll lose data. You have, as you know, a whole bunch of columns called title.
You want something like this, putting aliases on some of your result set columns so they don't all have the same name.
These aliases don't change the names of columns in your tables. They change the names of the columns in the results of your query only. When you say
SELECT title AS user_title
it fetches the column named title from the table, but then gives it the name user_title in the result set.
Try this:
SELECT UC.user_id, UC.courses_id, UC.semester_id, UC.batch_id, UC.department_id,
U.title AS user_title,
U.firstname, U.lastname,
B.title AS batch_title,
CO.title AS course_title,
SE.title AS semester_title,
DEP.title AS department_title
...
Then, in php, use code like this....
<?php echo $k['user_title'];
<?php echo $k['batch_title'];?>
<?php echo $k['course_title'];?>
<?php echo $k['semester_title'];?>
<?php echo $k['department_title'];?>
Alternatively, you can, if you're using PDO, fetch each row of your result set into a numbered rather than associative array.
$sth = $dbh->prepare("/*YOUR ORIGINAL QUERY8?") || die "prepare failed";
$sth->execute() || die "execute failed";
while ( $k = $sth->fetch( PDO::FETCH_NUM ) ) {
<?php echo 'batch title: ';echo $k[8];?>
<?php echo 'course title: ';echo $k[9];?>
/* etcetera */
}
This works because it fetches the columns of each row in your result set into a numerically indexed array ( 0 .. n) rather than an associative array.
You can also, in the obsolete mysql API, get the same effect with
while ($k = mysql_fetch_array($resultset, MYSQL_NUM) ) {
/* handle the row */
}

PHP SQL how the get the exact count from a table

I have two tables with the following columns:
FAMILY - id, ...
PRODUCTS - id, idfamily, type, ...
FAMILY & PRODUCTS are connected with family.id = products.idfamily.
I'm doing pagination over families with filters based on products types, so I'm trying to get the exact count of FAMILY containing almost one product with a specific type.
First query is ok, I get all the families:
if (!isset($_GET['type']) || $_GET['type'] == 'all') {
$query_family=mysql_query("SELECT * FROM family");
}
$count=mysql_num_rows($query_family);
// result = 166
Unfortunately, the following query is wrong:
} else {
$query_family=mysql_query("
SELECT * FROM family f LEFT JOIN products p ON f.id = p.idfamily
WHERE p.type = '$_GET[type]'
");
}
$count=mysql_num_rows($query_family);
// result = 500+
it's wrong because I get all the products with a type, but I'm trying to get the number of families containing products with the selected type ($_GET[type]).
Thank you
You should have
SELECT distinct f.id from family f LEFT JOIN products p on f.id = p.idfamily WHERE p.type = '$_GET[type]' in the second query
I think.
SELECT COUNT(*) as nbr FROM family
INNER JOIN products ON products.idfamily = family.id
WHERE product.type = ".intval($_GET['type'])."
GROUP BY family.id
Avoid the mysql_ driver, use PDO or mysqli instead. Don't forget to protect you from sql injections too.
To get the count of families with a specified type, use
SELECT * FROM family f INNER JOIN products p ON f.id = p.idfamily
WHERE IFNULL(p.type, 'notspecified') = '$_GET[type]'
With INNER JOIN instead of LFET JOIN you get only rows where a connection between f.id and p.idfamily exist. By LEFT JOIN, all family rows are returned with NULL values in the fields of product table.
So when p.type is NULL, your p.type = '$_GET[type]' evaluates always to NULL, and your filtering will not work as expected. For this reason, use IFNULL.
You can try below code:
$query_family=mysql_query("
SELECT COUNT(f.id) AS cnt FROM family f
INNER JOIN products p ON f.id = p.idfamily
WHERE p.type = '$_GET[type]'");
Also try to use mysqli not mysql.
And you don't have to use mysql_num_rows, you can get the right number directly from mysql:
$query = "SELECT COUNT(DISTINCT f.id) ".
"FROM family f ".
"LEFT JOIN products p ON f.id = p.idfamily ".
"WHERE p.type = '".$_GET['type']."'";
Use INNER JOIN instead of LEFT.

How does one craft an array from LEFT JOINs when joined tables have multiple matches and GROUP BY omits needed rows

I was presented with something PHP/MYSQL related today that I was unsure exactly how to approach.
I am running this query against an altered vbulletin database:
SELECT
p.postid,
p.threadid,
p.parentid,
p.username,
p.title,
p.dateline,
p.pagetext,
p.allowsmilie,
p.visible,
pe.post_type,
pe.element_type,
pe.element,
pr.region_id,
th.threadid,
th.title,
th.forumid,
th.lastpost,
th.lastposter,
th.lastpostid,
th.similar,
at.contentid,
at.attachmentid,
p.attach
FROM thread AS th
JOIN post as p ON p.threadid = th.threadid
LEFT JOIN post_element AS pe ON pe.post_id = p.postid
LEFT JOIN post_region AS pr ON pr.post_id = p.postid
LEFT JOIN attachment AS at ON at.contentid = p.postid
GROUP BY p.postid;
The issue with the above is that sometimes there is 2, 3, or 4 rows from post_element that have the same post_id == p.postid. In that case the GROUP BY returns the 1st match and omits the 2nd 3rd....etc....
What I really desire is to create an array in something like:
while ($row = mysql_fetch_assoc($result)) {
}
that builds nested arrays for the pe.element, pe.post_type, pe.element_type fields when there is more than one match on post_id.
I was able to hack this by using this:
SELECT
p.postid,
p.threadid,
p.parentid,
p.username,
p.title,
p.dateline,
p.pagetext,
p.allowsmilie,
p.visible,
group_concat(pe.post_type) as post_type,
group_concat(pe.element_type) as element_type,
group_concat(pe.element) as element,
group_concat(pr.region_id) as region_id,
th.threadid,
th.title,
th.forumid,
th.lastpost,
th.lastposter,
th.lastpostid,
th.similar,
at.contentid,
at.attachmentid,
p.attach
FROM thread AS th
JOIN post as p ON p.threadid = th.threadid
LEFT JOIN post_element AS pe ON pe.post_id = p.postid
LEFT JOIN post_region AS pr ON pr.post_id = p.postid
LEFT JOIN attachment AS at ON at.contentid = p.postid
GROUP BY p.postid'
and then
$row['element'] = explode(",", $row['element']);
inside of the aforementioned mysql_fetch_assoc while loop...
but how would you craft an array with PHP that has only one $row for each post_id == postid with a nested array for fields that have multiple matches from $result?
Many Many thanks for your brainPower!
I would select the first as you are currently, add a count(pe.post_id) to the query and if that number is greater than 1, theuy just query that table directly
If > 50% have more than 1 then always use the second query and never check for it.
so
//do main query
foreach($post as &$p) {
$elements = array();
$p['elements'] = getElements($p['postid'])
}

mysql group_concat returns null row if one entry in the group is null

I have a rather simple query which looks to combine all results from another table into one json object.
MySQL
SELECT s.id, s.uid, s.sexnumber, s.rating, s.sextime, s.diary, GROUP_CONCAT(p.pid,',') as positions, GROUP_CONCAT(w.id,':',w.who) as who, GROUP_CONCAT('{',
'\"lat\":',l.lat,
',\"lon\":',l.lon,
',\"house\":',l.house,
',\"house_number\":',l.house_number,
',\"road\":',l.road,
',\"supermarket\":',l.supermarket,
',\"city\":',l.city,
',\"city_district\":',l.city_district,
',\"country\":',l.country,
',\"country_code\":',l.country_code,
',\"county\":',l.county,
',\"neighbourhood\":',l.neighbourhood,
',\"pedestrian\":',l.pedestrian,
',\"place_of_worship\":',l.place_of_worship,
',\"postcode\":',l.postcode,
',\"state\":',l.state,
',\"suburb\":',l.suburb,
'}'
) as location, GROUP_CONCAT(ww.id,':',ww.name) as place
FROM users u join sex s
on s.uid = u.uid
LEFT OUTER JOIN whos ws
ON s.id = ws.sid
LEFT OUTER JOIN who w
ON w.id = ws.wid
LEFT OUTER JOIN locations l
ON l.sid = s.id
LEFT OUTER JOIN wheresex whs
ON whs.sid = s.id
LEFT OUTER JOIN wherewhere ww
ON whs.wid = ww.id
LEFT OUTER JOIN positions p
ON s.id = p.sid
WHERE u.sessionCheck = '%s'
GROUP BY s.id
ORDER BY s.sextime DESC;
If any of the location results inside the GROUP_CONCAT are NULL then the entire entry will return NULL.
How can I have the individual results that are NULL return this way and anything else with their values?
I must admit that I haven't tried to execute this sort of query before, but I can't see how a coalesce wouldn't do the trick?
SELECT s.id, s.uid, s.sexnumber, s.rating, s.sextime, s.diary, GROUP_CONCAT(p.pid,',') as positions, GROUP_CONCAT(w.id,':',w.who) as who, GROUP_CONCAT('{',
'\"lat\":',coalesce(l.lat,'Unknown'),
',\"lon\":',coalesce(l.lon,'Unknown'),
',\"house\":',coalesce(l.house,'Unknown'),
',\"house_number\":',coalesce(l.house_number,'Unknown'),
',\"road\":',coalesce(l.road,'Unknown'),
',\"supermarket\":',coalesce(l.supermarket,'Unknown'),
',\"city\":',coalesce(l.city,'Unknown'),
',\"city_district\":',coalesce(l.city_district,'Unknown'),
',\"country\":',coalesce(l.country,'Unknown'),
',\"country_code\":',coalesce(l.country_code,'Unknown'),
',\"county\":',coalesce(l.county,'Unknown'),
',\"neighbourhood\":',coalesce(l.neighbourhood,'Unknown'),
',\"pedestrian\":'coalesce(,l.pedestrian,'Unknown'),
',\"place_of_worship\":',coalesce(l.place_of_worship,'Unknown'),
',\"postcode\":',coalesce(l.postcode,'Unknown'),
',\"state\":',coalesce(l.state,'Unknown'),
',\"suburb\":',coalesce(l.suburb,'Unknown'),
'}'
) as location, GROUP_CONCAT(ww.id,':',ww.name) as place
Edit: I am not sure if I understand your comment correctly, but if you want to add quotes around the values returned by the coalesce you could use a concat function like this:
'\"lat\":',concat('"',coalesce(l.lat,'Unknown'),'"')
You maybe looking for Concat not Group_Concat
CONCAT(w.id,':',w.who)
and same for
CONCAT('{',
'\"lat\":',l.lat,
',\"lon\":',l.lo

Categories