How do I get counts for different values of the same column with a single totals row, using Postgres SQL? - sql

So I have a list of children and I want to create a list of how many boys and girls there are in each school and a final total count of how many there are.
My query including logic
select sch.id as ID, sch.address as Address, count(p.sex for male) as boycount, count(p.sex for female) as girlcount
from student s
join school sch on sch.studentid = s.id
join person p on p.studentid = s.id
Obviously I know this query wont work but I dont know what to do further from here. I thought about nested query but im having difficulty getting it to work.
I found a similar question for postgres 9.4
Postgres nested SQL query to count field. However I have Postgres 9.3.
Final result would be like :

WARNING
Depending on the data type of the school ID, you may get an error with this union. Consider casting the school ID as a varchar if it is of type INT.
SELECT
sch.id as ID, /*Consider casting this as a varchar if it conflicts with
the 'Total' text being unioned in the next query*/
sch.address as Address,
SUM(CASE
WHEN p.sex = 'male'
THEN 1
ELSE 0
END) AS BoyCount,
SUM(CASE
WHEN p.sex = 'female'
THEN 1
ELSE 0
END) AS GirlCount
FROM
student s
JOIN school sch
ON sch.studentid = s.id
JOIN person p
ON p.studentid = s.id
UNION ALL
SELECT
'Total' as ID,
NULL as Address,
SUM(CASE
WHEN p.sex = 'male'
THEN 1
ELSE 0
END) AS BoyCount,
SUM(CASE
WHEN p.sex = 'female'
THEN 1
ELSE 0
END) AS GirlCount
FROM
person p

Related

How to create a query to join three tables and make calculations in SQL?

I'm just at the beginning of my SQL studies and can't figure out how to resolve the next problem.
So, there are three tables:
! given tables
The task is: "Get number of pet type per owner"
Write a query to generate the result below:
! desired output
The best result I have for the moment:
SELECT owners.OWNER_NAME, COUNT(pets.OWNER_ID) AS pets
FROM owners
JOIN pets ON owners.ID = pets.OWNER_ID
JOIN pet_type ON pets.TYPE = pet_type.ID
GROUP BY owners.OWNER_NAME;
It returns first column with owner names and second column with the sum of particular owner pets.
Will appreciate any help.
You need conditional aggregation:
SELECT
o.OWNER_NAME,
SUM(CASE WHEN t.name = 'CAT' THEN 1 ELSE 0 END) CAT,
SUM(CASE WHEN t.name = 'DOG' THEN 1 ELSE 0 END) DOG,
SUM(CASE WHEN t.name = 'SNAKE' THEN 1 ELSE 0 END) SNAKE
FROM owners o
JOIN pets p ON o.ID = p.OWNER_ID
JOIN pet_type t ON p.TYPE = t.ID
GROUP BY o.OWNER_NAME;
I use name as the name of the column describing the type in table pet_type. Change it to the actual name of the column.
Check this. To Get number of pet type per owner, this is sufficient to join only Pets table with the owners table. A DISTINCT count of Pet.Type will give your desired output.
SELECT
owners.ID,
owners.OWNER_NAME,
COUNT(DISTINCT pets.TYPE) AS Num_Pet_Type
FROM owners
INNER JOIN pets ON owners.ID = pets.OWNER_ID
GROUP BY owners.ID,owners.OWNER_NAME;
If you wants number of Pet per type, use this below script-
SELECT
owners.ID,
owners.OWNER_NAME,
pets.TYPE,
COUNT(*) AS Num_Of_Pet
FROM owners
INNER JOIN pets ON owners.ID = pets.OWNER_ID
GROUP BY owners.ID,owners.OWNER_NAME,pets.TYPE;

Oracle Select Query on to many, Inner Query, Getting Not a Group Expression. Duplicates from JOIN

