Group By Count in MySql (Users and Group) - php

I'm creating a social mobile-application which has groups and groups can have user. I'm trying to write a query in order to get all the groups along with number of users in it.
Note: Group can have 0 users as well.
Even if group has zero user, I still need to get its information. How should I do that? I tried:
Select *, count(ug.group_id) from groups g
left join images i ON(g.group_image_id = i.image_id)
left join location l ON(g.group_location_id = l.location_id)
.
.
left join user_group ug on(ug.gorup_id = g.group_id)
group by ug.group_id;
Now, this query does not give me group that has zero user(s). How can change it so it shows all group even if group has 0 users.

Using SELECT * and GROUP BY is a MySQL abomination. In your case, it doesn't look like it hurts since image and location appear to have a 0:1 relation ship with group. It's just very bad practice.
Here's an ANSI compliant way to write your query. If you want the full result set from JOINing a few tables, plus the count, then add only the count portion as an expression.
Select *, (select count(ug.group_id)
from user_group ug
where ug.gorup_id = g.group_id) GroupUserCount
from groups g
left join images i ON(g.group_image_id = i.image_id)
left join location l ON(g.group_location_id = l.location_id)
.
.

you should group them from groups table
GROUP BY g.group_id

I believe you are looking for something like:
Select g.group_id,coalesce(users ,0)
from groups g
left join (select ug.gorup_id,count(*) users
from user_group ug
group by ug.gorup_id) s
on s.group_id = g.group_id
If you only need to know the group and number of users, the other table in the example are not necessary.

Your are grouping by a column of your left join, this means that your are grouping by null when a group have no users.
You need to group by id of the groups table and the count(*) will count the number of joined rows.
Then add the empty groups using a left join and where join row is null
Select g.group_id as groupId, count(*) as nb
from groups g
join user_group ug on (ug.group_id = g.group_id)
group by g.group_id
UNION ALL
Select g.group_id as groupId, 0 as nb
from groups g
left join user_group ug on (ug.group_id = g.group_id)
where ug.group_id is null
group by g.group_id
[EDIT] One query count looking for null join in the select statement and setting nb to 0 if found
Select g.group_id as groupId, IF(ug.group_id is null, 0,count(*)) as nb
from groups g
left join user_group ug on (ug.group_id = g.group_id)
group by g.group_id

Related

EXISTS query optimization on mysql query

I have a big data problem with MySQL.
I have:
a users table with 59033 rows, and
a user_notes table with 8753 rows.
But when I search which users have user note in some dates.
My query like this :
SELECT u.*, rep.name as rep_name FROM users as u
LEFT JOIN users as rep on rep.id = u.add_user
LEFT JOIN authorization on authorization.id = u.authorization
LEFT JOIN user_situation_list on user_situation_list.user_situation_id = u.user_situation
WHERE
EXISTS(
select * from user_notes
where user_notes.note_user_id = u.id AND user_notes.create_date
BETWEEN "2017-10-20" AND "2017-10-22"
)
ORDER BY u.lp_modify_date DESC, u.id DESC
Turn it around -- find the ids first; deal with the joins later.
SELECT u.*,
( SELECT rep.name
FROM users AS rep
WHERE rep.id = u.add_user ) AS rep_name
FROM (
SELECT DISTINCT note_user_id
FROM user_notes
WHERE create_date >= "2017-10-20"
AND create_date < "2017-10-20" + INTERVAL 3 DAY
) AS un
JOIN users AS u ON u.id = un.note_user_id
ORDER BY lp_modify_date DESC, id DESC
Notes
No GROUP BY needed;
2 tables seem to be unused; I removed them;
I changed the date range;
User notes needs INDEX(create_date, note_user_id);
Notice how I turned a LEFT JOIN into a subquery in the SELECT list.
If there can be multiple rep_names, then the original query is "wrong" in that the GROUP BY will pick a random name. My Answer can be 'fixed' by changing rep.name to one of these:
MAX(rep.name) -- deliver only one; arbitrarily the max
GROUP_CONCAT(rep.name) -- deliver a commalist of names
Rewriting your query to use a JOIN rather than an EXISTS check in the where should speed it up. If you then group the results by the user.id it should give you the same result:
SELECT u.*, rep.name as rep_name FROM users as u
LEFT JOIN users as rep on rep.id = u.add_user
LEFT JOIN authorization on authorization.id = u.authorization
LEFT JOIN user_situation_list on user_situation_list.user_situation_id = u.user_situation
JOIN user_notes AS un
ON un.note_user_id
AND un.create_date BETWEEN "2017-10-20" AND "2017-10-22"
GROUP BY u.id
ORDER BY u.lp_modify_date DESC, u.id DESC

