sql basic queries - sql

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

Related

How can I write a SQL query to find records related to a record in another table that aren't related to another record

I have a requirement to write a query that finds records related to a record in another table that aren't related to another record.
Below is an example of what I mean. I will happily rewrite this question and title if I can express the question in a better way (advice welcome).
Table company
id
1
2
3
Table company_partner
id company_id company_name
1 1 Nike
2 1 Reebok
3 2 Nike
4 3 Nike
In the above example, I would like all companies partnered with Nike but not if they are also partnered with Reebok. Using the above example that would be companies 2 and 3.
I can write a query that gives me all companies partnered with Nike:
SELECT c.id
FROM company c
INNER JOIN company_partner cp ON c.id = cp.company_id
WHERE
cp.company_name = 'Nike'
-- ignore cp.company_name = 'Reebok' ???
I am unclear how I can ignore companies that are also partnered with Reebok?
You should be able to use not in - like this:
SELECT c.id
FROM company c
INNER JOIN company_partner cp ON c.id = cp.company_id
WHERE cp.company_name = 'Nike'
AND c.id not in (
select id from company_partner where company_name = 'Reebok'
)
Aggregation provides one straightforward option:
SELECT company_id
FROM company_partner
GROUP BY company_id
HAVING COUNT(CASE WHEN company_name = 'Nike' THEN 1 END) > 0 AND
COUNT(CASE WHEN company_name = 'Reebok' THEN 1 END) = 0;

How to show all users name in SQL

How to show all user name even count is zero.
$users = DB::select('select count(c.status) as countEvent, sum(c.point) as totalPoint,u.name from users u left join campaign_users c on u.id=c.user_id where u.deleted_at IS NULL group by u.name order by countEvent desc');
I want to count every user attend how many events but some of them not attend at all which is zero. I want to display all but the result only displayed users who attend at least one.
users - id, name
campaign_users - status (1 for pending, 2 for attend, 3 for absent), point
Assuming you are interested in countEvent and totalPoint values only for status = 2 (i.e. Attend)
SELECT u.name
,SUM(CASE WHEN c.status = 2 THEN 1 ELSE 0 END) AS countEvent
,SUM(CASE WHEN c.status = 2 THEN c.point ELSE 0 END ) AS totalPoint
FROM users u
LEFT JOIN campaign_users c
ON u.id=c.user_id
WHERE u.deleted_at IS NULL
GROUP BY u.name
It will be great if you can provide the sample data and intended output to understand your requirement clearly.
I guess one of the right way to do it is to query first the names and then count the results of the query .

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;

How do I get counts for different values of the same column with a single totals row, using Postgres 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

Summing Course Credits with Business Logic

In order for a student to graduate high school with "emphasis" in a particular subject, they must complete at least one introductory course in a defined list that area of expertise, and three core courses. I am trying to show the number of credits a student has and can be used to assign a student an "emphasis". The problem is a student can take numerous introductory level classes that might apply to an area of expertise, but only one of them should count towards the total credits. Or visa-versa, they can't be assigned if they have four core courses but no introductory class. Here is my statement below. Everything about this is telling me it is a loop, but I understand that opening cursors is bad for performance, and there is a 't-sql' way of doing this most likely.
Select * From (Select ks.ks_id,
ks.student_name,
ks.current_grade_level,
cpp.title,
ks.bldg_id,
Sum(cc.credits) as 'Sum of Credits Per Expertise' ,
From #KidsSilo as ks
Inner Join cpp_courses as cc on cc.statecourse_id = ks.statecourse_id --- cpp_courses contains all the courses
Inner Join #Cpp_expertise as cpp on cc.cpp_ID = cpp.cpp_id --- cpp_expertise contains all the Expertise' and the id link to each cpp_courses
Where cpp.bldg_id = ks.bldg_id
--And cc.course_level = 'X' -- Introductory, Would a nested Select Top 1 1 work for this piece?
--And cc.course_level = 'C' -- Core classes
Group by
ks.ks_id,
cpp.title,
ks.student_name,
ks.current_grade_level,
ks.bldg_id) as t
Inner Join #Districts as d on d.bldg_id = t.bldg_id
order by t.bldg_id desc
If you have a courses table with:
course id
expertise
level
And an enrollment table that has students in the courses, then I would expect a query like this:
select e.student_id, c.expertise
from enrollment e join
courses c
on e.course_id = c.course_id
group by e.student_id, c.expertise
having sum(case when level = 'introductory' then 1 else 0 end) >= 1 and
sum(case when level = 'core' then 1 else 0 end) >= 3;
Your data model is very unclear, but perhaps this will help.