PostgreSQL - Selecting count of unique values in one and two columns - sql

Firstly, I'd like to apologise for the ambiguous title (I promise to revise it once I'm actually aware of the problem I'm trying to solve!)
I have two tables, player and match, which look like the following:
player:
id name
-- ----
1 John
2 James
3 April
4 Jane
5 Katherine
match:
id winner loser
-- ------ -----
1 1 2
2 3 4
Records in the match table represent a match between two players, where the id column is generated by the database, and the values in the winner and loser columns reference the id column in the player table.
I want to run a query which spits out the following:
player.id player.name total_wins total_matches
--------- ----------- ---------- -------------
1 John 1 1
2 James 0 1
3 April 1 1
4 Jane 0 1
5 Katherine 0 0
I currently have a query which retrieves total_wins, but I'm not sure how to get the total_matches count on top of that.
select p.id, p.name, count(m.winner)
from player p left join match m on p.id = m.winner
group by p.id, p.name;
Thanks for your help!

Try
select p.id, p.name,
sum(case when m.winner = p.id then 1 end ) as total_wins,
count(m.id) as total_matches
from player p
left join match m on p.id in ( m.winner, m.loser )
group by p.id, p.name;

One method splits the match match table, so you have a single row for each win and loss. The rest is just a left join and aggregation:
select p.id, p.name, coalesce(sum(win), 0) as win, count(m.id) as total_matches
from player p left join
(select match, winner as id, 1 as win, 0 as loss from match
union all
select match, loser as id, 0 as win, 1 as loss from match
) m
on p.id = m.id
group by p.id, p.name;

Related

Select from multiple tables where 1 record has more than 1 match in table 2

I have 2 tables, for example one with a person ID, name and food ID for an order and the second with the food ID and food name. I want to join these and return the ID, name, Food ID and Food Name but only for instances where the count of IDs and Food names are > 1 like below. Unfortunately when I try to do this I either get NULL instances from ID or it pulls the Food IDs I'm trying to exclude
Person
ID
Name
Food_ID
1
Joe
3
2
Jill
2
3
Jack
1
1
Joe
1
2
Jill
3
3
Jack
3
1
Joe
4
2
Jill
4
3
Jack
4
Food
Food ID
Food
1
Meat - Fish
2
Veg - Potato
3
Meat - Chicken
4
Veg - Broccoli
ID
Name
Food_ID
Food
1
Joe
3
Meat - Chicken
1
Joe
1
Meat - Fish
3
Jill
1
Meat - Fish
3
Jill
3
Meat - Chicken
I can do it using a temp table to get count of IDs where food like '%Meat%' and count (p.ID) > 1 but I need it to run in just a select query and I've no ID how to approach it as including a where exists just returns me NULL IDs. Apologies for how bad my SQL is but I haven't used it in years and am used to doing all my aggregation in Excel so have little idea how I'm meant to approach it, it's probably a really simple solution
SELECT p.ID, p.Name, f.Food_ID, f.Name
FROM Person p
LEFT JOIN Food f ON p.Food_ID = f.Food_ID
WHERE EXISTS (
SELECT COUNT(p.ID), COUNT(f.Food_ID)
FROM Person p
LEFT JOIN Food f ON p.Food_ID = f.Food_ID
WHERE f.Food LIKE '%Meat%'
GROUP BY p.ID
HAVING COUNT(p.id) > 1
)
GROUP BY p.ID
Try this:
SELECT
*
FROM
Person
JOIN Food ON Food.Food_ID = Person.Food_ID
WHERE
Person.ID IN (
SELECT
Person.ID
FROM
Person
JOIN Food ON Food.Food_ID = Person.Food_ID
WHERE
Food.Food LIKE '%meat%'
GROUP BY
Person.ID
HAVING
COUNT(*) > 1
)
ORDER BY
Person.ID
;
You can use a CTE to get GROUP BY p.ID HAVING COUNT(*) > 1, then get the other column values you need in your main query with a JOIN to your CTE using the p.ID column.
WITH cte AS (
SELECT
p.ID
FROM Person p
LEFT JOIN Food f ON p.Food_ID = f.Food_ID
WHERE f.Food LIKE '%Meat%'
GROUP BY p.ID
HAVING COUNT(*) > 1)
SELECT
p.ID,
p.Name,
f.Food_ID,
f.Food
FROM Person p
LEFT JOIN Food f ON p.Food_ID = f.Food_ID
INNER JOIN cte ON p.ID = cte.ID
WHERE f.Food LIKE '%Meat%'
ORDER BY p.ID ASC
Fiddle here.
Result:
ID
Name
Food_ID
Food
1
Joe
3
Meat - Chicken
1
Joe
1
Meat - Fish
3
Jack
1
Meat - Fish
3
Jack
3
Meat - Chicken
Note: Based on your provided data, I believe Jack should be listed in your result set, not Jill.

