Count and concatenate MySQL entries - php

Essentially, I have a table that is like this:
FirstName, LastName, Type
Mark, Jones, A
Jim, Smith, B
Joseph, Miller, A
Jim, Smith, A
Jim, Smith, C
Mark, Jones, C
What I need to do is be able to display these out in PHP/HTML, like:
Name | Total Count Per Name | All Type(s) Per Name
which would look like...
Mark Jones | 2 | A, C
Jim Smith | 3 | B, A, C
Joseph Miller | 1 | A
Jim Smith | 3 | B, A, C
Jim Smith | 3 | B, A, C
Mark Jones | 2 | A, C
I have spent time trying to create a new table based off the initial one, adding these fields, as well as looking at group_concat, array_count_values, COUNT, and DISTINCT, along with other loop/array options, and cannot figure this out.
I've found a number of answers that count and concatenate, but the problem here is I need to display each row with the total count/concatenation on each, instead of shortening it.

How about doing it like this?
SELECT aggregated.* FROM table_name t
LEFT JOIN (
SELECT
CONCAT(FirstName, ' ', LastName) AS Name,
COUNT(Type) AS `Total Count Per Name`,
GROUP_CONCAT(Type SEPARATOR ',') AS `All Type(s) Per Name`
FROM table_name
GROUP BY Name) AS aggregated
ON CONCAT(t.FirstName, ' ', t.LastName) = aggregated.Name

Without an ORDER BY clause, the order the rows will be returned in is indeterminate. Nothing wrong with that, by my personal preference is to have the result to be repeatable.
We can use an "inline view" (MySQL calls it a derived table) to get the count and the concatenation of the Type values for (FirstName,LastName).
And then perform a join operation to match the rows from the inline view to each row in the detail table.
SELECT CONCAT(d.FirstName,' ',d.LastName) AS name
, c.total_coount_per_name
, c.all_types_per_name
FROM mytable d
JOIN ( SELECT b.FirstName
, b.LastName
, GROUP_CONCAT(DISTINCT b.Type ORDER BY b.Type) AS all_types_per_name
, COUNT(*) AS total_count_per_name
FROM mytable b
GROUP
BY b.FirstName
, b.LastName
) c
ON c.FirstName = d.FirstName
AND c.Last_name = d.LastName
ORDER BY d.FirstName, d.LastName
If you have an id column or some other "sequence" column, you can use that to specify the order the rows are to be returned; same thing in the GROUP_CONCAT function. You can omit the DISTINCT keyword from the GROUP_CONCAT if you want repeated values... 'B,A,B,B,C',

Related

Return all the rows even the joined table has empty results

In my table 1 I have something like this
name | age
George 42
Bob 30
Ken 23
In my table 2, I have something like this, this is where i store votes for each person.
name | votes |
George 1
Ken 1
George 1
George 1
Ken 1
My goal is to combine the 2 tables, and return all the rows in table 1 even it doesn't exist in table 2.
Desire results:
name | age | total_votes
George 42 3
Bob 30 0
Ken 23 2
But instead I get:
name | age | total_votes
George 42 3
Ken 23 2
I have tried something like this
SELECT `table_1`.*, coalesce(COUNT(`table_2`.votes), 0) AS total_votes
FROM `table_1`
LEFT JOIN `table_2`
ON `table_1`.name = `table_2`.name
You can do one of these:
1) Use Right Join instead of current Left Join.
Or
2) Exchange table1 and table2 places in your join expression, like:
FROM table_2
LEFT JOIN table_1
Try this. This works in MS Access , I think this will work on your's too just convert the query to SQL:
SELECT Table1.name, First(Table1.age) AS age, Count(Table2.Votes) AS totalVotes
FROM Table1 LEFT JOIN Table2 ON Table1.name = Table2.name
GROUP BY Table1.name;
Left Join table1 to table2 so that all entry from table1 , even if its is corresponding data is null, will be included. GROUP BY your query by name so that votes will be counted by name .

Use commaseparated mySQL column value in LIKE

I have column in my database named as foodtype . Table name is restaurant and the column foodtype has comma separated values (as Indian,Chinese).
Now, when a person select Chinese then the i want mysql query should return restaurant where foodtype is Chinese.
Query should become like as below:
SELECT * FROM restaurant WHERE cityname='Chicago' and foodtype
LIKE ('%Chinese%')
And when a person select Indian then the i want mysql query should return restaurant where foodtype is Indian.
Query should become like as below:
SELECT * FROM restaurant WHERE cityname='Chicago' and foodtype
LIKE ('%Indian%')
And when a person select Indian and Chinese both then the i want mysql query should return restaurant where foodtype is Indian and Chinese.
Query should become like as below:
SELECT * FROM restaurant WHERE cityname='Chicago' and foodtype
LIKE ('%Indian%,%Chinese%')
Please let me know how can i achieve this.
Use FIND_IN_SET()
SELECT *
FROM restaurant
WHERE cityname='Chicago'
and find_in_set(foodtype, 'Indian') > 0
and find_in_set(foodtype, 'Chinese') > 0
But actually you are better off by chaning your table structure. Never, never, never store multiple values in one column!
To achieve that you can add 2 other tables to your DB
foodtypes table
---------------
id
name
restaurant_foodtypes
--------------------
restaurant_id
foodtype_id
Example data:
foodtypes
id | name
1 | chinese
2 | indian
restaurant_foodtypes
restanrant_id | foodtype_id
1 | 1
1 | 2
Then you can select restaurants having both foodtypes like this
select r.name
from restaurants r
join restaurant_foodtypes rf on rf.restaurant_id = r.id
join foodtypes f on rf.foodtype_id = f.id
where f.name in ('indian','chinese')
group by r.name
having count(distinct f.name) = 2

