SQL statement to select all dead people - sql

I have tables:
City:
zip, name,...
People:
id, city_zip(refers to city.zip), born_time, dead_time
I need to select data about cities where ALL people from that city are dead: born_time NOT NULL AND dead_time < NOW()
because we do not assume that someone is dead if we do not have information.

You can use not exists:
select c.*
from city c
where not exists (
select 1
from people p1
where p1.city_zip = c.zip and (dead_time is null or dead_time > now())
)
This would also return cities that have no people at all. If that's something you want to avoid, then another option is aggregation:
select c.*
from city c
inner join people p on p.city_zip = c.zip
group by c.zip
having max(case when dead_time is null or dead_time > now() then 1 else 0 end) = 0
select c.* ... from city c ... group by c.zip is valid standard SQL (assuming that zip is the primary key of table city). However, all databases do not support it, in which case you will need to enumerate the columns you want in both the select and group by clauses.

Related

CASE statement leaving NULL

I am trying to make it so there are no NULLs in the JobTitle column. When I do it without at the CASE, I get two JobTitle columns (one for males and one for females) and some have NULL. I want to make it so there is just one column listing all the job titles then listing the total number of males/females next to that column in their own columns. (This is using the AdventureWorks db)
USE AdventureWorks2019
GO
select count(hre.gender) AS NumberOfFemales, JobTitle
into #FemalesPerJobTitle
from HumanResources.employee as hre
group by JobTitle, Gender
having gender = 'F';
SELECT COUNT(HRE.Gender) AS NumberOfMales, JobTitle
INTO #MalesPerJobTitle
FROM HumanResources.Employee AS HRE
GROUP BY JobTitle, Gender
HAVING gender = 'M';
SELECT FPJ.NumberOfFemales AS Females
, MPJ.NumberOfMales AS Males
,
CASE
WHEN MPJ.JobTitle IS NULL THEN FPJ.JobTitle
END AS JobTitle
FROM #FemalesPerJobTitle AS FPJ
FULL OUTER JOIN #MalesPerJobTitle AS MPJ
ON FPJ.JobTitle = MPJ.JobTitle
SELECT
JobTitle,
SUM(CASE WHEN gender = ‘M’ THEN 1 ELSE 0 END) AS Males,
SUM(CASE WHEN gender = ‘F’ THEN 1 ELSE 0 END) AS Females
FROM HumanResources.Employee
GROUP BY JobTitle
You could probably put a COALESCE around the SUMs if it is returning any nulls and you want zeros instead
It sounds like you want to produce a result set that
list all job titles, with
the count of men and women holding each title
If you have a table holding the complete list of job titles (which the database should since job title is/would seem to be a proper entity), I would use that as the source for the job title column.
But, if you don't have that, I'd do something like the following and use a derived table to give the the distinct set of job titles in both source tables:
select coalesce( f.NumberOfFemales , 0 ) Females ,
coalesce( m.NumberOfMales , 0 ) Males ,
jt.JobTitle JobTitle
from ( select JobTitle from #FemalesPerJobTitle
UNION select JobTitle from #MalesPerJobTitle
) jt
left join #FemalesPerJobTitle f on f.JobTitle = jt.JobTitle
left join #MalesPerJobTitle m on m.JobTitle = jt.JobTitle
Another way to go about it (and probably easier for others to understand) would be to do something like this:
select JobTitle = t.JobTitle,
NumberOfFemales = sum( t.NumberOfFemales ) ,
NumberOfMales = sum( t.NumberOfMales )
from ( select JobTitle,
NumberOfFemales = NumberOfFemales,
NumberOfMales = 0
from #FemalesPerJobTitle
UNION ALL
select JobTitle,
NumberOfFemales = 0 ,
NumberOfMales = NumberOfMales
from #MalesPerJobTitle
) t
group by t.JobTitle
Here, the derived table uses UNION ALL to not eliminate duplicates because (1) there shouldn't be any, (2) the query will be more efficient, and (3) the group by clause will take care of the roll-up.
Here you go, I used CTE which makes more sense than temp tables here.
WITH Fcount AS
(
select count(hre.gender) AS NumberOfFemales, JobTitle
from HumanResources.employee as hre
WHERE gender = 'F'
group by JobTitle
), Mcount AS
(
SELECT COUNT(HRE.Gender) AS NumberOfMales, JobTitle
FROM HumanResources.Employee AS HRE
WHERE gender = 'M'
GROUP BY JobTitle
), titles CASE
(
SELECT DISTINCT JobTitle FROM Fcount
UNION ALL
SELECT DISTINCT JobTitle FROM MCount
)
SELECT titles.JobTitle,
FPJ.NumberOfFemales AS Females,
MPJ.NumberOfMales AS Males
FROM titles
LEFT JOIN Fcount AS FPJ ON titles.JobTitle = FPJ.JobTitle
LEFT JOIN Mcount AS MPJ ON titles.JobTitle = MPJ.JobTitle