PostreSQL filter results by subset of a joined table and group them

I have table of people, another table of cars and a third table to join them since they have a many to many relationship. I want to select people who own a certain set of cars and group them by a region property on the person. So for example I would want to find all American's who own a Honda and a Nissan.
Example:
people table
id name region
1 Jon America
2 Jane Europe
3 Mike America
cars table
id make
1 Honda
2 Toyota
3 Nissan
people_cars table
person_id car_id
1 1
1 3
2 2
3 1
Desired result:
region own_honda_and_nissan
America 1
Europe 0
An idea for a SQL expression I have is:
SELECT
people.region,
CASE WHEN SUM(CASE WHEN cars.name IN ('Honda', 'Nissan') THEN 1 ELSE 0 END) = 2 THEN 1 ELSE 0 AS own_honda_and_nissan
FROM people
JOIN people_cars ON people_cars.person_id = people.id
JOIN cars ON people_cars.car_id = cars.id
GROUP BY people.region
HAVING
SUM(CASE WHEN cars.name IN ('Honda', 'Nissan') THEN 1 ELSE 0 END) = 2
ORDER BY own_honda_and_nissan DESC
This works if you group by people.id but once they get grouped by region it no longer works.
Use two levels of aggregation:
SELECT p.pregion, COUNT(*) as own_honda_and_nissan
FROM (SELECT pid, p.region,
FROM people p JOIN
people_cars pc
ON pc.person_id = p.id JOIN
cars c
ON pc.car_id = c.id
WHERE c.name IN ('Honda', 'Nissan')
GROUP BY p.id, p.region
HAVING COUNT(DISTINCT c.name) = 2
) p
GROUP BY p.region
ORDER BY own_honda_and_nissan DESC

MSSQL: GROUP BY and Count

I have 3 tables that contain info about users. I would like to find how many of each type of item in each bucket a person has.
I'm not grasping why this wouldn't work. Does table join order matter or something I not aware of?
A sample of the tables:
PERSONS
ID
NAME
1
John
2
Jane
BUCKETS
ID
LABEL
PERSONID
1
Random
1
2
Vacation
1
THINGS
ID
BUCKETID
TYPE
VALUE
1
1
Image
abc12
2
1
Image
abc13
3
1
Video
abc34
4
1
Image
def12
5
1
Video
def34
SELECT P.NAME, B.LABEL, T.TYPE, COUNT(T.TYPE)
FROM PERSONS P
LEFT JOIN BUCKETS B ON
B.PERSONID = P.ID
LEFT JOIN THING T ON
T.BUCKETID = B.ID
GROUP BY P.NAME, B.LABEL, T.TYPE
I expect it to return:
John, Random, Images, 3
John, Random, Videos, 2
But it returns:
John, Random, Images, 5
John, Random, Videos, 5
I have tried COUNT(*) which results in the same and COUNT(DISTINCT T.TYPE) which of course returns 1 as the count.
This works perfectly in MySQL. Fiddle here: https://www.db-fiddle.com/f/vcb3wiPMSAFBXrWbgYxuMH/8
MSSQL is a different beast all together.
I think you want count(distinct) of some sort. I would speculate:
SELECT P.NAME, B.LABEL, T.TYPE, COUNT(DISTINCT T.BUCKETID)
FROM PERSONS P LEFT JOIN
BUCKETS B
ON B.PERSONID = P.ID LEFT JOIN
THING T
ON T.BUCKETID = B.ID
GROUP BY P.NAME, B.LABEL, T.TYPE;

