Select all values from last date that is shared between rows - sql

I’ve got a Postgresql table with counts for countries over time. Not every country has a count for each date, and some have NULL values. I’d like to get the counts for each country up to the last date every country has data for, excluding NULL values.
I made a DB Fiddle with example data.
Example:
country date count id
Germany 2020-05-25 10 1
Germany 2020-05-26 11 2
Germany 2020-05-27 12 3
Germany 2020-05-28 13 4
Italy 2020-05-25 20 5
Italy 2020-05-26 21 6
Italy 2020-05-27 22 7
Italy 2020-05-28 23 8
France 2020-05-25 30 9
France 2020-05-26 31 10
France 2020-05-27 NULL 11
I’d like to get back the following:
country date count id
Germany 2020-05-25 10 1
Germany 2020-05-26 11 2
Italy 2020-05-25 20 5
Italy 2020-05-26 21 6
France 2020-05-25 30 9
France 2020-05-26 31 10
I’ve searched, but I’m relatively new to SQL and don’t seem to know what keywords to search for.

You can use window functions to count the number of rows with dates and then compare to the number of countries:
SELECT c.*
FROM (SELECT c.*, COUNT(count) over (partition by date) as num_countries_on_date
FROM countries c
) c
WHERE num_countries_on_date = (SELECT COUNT(DISTINCT c2.country) FROM countries c2);
Here is a db<>fiddle.
If you wanted to generate data for a range of dates -- sort of the opposite problem -- you could use a CROSS JOIN to generate the rows, a LEFT JOIN to bring in the data, and COALESCE() to turn NULL to 0:
SELECT c.country, d.date, coalesce(co.count, 0) as count
FROM (SELECT DISTINCT country FROM countries) c CROSS JOIN
generate_series('2020-05-26'::date, '2020-05-27'::date, interval '1 day') d(date) LEFT JOIN
countries co
ON co.country = c.country AND co.date = d.date;

Related

How to get top values when there is a tie

I am having difficulty figuring out this dang problem. From the data and queries I have given below I am trying to see the email address that has rented the most movies during the month of September.
There are only 4 relevant tables in my database and they have been anonymized and shortened:
Table "cust":
cust_id
f_name
l_name
email
1
Jack
Daniels
jack.daniels#google.com
2
Jose
Quervo
jose.quervo#yahoo.com
5
Jim
Beam
jim.beam#protonmail.com
Table "rent"
inv_id
cust_id
rent_date
10
1
9/1/2022 10:29
11
1
9/2/2022 18:16
12
1
9/2/2022 18:17
13
1
9/17/2022 17:34
14
1
9/19/2022 6:32
15
1
9/19/2022 6:33
16
3
9/1/2022 18:45
17
3
9/1/2022 18:46
18
3
9/2/2022 18:45
19
3
9/2/2022 18:46
20
3
9/17/2022 18:32
21
3
9/19/2022 22:12
10
2
9/19/2022 11:43
11
2
9/19/2022 11:42
Table "inv"
mov_id
inv_id
22
10
23
11
24
12
25
13
26
14
27
15
28
16
29
17
30
18
31
19
31
20
32
21
Table "mov":
mov_id
titl
rate
22
Anaconda
3.99
23
Exorcist
1.99
24
Philadelphia
3.99
25
Quest
1.99
26
Sweden
1.99
27
Speed
1.99
28
Nemo
1.99
29
Zoolander
5.99
30
Truman
5.99
31
Patient
1.99
32
Racer
3.99
and here is my current query progress:
SELECT cust.email,
COUNT(DISTINCT inv.mov_id) AS "Rented_Count"
FROM cust
JOIN rent ON rent.cust_id = cust.cust_id
JOIN inv ON inv.inv_id = rent.inv_id
JOIN mov ON mov.mov_id = inv.mov_id
WHERE rent.rent_date BETWEEN '2022-09-01' AND '2022-09-31'
GROUP BY cust.email
ORDER BY "Rented_Count" DESC;
and here is what it outputs:
email
Rented_Count
jack.daniels#google.com
6
jim.beam#protonmail.com
6
jose.quervo#yahoo.com
2
and what I want it to be outputting:
email
jack.daniels#google.com
jim.beam#protonmail.com
From the results I am actually getting I have a tie for first place (Jim and Jack) and that is fine but I would like it to list both tieing email addresses not just Jack's so you cant do anything with rows or max I don't think.
I think it must have something to do with dense_rank but I don't know how to use that specifically in this scenario with the count and Group By?
Your creativity and help would be appreciated.
You're missing the FETCH FIRST ROWS WITH TIES clause. It will work together with the ORDER BY clause to get you the highest values (FIRST ROWS), including ties (WITH TIES).
SELECT cust.email
FROM cust
INNER JOIN rent
ON rent.cust_id = cust.cust_id
INNER JOIN inv
ON inv.inv_id = rent.inv_id
INNER JOIN mov
ON mov.mov_id = inv.mov_id
WHERE rent.rent_date BETWEEN '2022-09-01' AND '2022-09-31'
GROUP BY cust.email
ORDER BY COUNT(DISTINCT inv.mov_id) DESC
FETCH FIRST 1 ROWS WITH TIES