How can I retrieve a comma-separate list of linked ids in a 3-way MYSQL join?

I'm creating a book tagging system (i'm sure this has been done tons of times before), and I'd like to create a view that gives me each book, with its tags' names.
I have three tables:
books ( id, name)
tags (id, name)
bookTags (id, book, tag)
And I'd like to have one view
booksInfo (books.id, books.name, [comma-separated-tags.names], [tagids])
With my current view (in the sql fiddle below), I get duplicate rows, one for each tag-book pair.
I'd love to get something like this:
BOOKID NAME TAGS TAGIDS
------ ----------- -------------------- ---------
1 1984 Dystopian, Political 3, 4
2 White Fang Dogs, Nature 5, 9
3 Bible Religion, History 6, 10
4 1776 Political, History 4, 10
I created a sqlfiddle here: http://sqlfiddle.com/#!2/74b57/1/0
I could do this with PHP after my select, then go through it and create a separate array, but that seems unnecessary. I find with MySQL there's almost always a query-way to do something.
Use GROUP_CONCAT with GROUP BY
select
b.id 'id',
b.name 'name',
GROUP_CONCAT(t.name) 'tag',
GROUP_CONCAT(t.id) 'tagid'
from
bookTags bt
left join
books b ON b.id = bt.book
left join
tags t ON t.id = bt.tag
GROUP BY bt.book;
Result for your fiddle
+------+------------------+------------------+-------+
| id | name | tag | tagid |
+------+------------------+------------------+-------+
| 1 | 1984 | Government | 2 |
| 2 | Huckelberry Finn | Adventure | 1 |
| 3 | The bible | Religion | 3 |
| 4 | White Fang | Adventure,Nature | 1,4 |
+------+------------------+------------------+-------+
4 rows in set (0.00 sec)
Check
create view booksInfo as
select
b.id 'id',
b.name as 'name',
GROUP_CONCAT(t.name) as 'tag',
GROUP_CONCAT(t.id) 'tagid'
from bookTags bt
left join books b
on b.id = bt.book
left join tags t
on t.id = bt.tag
group by bt.book;
http://sqlfiddle.com/#!2/68cdd/1
You can use the group_concat function to transform a series of values on different rows to a coma delimited values:
SELECT b.id AS book_id, b.name AS book_name,
GROUP_CONCAT(t.name) AS tags,
GROUP_CONCAT(t.id) AS tag_ids
FROM bookTags bt
LEFT JOIN books b ON b.id = bt.book
LEFT JOIN tags t ON t.id = bt.tag
GROUP BY b.id, b.name
GROUP_CONCAT(exp) Function is Dedicated for the comma separated
http://www.w3resource.com/mysql/aggregate-functions-and-grouping/aggregate-functions-and-grouping-group_concat.php
SELECT b.id 'id',b.name 'name',
GROUP_CONCAT(t.name) 'tag',
GROUP_CONCAT(t.id) 'tagid'
FROM
bookTags bt
LEFT JOIN
books b ON (b.id = bt.book)
LEFT JOIN
tags t ON (t.id = bt.tag) GROUP BY bt.book;
You shouldn't manipulate your results in your query. It would be much nicer to return your list with duplicates and roll up on ID.
foreach ($rows as $key => $row) {
$out[$row['id']]['id'] = $row['id'];
$out[$row['id']]['name'] = $row['name'];
$out[$row['id']]['tags'] .= ', '.$row['tags'];
$out[$row['id']]['tagids'] = ', '.$row['name'];
}
This is only quick, but you could roll this up into a nice little function that didn't reference keys directly, didn't repeat commas etc etc etc. (i.e. this is really ugly code and I apologise).
But the moment you try and scale a large amount of data with groups, you'll have another problem to solve.

MySQL selecting row for attendance case