mysql join return 0 for one column with other columns intact

I wish to join multiple tables like- Categories, menus, restaurants, reviews, etc.
to return the restaurants that provide the inserted food with their prices.
Everything works except numberOfReviews in reviews table.
If a restaurant has no reviews then output should be 0 for numOfReviews column but other column values should be retrieved i.e. price, name, etc.
With following query I get all fields as null and count(numReviews) as 0:
select r.id
,r.`Name`
,r.`Address`
,r.city
,r.`Rating`
,r.`Latitude`
,a.`AreaName`
,m.`Price`
,count(rv.id)
from `categories` c, `menus` m, `restaurants` r, areas a, reviews rv
where m.`ItemName`="tiramisu"
and c.`restaurant_id`=r.`id`
and m.`category_id`=c.id
and r.`AreaId`=a.`AreaId`
and if I can't match rv.restaurant_id=r.id in where clause(obviously).
Where am I getting wrong? How do I solve this?
edited
select r.id,
r.`Name`,
r.`Address`,
r.city,
r.`Rating`,
r.`Latitude`,
a.`AreaName`,
m.`Price`,
r.`Longitude`,
r.Veg_NonVeg,
count(rv.id)
from restaurants r LEFT JOIN `reviews` rv on rv.`restaurant_id`=r.`id`
inner join `categories` c on c.`restaurant_id` = r.id
inner join `menus` m on m.`category_id` = c.id
inner join `areas` a on a.`AreaId` = r.`AreaId`
where m.`ItemName`="tiramisu"
First of all, don't use this old school syntax for the jointures.
Here is a query that may solve your problem:
SELECT R.id
,R.Name
,R.Address
,R.city
,R.Rating
,R.Latitude
,R.Longitude
,A.AreaName
,M.Price
,R.Veg_NonVeg
,COUNT(RV.id) AS numOfReviews
FROM restaurants R
INNER JOIN categories C ON C.restaurant_id = R.id
INNER JOIN menus M ON M.category_id = C.id
INNER JOIN areas A ON A.AreaId = R.AreaId
LEFT JOIN reviews RV ON RV.restaurant_id = R.id
WHERE M.ItemName = 'tiramisu'
GROUP BY R.id, R.Name, R.Address, R.city, R.Rating, R.Latitude, R.Longitude, A.AreaName, M.Price, R.Veg_NonVeg
I used explicit INNER JOIN syntax instead of your old school syntax and I modified the jointure with table reviews in order to get the expected result. The GROUP BY clause is required to use the aggregate function COUNT, every rows will be grouped by the enumerated columns (every column except the one used by the function).
Here is another solution that simplify the GROUP BY clause and allow the modification of SELECT statement without having to worry about the fact that every columns need to be part of the GROUP BY clause:
SELECT R.id
,R.Name
,R.Address
,R.city
,R.Rating
,R.Latitude
,R.Longitude
,A.AreaName
,M.Price
,R.Veg_NonVeg
,NR.numOfReviews
FROM restaurants R
INNER JOIN (SELECT R2.id
,COUNT(RV.id) AS numOfReviews
FROM restaurants R2
LEFT OUTER JOIN reviews RV ON RV.restaurant_id = R2.id
GROUP BY R2.id) NR ON NR.id = R.id
INNER JOIN categories C ON C.restaurant_id = R.id
INNER JOIN menus M ON M.category_id = C.id
INNER JOIN areas A ON A.AreaId = R.AreaId
WHERE M.ItemName = 'tiramisu'
As you can see here I added a new jointure on a simple subquery that does the aggregation job in order to provide me the expected number of reviews for each restaurant.
Hope this will help you.

How to get all values from a table which includes itself as another value neighbour field at the same table?