Sum of Different Rows on Condition

I have a query that looks like this:
with x as (
select *, date_format(SomeDate, 'MMM') as Month from SomeTable
)
select *, count(Package) over (partition by Company, Region order by SomeDate) as BoxCount
from x
Table SomeTable basically looks like this:
Package Company Region SomeDate
1 A East 20220101
2 A East 20220105
3 A East 20220310
4 A East 20220411
5 A East 20220502
6 A West 20220405
7 A West 20220505
8 A West 20220508
9 B East 20220106
10 B East 20220212
11 B East 20220311
12 B West 20220505
13 B North 20220908
The result I want is basically this:
Company Month BoxCount
A Jan 2
A Mar 3
A Apr 4
A May 8
B Jan 1
B Feb 2
B Mar 3
B May 4
B Sept 5
What I want is basically a CUSUM by Company and Region, however, when it's the month of the May, I'd like to calculate Region West with Region East then in September I'd like to calculate all 3 regions for each respective company. Is there a way to do this in Spark SQL?
My Query gives the cumulative sum, but I'm not sure how to go about from here.

SQL Query - in how many countries we have users and in how many countries we don't have users

Need a SQL query that will answer this question.
Please tell us in how many countries we have users and in how many countries we don't have users. This should be presented in a table, in a single row and two columns (one with the number of countries in which we have users and one with the number of countries where we don't have users).
We have two tables: AllCountriesInTheWorld and AllUsers.
Table AllCountriesInTheWorld
country_id
country_name
1
USA
2
France
3
Italy
4
Portugal
5
Spain
6
Canada
7
UK
8
China
9
Japan
10
Germany
Table AllUsers
user_id
user_name
country_id
a1
John
4
b2
Jane
6
c3
Tony
6
d4
Dan
9
e5
Dave
1
Thanks a lot in advance guys!
If you LEFT JOIN the 2 tables on their common key,
then you can get all countries with or without users.
select *
from AllCountriesInTheWorld ctry
left join AllUsers usr
on usr.country_id = ctry.country_id
country_id
country_name
user_id
user_name
country_id
1
USA
e5
Dave
1
2
France
null
null
null
3
Italy
null
null
null
4
Portugal
a1
John
4
5
Spain
null
null
null
6
Canada
b2
Jane
6
6
Canada
c3
Tony
6
7
UK
null
null
null
8
China
null
null
null
9
Japan
d4
Dan
9
10
Germany
null
null
null
If you then GROUP BY the countries then you can COUNT how many USER_ID each country has.
count(usr.user_id) as total_users
country_id
total_users
1
1
2
0
3
0
4
1
5
0
6
2
7
0
8
0
9
1
10
0
Then just wrap that in a CTE or sub-query and use conditional aggregation on the total users.
;with CTE_COUNTRIES as (
...
)
select
sum(case when total_users > 0 then 1 else 0 end) as countries_with_users
, sum(case when total_users = 0 then 1 else 0 end) as countries_without_users
from CTE_COUNTRIES
countries_with_users
countries_without_users
4
6

Replace Id of one column by a name from another table while using the count statement?