I have these tables:
table 1 : attendance
-------------------------------
ID | DATE | EMPLOYEE_ID |
-------------------------------
1 2013-09-10 1
2 2013-09-10 2
3 2013-09-10 3
-------------------------------
table 2: employee
---------------
ID | NAME |
---------------
1 Smith
2 John
3 Mark
4 Kyle
5 Susan
6 Jim
---------------
My actual code to show employee option.
while($row=mysql_fetch_array($query)){
echo "<option value='$row[employee_id]'>$row[first_name] $row[last_name]</option>";
}
?>
How can i show the list of employee that not registered in table 1?
The condition is if an employee already registered in table 1, they won't appear on the option.
I want to show the list in <option>'s of <select> element. So it will return: kyle, susan, jim.
Please tell me the correct query or if there is any better option, it'll be good too. Please give some solution and explain. Thank you very much
UPDATE / EDIT:
it also based on current date, if in table 1 have no latest date e.g. today it's 2013-09-15. It will show all of employee.
You can do this with a left join and then checking for no matches:
select e.*
from employee e left outer join
attendance a
on e.id = a.employee_id
where a.employee_id is null;
This is probably the most efficient option in MySQL.
EDIT:
To include a particular date, add the condition to the on clause:
select e.*
from employee e left outer join
attendance a
on e.id = a.employee_id and a.date = date('2013-09-20')
where a.employee_id is null;
If I understood correctly this should work, get all employees whose ID is not in the attendance table.
SELECT * FROM employee WHERE employee.ID NOT IN
(SELECT EMPLOYEE_ID FROM attendance)

PHP mySQL query to return a reordered list and count occurences of field value even if absent

Basically, I have a mysql db table which contains a datetime column and a category column. I want to create a SQL query to retrieve all the values present in the category column and count how many occurences of each category values grouped by month/year of the datetime column. If it is possible, I'd also like totals to be returned. A total for the number of all occurences in a month and a total of category counted.
Note: the category values cannot be hardcoded because they are set by the user and stored in another table.
DB table has following structure:
datetime | category
2009-01-05 | fish
2009-01-06 | fish
2009-01-06 | potato
2009-01-16 | fish
2009-02-08 | pineapple
2009-02-15 | potato
I wish returned result from query would be:
Month | fish | potato | pineapple | total
2009-01 | 3 | 1 | 0 | 4
2009-02 | 0 | 1 | 1 | 2
Total | 3 | 2 | 1 | 6
I think (hope) it can be done in a single SQL query but I can't figure out how.
Can anyone help me?
Thanks!
Let me first say that I think this feels more like an issue to handle in your presentation logic (php code). However, SQL can produce such a result. You are trying to accomplish two different things.
First, you're looking for a PIVOT table. MySQL does not support the PIVOT command, but you can simulate it with MAX and CASE. This works well when you know the number of potential categories, but won't work in your case.
Next, you want to have row totals and then a final total row. Again, this is more appropriate to handle in the presentation layer.
However, using Dynamic SQL, you can achieve both a PIVOT table and row totals. Here is some sample code:
First build your PIVOT variable #sql:
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'COUNT(IF(category = ''', category, ''',1,NULL)) AS ', category)
) INTO #sql
FROM (
SELECT *,
#rn:=IF(#prevMonthYear=CONCAT(YEAR(datetime),'-',MONTH(datetime)),#rn+1,1) rn,
#prevMonthYear:=CONCAT(YEAR(datetime),'-',MONTH(datetime)) dt
FROM yourtable JOIN (SELECT #rn:=0,#prevParent:=0) t
) t
;
Now build your Row Summary variable #totsql:
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'SUM(', category, ') AS sum_', category)
) INTO #totsql
FROM (
SELECT *,
#rn:=IF(#prevMonthYear=CONCAT(YEAR(datetime),'-',MONTH(datetime)),#rn+1,1) rn,
#prevMonthYear:=CONCAT(YEAR(datetime),'-',MONTH(datetime)) dt
FROM yourtable JOIN (SELECT #rn:=0,#prevParent:=0) t
) t
;
Put it all together:
SET #sql = CONCAT('SELECT dt,
', #sql, ', COUNT(1) total
FROM (
SELECT *,
#rn:=IF(#prevMonthYear=CONCAT(YEAR(datetime),''-'',MONTH(datetime)),#rn+1,1) rn,
#prevMonthYear:=CONCAT(YEAR(datetime),''-'',MONTH(datetime)) dt
FROM yourtable JOIN (SELECT #rn:=0,#prevParent:=0) t
) t
GROUP BY dt
UNION
SELECT ''Totals'',', #totsql, ', SUM(total)
FROM (
SELECT dt,
', #sql, ', COUNT(1) total
FROM (
SELECT *,
#rn:=IF(#prevMonthYear=CONCAT(YEAR(datetime),''-'',MONTH(datetime)),#rn+1,1) rn,
#prevMonthYear:=CONCAT(YEAR(datetime),''-'',MONTH(datetime)) dt
FROM yourtable JOIN (SELECT #rn:=0,#prevParent:=0) t
) t
GROUP BY dt
) t2
;');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SQL Fiddle Demo
Results:
MONTH FISH POTATO PINEAPPLE TOTAL
2009-1 3 1 0 4
2009-2 0 1 1 2
Totals 3 2 1 6
You can have multiple nested queries or you can use mysql loops in this case. Easiest thing would be to get all the data you need and process it using php.

Categories