SQL join with self - mysql

I have a table with ID numbers of people and then items of food they've ordered:
table "food_id"
food id
ham 1
cheese 2
turkey 2
ham 3
ham 4
bread 5
cheese 6
turkey 6
cheese 7
And I'd like to use SQL to figure out, for each id, the total number of other IDs who ordered at least one of the same food items. For the above example, the answer should be:
"result_table"
count id
3 1
3 2
3 3
3 4
1 5
3 6
3 7
The challenge is to avoid double counting here. For example, person number 2 got both cheese and turkey, so we want his final count to be 3 because person # 2, 6, and 7 got cheese, and person # 2 and 6 got turkey, and there are 3 unique IDs in this list of (2,6,7,2,6).
My initial thoughts were to first get a table with food items to distinct ID numbers, and then to join this table with the original table and then group by ID number and get a count of the distinct number of IDs. However, I'm a beginner to SQL and can't figure out how to implement the code correctly.
Any direction would be greatly appreciated.

To avoid the problem with the double counting you can concat both ids from the join and count only distinct combinations. I add a separator to make the combination unique with greater id values:
SELECT
COUNT(DISTINCT CONCAT(f1.id, ',', f2.id)) as count,
f1.id
FROM
food_id f1
INNER JOIN
food_id f2
ON
f1.food = f2.food
GROUP BY f1.id;
See demo

Like you said, you can do a self join. You can join by food, and count the number of distinct matching ids.
select
a.id, -- Person you're investigating
count(distinct b.id) as samefoodcount -- number of people sharing the same food
from
food_id a
inner join food_id b on b.food = a.food
group by
a.id
Here you can see the query in action: http://sqlfiddle.com/#!2/c53884/1

You can run the following query to get the desired output:
select MAX(T.total),id
from table_name, (select count(*) as total,food from table_name group by food) T
where table_name.food=T.food
group by id
Check the DEMO

Related

mySQL: linking multiple rows from another table to a single cell using their id's [duplicate]

So basically as an example, here's a table of foods and the date they were consumed on by different people
consumption
user_id
food
date
1
apple
12-DEC-09
1
banana
12-DEC-09
1
strawberry
13-DEC-09
2
apple
14-DEC-09
2
apple
15-DEC-09
2
orange
15-DEC-09
I want to select all foods that each user_id ate on each unique date, so the output looks something like:
user_id
food
date
1
apple, banana
12-DEC-09
1
strawberry
13-DEC-09
2
apple
14-DEC-09
2
apple, orange
15-DEC-09
I've tried something like this:
SELECT food
FROM consumption
WHERE food IN (
SELECT food
FROM consumption
GROUP BY food, `date`
)
GROUP BY user_id;
But I'm stumped. Any help would be appreciated!
The group in your case is rows group where both user_id and date is the same. Hence you must group by these two columns.
You need all food names concatenated into single value within a group. Hence you must use according aggregate function.
SELECT user_id, GROUP_CONCAT(food) food_list, `date`
FROM consumption
GROUP BY user_id, `date`;

AVG() function is displaying average of all employees instead of average of employees in each town per shop

I want to display the average number of employees in each town. Town can have
This is my data_base:
To clarify it's assumed that employers are part-timers so there may be a situation when they are currently not employed for the sake of exercising OUTER JOIN queries. They can also be assigned to more than 1 shop.
I found the way to do it is to use subquery for AVG function. In a subquery, I will use COUNT to count the number of employees for each town and each shop and the main query will get AVG of those numbers grouping by the town. However while logically I feel the query should be working, it is displaying wrong data. Below is a query that I created.
SELECT s.Town, AVG(a.cnt) AS `Number of employees` FROM
(SELECT COUNT(k.EmpId) AS cnt FROM `Shops and Employees` k
INNER JOIN Shops s ON s.ShopId = k.ShopId
GROUP BY s.Town) AS a, shops s
GROUP BY s.Town
I expected to see something like this:
Town name | AVG of employees per town
-------------------------------------
town1 | 3
town2 | 5
town3 | 1
town4 | 4
Instead I get this:
Town name | AVG of employees per town
-------------------------------------
town1 | 2
town2 | 2
town3 | 2
town4 | 2
Basically what I get is all employes summed divided by the number of towns. I want an average of employees for each town e.g. in town1 I have 2 shops. Shop1 has 2 employees while shop2 has 4 employees. An average for town1 should be 3.
It looks like your problem is that you're not grouping the data by shop in your employee count subquery, so that query is returning the total number of employees per town. Also, you don't need to group by town in that subquery. Without the complete table structure and sample data it's hard to be certain, but I think this is what you really want:
SELECT s.Name as `Town Name`, AVG(se.cnt) AS `AVG employees per shop`
FROM Shops s
JOIN (SELECT ShopId, COUNT(*) AS cnt
FROM `Shops and Employees`
GROUP BY ShopId) se ON se.ShopId = s.ShopId
GROUP BY s.Name

Selecting everything from table 1 with average AND distinct values from table 2 and table 3