I have 3 tables.
First table keeps "group_names" with id numbers. Second table keeps "groups_elements" with id numbers and group_id numbers next to element_name. Third table keeps relations between group_elements which includes element_id, sub_element_id.
I wish to get concat group_name, element_name and element_id numbers sub_elements numbers.
Here is sqlfiddler link http://sqlfiddle.com/#!2/44f63
And i wish to get such result:
Solid Soil 5,6,7
Liquid Oil 8,9,10
I am using MySQL and PHP.
You can do so
SELECT CONCAT(g.group_name,' , ',e.element_name)
, GROUP_CONCAT(DISTINCT er.sub_element_id)
FROM groups g
JOIN elements e ON(g.id = e.group_id)
JOIN element_subelement_relation er ON(er.element_id= e.id)
GROUP BY g.group_name, e.element_name
Demo
Edit from comments
SELECT CONCAT(g.group_name,' , ',e.element_name) `group_elements`
, GROUP_CONCAT(DISTINCT er.`sub_element_id`) `ids`
FROM groups g
LEFT JOIN elements e ON(g.id = e.group_id)
LEFT JOIN element_subelement_relation er ON(er.element_id= e.id)
GROUP BY g.group_name, e.element_name
HAVING group_elements IS NOT NULL
ORDER BY g.group_name
Demo
using group_concat()
SELECT g.group_name, group_concat(sub_element_id) as items
FROM elements e INNER JOIN element_subelement_relation er
ON e.id = er.element_id INNER JOIN groups g
ON g.id = e.group_id
GROUP BY g.group_name
demo: http://sqlfiddle.com/#!2/44f63/21
Simply join and use group_concat on the sub ids:
select
concat(g.group_name, ' ', e.element_name) as name,
group_concat(sub_element_id order by sub_element_id) as sub_elements
from elements e
inner join groups g on g.id = e.group_id
inner join element_subelement_relation r on r.element_id = e.id
group by name
order by sub_elements, name;
The SQL fiddle: http://sqlfiddle.com/#!2/44f63/31.
You may also want to try these:
SELECT g.group_name, e.element_name, concat(g.id,",", e.id,",",esr.id) as ID
FROM element_subelement_relation esr
LEFT JOIN elements e ON(esr.element_id = e.id)
LEFT JOIN groups g ON(e.group_id = g.id)

How to write JOIN QUERY for 4 tables in the below condition