Thanks, I have been getting great help from generous people in this forum. I am a beginner in Oracle SQL.
I have a one to many relationship between two VIEWs, which are PERSON_VIEW and PHONE_VIEW. With help, I got a query with case/when for the phone to work, but when I added it back to my main query, it affected my other join with the TASK_VIEW.
I figured I needed to add it as an Inner Query so I get back one result. I added the following to my query. I ran the query in TOAD and it gave me an error, "ORA-00979: not a GROUP BY expression" and highlighted all the PERSON_IDs in the query. If I take the following left join, it runs fine and does not output the phone numbers. Any help is appreciated.
I also noticed that since I am bring back 3 phone numbers (cell, home, and business), it causes dups in my return from TASK. With the Task Inner Join,
I get results like
Before:
1 John M. Doe ToDo1:ToDO1, ToDo2:ToDo2
After:
1 John M. Doe ToDo1:ToDO1,ToDo1:ToDO1,ToDo1:ToDO1, ToDo2:ToDo2,ToDo2:ToDo2,ToDo2:ToDo2
I took the max out of the 3 case statements and it ran the query. But it return a separate row for each phone number like so
1 John Doe 999-999-9999
1 John Doe 888-888-8888
1 John Doe 222-222-2222
How can I fix this?
LEFT JOIN (SELECT PERSON_ID, PHONE_TP_SHORT_DESC,
max(case when PHONE_TYPE = 'HOME' then PHONE_NUMBER end) as HOME_PHONE,
max(case when PHONE_TYPE = 'BUSINESS' then PHONE_NUMBER end) as BUSINESS_PHONE,
max(case when PHONE_TYPE = 'CELL' then PHONE_NUMBER end) as CELL_PHONE
FROM PHONE_VIEW) PHONE
ON PERSON.PERSON_ID = PHONE.PERSON_ID AND PHONE.PHONE_TYPE IN ('HOME','BUSINESS','CELL')
Here is my entire query which I am trying to debug
SELECT PERSON.PERSON_ID,
PERSON.FIRST_NAME,
PERSON.MIDDLE_NAME,
PERSON.LAST_NAME,
PERSON.USER_NAME,
PHONE.HOME_PHONE,
PHONE.BUSINESS_PHONE,
PHONE.CELL_PHONE,
LISTAGG(case
when SKILLS.SKILL_SHORT_DESC = 'ToDo1' then 'ToDo1:ToDo1'
when SKILLS.SKILL_SHORT_DESC = 'ToDo2' then 'ToDo2:ToDo2'
when SKILLS.SKILL_SHORT_DESC = 'ToDo3' then 'ToDo3:ToDo3'
when SKILLS.SKILL_SHORT_DESC = 'ToDo4' then 'ToDo4:ToDo4'
when SKILLS.SKILL_SHORT_DESC = 'ToDo5' then 'ToDo5:ToDo5'
when SKILLS.SKILL_SHORT_DESC = 'ToDo6' then 'ToDo6:ToDo6'
else ''
end, ',')
WITHIN GROUP (ORDER BY TASK.TASK_DESC ) AS TASK
FROM PERSON_VIEW PERSON
LEFT JOIN (SELECT PERSON_ID, PHONE_TP_SHORT_DESC,
max(case when PHONE_TYPE = 'HOME' then PHONE_NUMBER end) as HOME_PHONE,
max(case when PHONE_TYPE = 'BUSINESS' then PHONE_NUMBER end) as BUSINESS_PHONE,
max(case when PHONE_TYPE = 'CELL' then PHONE_NUMBER end) as CELL_PHONE
FROM PHONE_VIEW) PHONE
ON PERSON.PERSON_ID = PHONE.PERSON_ID AND PHONE.PHONE_TYPE IN ('HOME','BUSINESS','CELL')
LEFT JOIN (SELECT DISTINCT PERSON_ID, TASK_DESC FROM TASK_VIEW) SKILLS
ON SKILLS.PERSON_ID = PERSON.PERSON_ID
GROUP BY
PERSON.PERSON_ID,
PERSON.FIRST_NAME,
PERSON.MIDDLE_NAME,
PERSON.LAST_NAME,
PERSON.USER_NAME,
PHONE.HOME_PHONE,
PHONE.BUSINESS_PHONE,
PHONE.CELL_PHONE
I do see one problem with your query. You have a LEFT JOIN to the following subquery:
(SELECT PERSON_ID,
PHONE_TP_SHORT_DESC,
max(case when PHONE_TYPE = 'HOME' then PHONE_NUMBER end) as HOME_PHONE,
max(case when PHONE_TYPE = 'BUSINESS' then PHONE_NUMBER end) as BUSINESS_PHONE,
max(case when PHONE_TYPE = 'CELL' then PHONE_NUMBER end) as CELL_PHONE
FROM PHONE_VIEW) PHONE
However, in the ON condition you refer to a column called PHONE_TYPE:
ON PERSON.PERSON_ID = PHONE.PERSON_ID AND
PHONE.PHONE_TYPE IN ('HOME','BUSINESS','CELL')
There is no column called PHONE_TYPE available, and so I would expect an error from this. Sometimes the error message you get from a database (or compiler) can be misleading in that it doesn't always reveal the root cause of the problem.
Another possible cause for error is that you are using views, and they behave differently from tables.