I am not entirely sure even how to name this post, because I do not know exactly how to ask it.
I have three tables. One with users, one with foods and one with the users rating of the foods, like such (simplified example):
Foods
id name type
---------------------
1 Apple fruit
2 Banana fruit
3 Steak meat
Users
id username
-----------------
1 Mark
2 Harrison
3 Carrie
Scores (fid = food id, uid = user id)
fid uid score
---------------------
1 1 3
1 2 5
2 1 2
3 2 3
Now, I have this query, which works perfectly:
SELECT fn.name as Food, ROUND(AVG(s.score),1) AS AvgScore FROM Foods fn LEFT JOIN Scores s ON fn.id = s.fid GROUP BY fn.id ORDER BY fn.name ASC
As you can tell, it lists all names from Foods including an average from all users ratings of the food.
I also want to add the unique users own score. (Assume that when Mark is logged in, his uid is set in a session variable or whatever)
I need the following output, if you are logged in as Mark:
Food AvgScore Your Score
Apple 4 3
I have made several attempts to make this happen, but I cannot find the solution. I have learned that if you have a question, it is very likely that someone else has asked it before you do, but I do not quite know how to phrase the question, so I get no answers when googling. A pointer in the right direction would be much appreciated.
You can try with case:
SELECT fn.name as Food,
ROUND(AVG(s.score),1) AS AvgScore,
sum(case s.uid = $uid when s.score else 0 end) as YourScore
FROM Foods fn
LEFT JOIN Scores s ON fn.id = s.fid
GROUP BY fn.id
ORDER BY fn.name ASC
$uid is variable off course.

SQL GROUP BY "HAVING" the required rows

Is there a succinct way of using HAVING to check if the required rows are within the GROUP BY?
With the example date:
turtle_id name
1 Mike
2 Ralph
3 Leo
4 Don
and
turtle_id crush_for
1 Pizza
1 April Oneil
2 April Oneil
3 Pizza
3 April Oneil
4 Pizza
4 Pizza
4 Science
4 April Oneil
And the SQL:
SELECT turtle.name
FROM turtle
JOIN turtle_crush ON turtle_crush.turtle_id = turtle.turtle_id
WHERE turtle_crush.crush_for IN ('Pizza', 'April Oneil')
GROUP BY turtle.turtle_id
HAVING (a crush on both "Pizza" and "April Oneil")
I realize I could do something like HAVING COUNT(*) > 1, but that would give a false positive for Don (id 4) because he likes 'Pizza' twice.
Edit:
Just adding a WHERE clause will return Ralph where he doesn't have a crush_for 'Pizza'
This should work:
SELECT t.turtle_name
FROM turtle t
INNER JOIN (SELECT turtle_id
FROM turtle_crush
WHERE crush_for IN ('Pizza','April Oneil')
GROUP BY turtle_id
HAVING COUNT(DISTINCT crush_for) = 2) tc
on t.turtle_id = tc.turtle_id;
In this code, the subquery first filter the results where crush_for is either 'Pizza' or 'April Oneil'. Then it groups by turtle_id, with another condition of choosing those turtle_ids that have 2 different crush_for values (hence ensuring that you get only the turtle_ids that have both crushes). Then it's joined with the turtle table to get the name.
Put the list of crushes in the WHERE clause, group by turtle ID, count the distinct values of crush types then keep only the groups that have at least 2 values (or how many crushes you put in the query):
SELECT turtle.name
FROM turtle
INNER JOIN turtle_crush ON turtle_crush.turtle_id = turtle.turtle_id
WHERE crush_for IN ("Pizza", "April Oneil")
GROUP BY turtle.turtle_id
HAVING COUNT(DISTINCT crush_for) = 2

How to join multiple queries with different tables and different column name

I want to join multiple queries with different tables and column name, along with I need to display the count of duplicate fields as shown below.
The queries are: (Proj_uid is common in all the tables which I need to match)
select proj_name,Agency,District,Division,Proj_status from tempproj
Need to join 2 tables to get the result that is payment80 and payment20 which contains billtype column with duplicate values, I want to count those value too
SELECT p.Proj_name,p.billtype, COUNT(1) as CNT
FROM payment80 p where billtype='civil'
GROUP BY Proj_name, billtype
(This is by using single table but I want this result by joining both payment80 and payment20 tables)
SELECT p.Proj_name,p.billtype, COUNT(1) as CNT
FROM payment80 p where billtype='Electric'
GROUP BY Proj_name, billtype
(The billtype values I want to count and just display a number of duplicate records)
Proj_Name billtype
------------------------
policegruha civil
gruhayojna Electric
policegruha civil
dcoffice civil
spoffice Electric
dcoffice civil
3) Select billtype from payment, here also I need count the duplicate values and display in billtype
Duplicate values will be in billtype which contains some thing like this:
Finally I want an output like this:
Proj_name Agency District Division Projstatus Civilbilltype Electricbilltype
policegruha kumar chitradurga davangere ongoing 3 1
gruhayojna khan ballary ballary completed 2 2
Atered john bangalore bangalore ongoing 2 4
dcoffice ravi mangalore mangalore ongoing 1 2
spoffice mary chitradurga davangere completed 3 4
hostel jack ballary ballary completed 3 3
univercity kumar bangalore bangalore ongoing 4 2
mess Raj mysore mysore ongoing 2 1
policestation khan mysore mysore ongoing 1 4
conferencehall Rosy davangere davangere ongoing 2 2
You are joining three separate tables. One is physical, tempproj, and the other two are virtual: they are aggregates.
This is the technique.
SELECT p.proj_name,p.Agency,p.District,p.Division,p.Proj_status,
Civilbills.billcount as Civilbills,
Electribills.billcount as Electricbills
FROM tempproj p
LEFT JOIN (
SELECT Proj_name, COUNT(*) as billcount
FROM payment80
where billtype='civil'
GROUP BY Proj_name
) Civilbills ON Civilbills.Proj_name = p.proj_name
LEFT JOIN (
SELECT Proj_name, COUNT(*) as billcount
FROM payment80
where billtype='Electric'
GROUP BY Proj_name
) Electricbills ON Electricbills.Proj_name = p.proj_name
Your requirement includes two separate aggregates from the payment80 table. The LEFT JOINs prevent suppression of project rows that lack any bills of either category.