I have 4 tables ACCOUNTS_TABLE , LINKS_TABLE, GROUPS_TABLE, KEYS_TABLE
I need to get all accounts details which is of acct_type xx with count of Links, groups& keywords . I have tried this query but it gives all count as 0
SELECT
acc.acct_id, acc.acct_type, count(link.id) as link_count, link.account,
groups.camp_id, count(groups.id) as group_count, count(keyword.key_id) as key_count
FROM ".ACCOUNTS_TABLE." as acc
LEFT JOIN ".LINKS_TABLE." as link ON link.account=acc.acct_id AND acct_type='xx'
LEFT JOIN ".GROUPS_TABLE." as groups ON groups.camp_id=link.id
LEFT JOIN ".KEYS_TABLE." as keyword ON keyword.camp_id=link.id
GROUP BY acc.acct_id
My required output should be like this
Any one please help me to slove this problem
You probably should use COUNT(DISTINCT ....).
SELECT acc.acct_id, COUNT(DISTINCT link.id), COUNT(DISTINCT groups.id), COUNT(DISTINCT keyword.key_id)
FROM ACCOUNTS_TABLE acc
LEFT OUTER JOIN LINKS_TABLE link ON link.account = acc.acct_id AND acct_type = 'advertiser'
LEFT OUTER JOIN GROUPS_TABLE groups ON groups.camp_id = link.id
LEFT JOIN KEYS_TABLE keyword ON keyword.id = link.id
WHERE acc.acct_type = 'xx'
GROUP BY acc.acct_id
EDIT
Amended to use the updated join conditions, etc:-
SELECT acc.acct_id, acc.acct_type, COUNT( DISTINCT link.id ) , COUNT( DISTINCT groups.id ) , COUNT( DISTINCT keyword.key_id )
FROM ACCOUNTS_TABLE acc
LEFT OUTER JOIN LINKS_TABLE link ON link.account = acc.acct_id
LEFT OUTER JOIN GROUPS_TABLE groups ON groups.camp_id = link.id
LEFT JOIN KEYS_TABLE keyword ON keyword.camp_id=link.id
WHERE acc.acct_type = 'xx'
GROUP BY acc.acct_id, acc.acct_type
You could try something like this:
SELECT ACC.Id
,( SELECT COUNT (*) FROM Links L WHERE L.AccountId = ACC.Id ) AS CountOfLinks
,( SELECT COUNT (*) FROM Groups G WHERE G.AccountId = ACC.Id ) AS CountOfGroups
FROM ( SELECT Id FROM Accounts Acc WHERE Acc.Type = 'some type' ) ACC
I've rejigged your code a bit (see below) for a few reasons:
It's helpful (for me anyway) to write my SELECT statements always in a certain way - with anything that is not being grouped placed first, and ideally putting things in same order as my JOINs and doing the same in my GROUP BY
I put anything which restricts my FROM table into the WHERE not the JOIN to make it clearer what I'm trying to do and also to make it easier to modify later on.
I also like to ensure it's well laid out to make it easier to scan for issues.
Take this rearranged query and read through it to make sure you are getting the behaviour you're expecting.
PS I'm not sure about your table names and quotation style - I usually use back ticks (`) and would never put dots (.) in my table names. If you put these in as placeholders that's fine but they could lead to trouble for you if they are real.
SELECT
acc.acct_id,
-- if you don't group by these then you need to remove them as they will just return the first values based on mysql behaviour
acc.acct_type,
link.account,
groups.camp_id,
-- these counts will only count where an ID is present which seems like what you're after
count(link.id) as link_count,
count(groups.id) as group_count,
count(keyword.key_id) as key_count
FROM ".ACCOUNTS_TABLE." as acc
LEFT JOIN ".LINKS_TABLE." as link ON link.account=acc.acct_id
LEFT JOIN ".GROUPS_TABLE." as groups ON groups.camp_id=link.id
LEFT JOIN ".KEYS_TABLE." as keyword ON keyword.id=link.id
WHERE acct_type='advertiser'
GROUP BY acc.acct_id,
-- only use these if you intend to group by them
acc.acct_type,
link.account,
groups.camp_id DESC
SELECT acct_type,
count(acct_type),
count(l.id),
count(g.id),
count(key_id)
FROM accounts a
LEFT JOIN links l ON (l.account = a.acct_id)
LEFT JOIN groups g ON (g.camp_id = l.id)
LEFT JOIN keysTable k ON k.group_id = g.id
GROUP BY acct_type HAVING acct_type = 'xx';
SQL Fiddle Validated: http://www.sqlfiddle.com/#!2/f4b6a/20
SELECT
accounts_table.acct_id,
accounts_table.acct_type,
COUNT(DISTINCT links_table.id) AS link_count,
COUNT(DISTINCT groups_table.id) AS group_count,
COUNT(DISTINCT keys_table.key_id) AS key_count
FROM
accounts_table
LEFT JOIN
links_table
ON links_table.account = accounts_table.acct_id
LEFT JOIN
groups_table
ON groups_table.camp_id = links_table.id
LEFT JOIN
keys_table
ON keys_table.camp_id = links_table.id
WHERE
acct_type = 'xx'
GROUP BY
accounts_table.acct_id,
accounts_table.acct_type
ORDER BY
link_count DESC,
group_count DESC,
key_count DESC
Edited answer to match updated question - this should do what you've asked for.
This should do what you've asked for, SQL fiddle here - http://www.sqlfiddle.com/#!2/f4b6a/20

Counting rows from table after joins

I have a query that selects data from 4 tables via joins, i want to also count the number rows in a fifth table containing a matching foreign key.
This is what my current query looks look like, and it doesnt work
"SELECT
ph.pheed_id,ph.user_id,ph.datetime,ph.repheeds,
ph.pheed,fav.id,fav.P_id,fav.datetime as stamp,
u.username,ava.avatar_small
COUNT(pheed_comments.comment_id) as comments
FROM favourite_pheeds fav
INNER JOIN pheeds ph ON ph.pheed_id=fav.P_id
INNER JOIN users u ON u.id=ph.user_id
INNER JOIN profiles pr ON pr.user_id=ph.user_id
LEFT JOIN user_avatars ava ON ava.avatar_id=pr.avatar
ORDER BY stamp DESC
LIMIT $offset,$limit";
How do i count the number rows in a fifth table containing a matching foreign key.
select ph.pheed_id,
ph.user_id,
ph.datetime,
ph.repheeds,
ph.pheed,
fav.id,
fav.P_id,
fav.datetime as stamp,
u.username,
ava.avatar_small,
coalesce(pcc.Count, 0) as comments_count
from favourite_pheeds fav
inner join pheeds ph on ph.pheed_id = fav.P_id
inner join users u on u.id = ph.user_id
inner join profiles pr on pr.user_id = ph.user_id
left join user_avatars ava on ava.avatar_id = pr.avatar
left outer join (
select pheed_id, count(*) as Count
from pheed_comments
group by pheed_id --took a guess at the column name here
) pcc on ph.pheed_id = pcc.pheed_id
order by stamp desc
LIMIT $offset, $limit

Categories