SQL count sum and joining table

I ran into a question from my SQL class but did not have a solution to it. My query needs to show ONLY the movies that have same number of female and male actors.
I have three tables:
(table:field 1,field2):
Casting: actor_number, movie_number
Actor_List: id, name, gender
Movie_List: id, movie_name
The sub-query uses a little CASE() trick to increment counts conditionally (i.e. countif). The sub-query factoring syntax means we only execute the query once.
with cte as (
select m.movie_name
, sum(case when a.gender = 'M' then 1 else 0 end) as male_tot
, sum(case when a.gender = 'F' then 1 else 0 end) as female_tot
from casting c
join movie_list m
on c.movie_number = m.id
join actor_list a
on c.actor_number = a.id
group by m.name
)
select cte.*
from cte
where cte.male_tot = cte.female_tot ;

sql basic queries

Im new to sql and playing around with some example databases I have found.
I have a table called student as follows
id name expenditure item
1 dan 45 social
1 dan 60 books
2 sarah 32 food
3 matt 64 food
3 matt 71 social
I was trying to find students who spent money on both food and social but spent more money on social than food.
I tried:
Select name
from student
where item = 'social' and item = 'food'
No individual line of data has item = 'social' AND item = 'food'. As the WHERE clause applies to one row of data at a time, you can't do that.
What you need to do is group all the records together by student, and then check some characteristics of the data in that group.
SELECT
id,
name
FROM
student
GROUP BY
id,
name
HAVING
SUM(CASE WHEN item = 'social' THEN 1 ELSE 0 END) > 0
AND SUM(CASE WHEN item = 'food' THEN 1 ELSE 0 END) > 0
That gives all students who have some records for 'social' and some records for 'food'.
Then, you can add the following to get only those who spent more on 'social' than on 'food'.
AND SUM(CASE WHEN item = 'social' THEN expenditure ELSE 0 END)
>
SUM(CASE WHEN item = 'food' THEN expenditure ELSE 0 END)
where  item = 'social' and item = 'food'  Doesn't work because a single row can never be both. When comparing two rows from the same table the easiest way is to do a self join
SELECT
s1.Name
FROM student s1
INNER JOIN student s2
ON s1.id = s2.id
WHERE
s1.item = 'social' and s2.item = 'food'
and
s1.expenditure > s2.expenditure
Notes:
If you have multiple rows for the same type of expenditures you'll want to use Dem's approach of summing the different types and then comparing
If you want to include people who haven't spent any money on food you'll have to use a LEFT JOIN. You'll also to move the test for 'food' into the ON clause otherwise it will act like an inner join.
LEFT JOIN student s2
ON s1.id = s2.id
and s2.item = 'food'
SQL Fiddle Demo

SQL join two tables with a link table between - GORM/Grails

I am trying to build an output as such:
Name source source source
Tim Other TV Radio
Where I want to combine a Person table and a Source table:
Person
ID Name
1 Tim
Source
ID Name
1 Other
2 TV
3 Radio
Person_Source
p_id s_id
1 1
1 2
I have a query that builds out each source for time and outputs in multiple lines. I'm looking for a single result in the result set
select source.name
from person left join person_source
on person_source.person_source_id = person.id join source
on source.id = person_source.source_id
You could add a DISTINCT to your query:
select DISTINCT source.name
from person left join person_source
on person_source.person_source_id = person.id join source
on source.id = person_source.source_id
Not sure what flavour of SQL this is supposed to be but you could try the following:
SELECT
p.Name,
MAX(CASE s.ID WHEN 1 THEN s.Name END) AS source1,
MAX(CASE s.ID WHEN 2 THEN s.Name END) AS source2,
MAX(CASE s.ID WHEN 3 THEN s.Name END) AS source3
FROM Person p
INNER JOIN Person_Source ps ON p.ID = ps.p_id
INNER JOIN Source s ON ps.s_id = s.ID
GROUP BY
p.ID,
p.Name
You could also check the sources differently, using their names, like this:
…
MAX(CASE s.Name WHEN 'TV' THEN s.Name END) AS source1,
MAX(CASE s.Name WHEN 'Radio' THEN s.Name END) AS source2,
MAX(CASE s.Name WHEN 'Other' THEN s.Name END) AS source3
…
Note that a SQL query always return a fixed number of columns. You'll need to decide beforehand how many sources should be returned by the query, and if the number should depend on the actual number of possible sources in the Source table, you'll have to build your query dynamically, to include all the necessary sources but no more than necessary.