Postgres Question: Aren't both a and b correct?

For questions below, use the following schema definition.
restaurant(rid, name, phone, street, city, state, zip)
customer(cid, fname, lname, phone, street, city, state, zip)
carrier(crid, fname, lname, lp)
delivery(did, rid, cid, tim, size, weight)
pickup(did, tim, crid)
dropoff(did, tim, crid)
It's a schema for a food delivery business that employs food carriers (carrier table).
Customers (customer table) order food from restaurants (restaurant table).
The restaurants order a delivery (delivery table); to deliver food from restaurant to customer.
The pickup table records when carrier picks up food at restaurant.
The dropoff table records when carrier drops off food at customer.
1.Find customers who have less than 5 deliveries.
a. select cid,count()
from delivery
group by cid
having count() < 5;
b. select a.cid,count()
from customer a
inner join delivery b
using(cid)
group by a.cid
having count() < 5;
c. select a.cid,count()
from customer a
left outer join delivery b
on a.cid=b.cid
group by a.cid
having count() < 5;
d. select cid,sum(case when b.cid is not null then 1 else 0 end)
from customer a
left outer join delivery b
using (cid)
group by cid
having sum(case when b.cid is not null then 1 else 0 end) < 5;
e. (write your own answer)
No, they are not correct. They miss customers who have had no deliveries.
The last is the best of a bunch of not so good queries. A better version would be:
select c.cid, count(d.cid)
from customer c left outer join
delivery d
on c.cid = d.cid
group by c.cid
having count(d.cid) < 5;
The sum(case) is over kill. And Postgres even offers a better solution than that!
count(*) filter (where d.cid is not null)
But count(d.cid) is still more concise.
Also note the use of meaningful table aliases. Don't get into the habit of using arbitrary letters for tables. That just makes queries hard to understand.

SQL WHERE both values of a variable exist

Suppose that I have a table of global box office information including columns "filmName", "country" and "earnings". The question is how to find out the films that sell better in country A than in country B. Here is my answer:
SELECT filmName
FROM Boxoffice
WHERE (SELECT earnings FROM Boxoffice WHERE country = "A") >
(SELECT earnings FROM Boxoffice WHERE country = "B")
GROUP BY filmName
But then I found out that there are some films that are not shown in both countries. I wonder how I can add the condition to the films that are shown in both countries to my existed answer. And I also have no idea if my answer has any problem since I do not have the real data.
I think a self join would be simpler:
SELECT a.filmname
FROM boxoffice a
JOIN boxoffice b ON a.country = 'A' AND b.country = 'B' AND a.earnings > b.earnings;
It seems filmname and country is the unique key for your table, i.e. there is one row per film and country.
One way to get films that sell better in country A than B is to aggregate and compare the earnings in the HAVING clause:
select filmname
from boxoffice
group by filmname
having max(case when country = 'A' then earnings end) >
max(case when country = 'B' then earnings end)
order by filmname;
Another way is to join, e.g.:
select a.filmname
from (select * from boxoffice where country = 'A') a
join (select * from boxoffice where country = 'B') b
on a.filmname = b.filmname and a.earnings > b.earnings
order by a.filmname;

fetching duplicate tuples in Oracle using SQL

Here are the two table structures
I have two tables where I am trying to fetch some duplicate records based on some condition like where a.fname=b.fname and a.phone_no<>b.phone_no
But also I need to include other column 'address' which is in table 2 and introduce the same condition for duplicate checking for address as well.
SELECT
"Fname"||' '||"Lname" AS "Customer_Name",
COUNT(*) AS "Countof"
FROM "S_CONTACT" A
WHERE EXISTS (
SELECT 1
FROM "S_CONTACT" B
WHERE A."PHONE" != B."PHONE"
AND A."Fname" = B."Fname"
AND A."EMAIL"=B."EMAIL"
AND A."Lname"=B."Lname"
AND "DOB" IS NULL
)
GROUP BY "Fname","Lname","EMAIL"
HAVING count(*) >1;
The above sql gives me a list of customers with duplicate names and email.
But I do not know how to introduce the column address in this sql which is from different table t2
If you are trying to find duplicates in one table:
select c.fname, c.lname, c.email
from contacts c
group by c.fname, c.lname, c.email
having min(c.phone) <> max(c.phone);
If you want to count null as a different value, then use:
having min(c.phone) <> max(c.phone) or count(c.phone) <> count(*)
You can do the same thing on the second table:
select c.fname, c.lname, c.email
from second_table c
group by c.fname, c.lname, c.email
having min(c.address) <> max(c.address) or count(c.address) <> count(*)
If you need the results in the same result set, then use union or union all or some similar mechanism.

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 ;