SQL - Using Count with WHERE clause

I'm trying to do a simple, at first sight, SQL query, but I can't wrap my head around what I'm doing wrong.
Problem
Imagine these are my tables:
TableUsers
UserID FirstName LastName
1 Bill Johnson
2 Alex Agnew
3 Mike Owen
4 Kate Ryan
TableArticles
ArticleID Description
1 Bananas
2 Eggs
3 Milk
TableOrders
OrderID UserID ArticleID
1 1 1
2 1 2
3 2 3
4 2 2
5 3 3
I want to list all users who have at least one order linked to their name, count how many orders and count how many of a specific article they ordered:
Desired result
UserID FirstName # Orders # Banana orders # Egg orders
1 Bill 2 1 1
2 Alex 2 0 1
3 Mike 1 0 0
I have tried:
SELECT
UserID,
FirstName,
COUNT(*) AS '# Orders',
(SELECT COUNT(*) FROM TableOrders O WHERE O.UserID = TableOrders.UserID AND O.ArticleID = 1) AS '# Banana orders',
(SELECT COUNT(*) FROM TableOrders O WHERE O.UserID = TableOrders.UserID AND O.ArticleID = 2) AS '# Egg orders',
FROM TableUsers
LEFT JOIN TableOrders ON TableOrders.UserID = TableUsers.UserID
GROUP BY UserID, FirstName
HAVING Count(*) > 0;
But I'm getting an error saying the ArticleID needs to be in an aggregate function or group by clause. If I add the ArticleID to the group by clause, users with multiple orders are shown more than once...
Any help is appreciated!
Thank you!
Just use conditional aggregation:
SELECT o.UserID, o.FirstName, COUNT(*) AS num_orders,
SUM(CASE WHEN o.ArticleID = 1 THEN 1 ELSE 0 END) as num_bananas,
SUM(CASE WHEN o.ArticleID = 2 THEN 1 ELSE 0 END) as num_eggs
FROM TableUsers u JOIN
TableOrders o
ON o.UserID = u.UserID
GROUP BY UserID, FirstName;
Notes:
You want users with orders, so just use an inner join, not an outer join.
The HAVING clause is then not necessary.
Table aliases make the query easier to read and write.
Don't use single quotes for column aliases. It is better to name the columns so they don't need to be escaped.

Select all categories with COUNT of sub-categories

I need to select all categories with count of its sub-categories.
Assume here are my tables:
categories
id | title
----------
1 | colors
2 | animals
3 | plants
sub_categories
id | category_id | title | confirmed
------------------------------------
1 1 red 1
2 1 blue 1
3 1 pink 1
4 2 cat 1
5 2 tiger 0
6 2 lion 0
What I want is :
id | title | count
------------------
1 colors 3
2 animals 1
3 plants 0
What I have tried so far:
SELECT c.id, c.title, count(s.category_id) as count from categories c
LEFT JOIN sub_categories s on c.id = s.category_id
WHERE c.confirmed = 't' AND s.confirmed='t'
GROUP BY c.id, c.title
ORDER BY count DESC
The only problem with this query is that this query does not show categories with 0 sub categories!
You also can check that on SqlFiddle
Any help would be great appreciated.
The reason you don't get rows with zero counts is that WHERE clause checks s.confirmed to be t, thus eliminating rows with NULLs from the outer join result.
Move s.confirmed check into join expression to fix this problem:
SELECT c.id, c.title, count(s.category_id) as count from categories c
LEFT JOIN sub_categories s on c.id = s.category_id AND s.confirmed='t'
WHERE c.confirmed = 't'
GROUP BY c.id, c.title
ORDER BY count DESC
Adding Sql Fiddle: http://sqlfiddle.com/#!17/83add/13
I think you can try this too (it evidence what column(s) you are really grouping by):
SELECT c.id, c.title, RC
from categories c
LEFT JOIN (SELECT category_id, COUNT(*) AS RC
FROM sub_categories
WHERE confirmed= 't'
GROUP BY category_id) s on c.id = s.category_id
WHERE c.confirmed = 't'
ORDER BY RC DESC