I am trying to get the count of patients by province for my school project, I have managed to get the count and the Id of the province in a table but since I am using the count statement it will not let me use join to show the ProvinceName instead of the Id (it says it's not numerical).
Here is the schema of the two tables I am talking about
The content of the Province table is as follow:
ProvinceId
ProvinceName
ProvinceShortName
1
Terre-Neuve-et-Labrador
NL
2
Île-du-Prince-Édouard
PE
3
Nouvelle-Écosse
NS
4
Nouveau-Brunswick
NB
5
Québec
QC
6
Ontario
ON
7
Manitoba
MB
8
Saskatchewan
SK
9
Alberta
AB
10
Colombie-Britannique
BC
11
Yukon
YT
12
Territoires du Nord-Ouest
NT
13
Nunavut
NU
And here is n sample data from the Patient table (don't worry it's fake data!):
SS
FirstName
LastName
InsuranceNumber
InsuranceProvince
DateOfBirth
Sex
PhoneNumber
2
Doris
Patel
PATD778276
5
1977-08-02
F
514-754-6488
3
Judith
Doe
DOEJ7712917
5
1977-12-09
F
418-267-2263
4
Rosemary
Barrett
BARR05122566
6
2005-12-25
F
905-638-5062
5
Cody
Kennedy
KENC047167
10
2004-07-01
M
604-833-7712
I managed to get the patient count by province using the following statement:
select count(SS),InsuranceProvince
from Patient
full JOIN Province ON Patient.InsuranceProvince = Province.ProvinceId
group by InsuranceProvince
which gives me the following table:
PatientCount
InsuranceProvince
13
1
33
2
54
3
4
4
608
5
1778
6
25
7
209
8
547
9
649
10
6
11
35
12
24
13
How can I replace the id's with the correct ProvinceShortName to get the following final result?
ProvinceName
PatientCount
NL
13
PE
33
NS
54
NB
4
QC
608
ON
1778
MB
25
SK
209
AB
547
BC
649
YT
6
NT
35
NU
24
Thanks in advance!
So you can actually just specify that in the select. Note that it's best practise to include the thing you group by in the select, but since your question is so specific then...
SELECT ProvinceShortName, COUNT(SS) AS PatientsInProvince
FROM Patient
JOIN Province ON Patient.InsuranceProvince=Province.ProvinceId
GROUP BY InsuranceProvince;
I would suggest:
select pr.ProvinceShortName, count(*)
from Patient p join
Province pr
on p.InsuranceProvince = pr.ProvinceId
group by pr.ProvinceShortName
order by min(pr.ProvinceId);
Notes:
The key is including the columns you want in the select and group by.
You seem to want the results in province number order, so I included an order by.
There is no need to count the non-NULL values of SS. You might as well use count(*).
Table aliases make the query easier to write and to read.
I assume that you need to show the patient count by province.
SELECT
Province.ProvinceShortName AS [ProvinceName]
,COUNT(1) as [PatinetCount]
FROM Patient
RIGHT JOIN Province ON Patient.InsuranceProvince = Province.ProvinceId
GROUP BY ProvinceShortName
Just altering your query to
select ProvinceShortName As PatientCount,count(InsuranceProvince) As PatientCount
from Patient
full JOIN Province ON Patient.InsuranceProvince = Province.ProvinceId
group by ProvinceShortName

Get count per year of data with begin and end dates

I have a set of data that lists each employee ever employed in a certain type of department at many cities, and it lists each employee's begin and end date.
For example:
name city_id start_date end_date
-----------------------------------------
Joe Public 54 3-19-1994 9-1-2002
Suzi Que 54 10-1-1995 9-1-2005
What I want is each city's employee count for each year in a particular period. For example, if this was all the data for city 54, then I'd show this as the query results if I wanted to show city 54's employee count for the years 1990-2005:
city_id year employee_count
-----------------------------
54 1990 0
54 1991 0
54 1992 0
54 1993 0
54 1994 1
54 1995 2
54 1996 2
54 1997 2
54 1998 2
54 1999 2
54 2000 2
54 2001 2
54 2002 2
54 2003 1
54 2004 1
54 2005 1
(Note that I will have many cities, so the primary key here would be city and year unless I want to have a separate id column.)
Is there an efficient SQL query to do this? All I can think of is a series of UNIONed queries, with one query for each year I wanted to get numbers for.
My dataset has a few hundred cities and 178,000 employee records. I need to find a few decades' worth of this yearly data for each city on my dataset.
replace 54 with your parameter
select
<city_id>, c.y, count(t.city_id)
from generate_series(1990, 2005) as c(y)
left outer join Table1 as t on
c.y between extract(year from t.start_date) and extract(year from t.end_date) and
t.city_id = <city_id>
group by c.y
order by c.y
sql fiddle demo