Left outer join for multiple users - sql

I have a database for rounds of golf. I want to see how many rounds each user has per month for the last year.
To do that I created this view last_12_months with this code
SELECT date_part('month'::text, dates.date) AS month,
date_part('year'::text, dates.date) AS year
FROM ( SELECT (generate_series((now() - '1 year'::interval), now(), '1 mon'::interval))::date AS date) dates;
month
year
1
2020
2
2020
and so on to ...
12
2020
The summary view for counting the rounds is very simple also rounds_count_by_users
user_id
month
year
count_all
1
1
2020
15
1
3
2020
12
1
5
2020
10
2
4
2020
7
2
8
2020
6
2
9
2020
3
Now for what I want, querying for each user with left outer join is quite simple with
select *
from last_12_months
left outer join rounds_count_by_users
on last_12_months.month = rounds_count_by_users.month
and last_12_months.year = rounds_count_by_users.year
and user_id = 1
Which gives me all the months even when the user has no played rounds. What I would like however is to be able to do this for every user and make a materialized view for easy querying. Is there a nice and easy way of doing this? To be clear this is the final table I want.
This query doesn't work at least, that much I know.
select *
from last_12_months
left outer join rounds_count_by_users
on last_12_months.month = rounds_count_by_users.month
and last_12_months.year = rounds_count_by_users.year
where user_id = 1
user_id
month
year
count_all
1
1
2020
15
1
2
2020
null
1
3
2020
12
1
4
2020
null
1
5
2020
10
1
6
2020
null
1
7
2020
null
1
8
2020
null
1
9
2020
null
1
10
2020
null
1
11
2020
null
1
12
2020
null
2
1
2020
null
2
2
2020
null
2
3
2020
null
2
4
2020
7
2
5
2020
null
2
6
2020
null
2
7
2020
null
2
8
2020
6
2
9
2020
3
2
10
2020
null
2
11
2020
null
2
12
2020
null
I made an SQL Fiddle for this (slightly different values but same schema)
PS: I know about table aliases and data modeling and that stuff. My question is strictly about how to achieve the final result.

This query:
select * from last_12_months
where (year = year(current_date) - 1 and month >= month(current_date))
or
(year = year(current_date) and month < month(current_date))
returns the rows of last_12_months for the last 12 months (not including the current month).
This query:
select distinct user_id from rounds_count_by_users
returns all the distinct user_ids (it would be better if these ids where stored in a users table).
You must CROSS join the above queries and then LEFT join rounds_count_by_users:
select u.user_id, m.month, m.year, r.count_all
from (
select * from last_12_months
where (year = year(current_date) - 1 and month >= month(current_date))
or
(year = year(current_date) and month < month(current_date))
) m cross join (select distinct user_id from rounds_count_by_users) u
left outer join rounds_count_by_users r
on m.month = r.month and m.year = r.year and u.user_id = r.user_id
order by u.user_id, m.month
See the demo.

So I managed it to do it this way after help in the comments.
This leaves me with exactly what I wanted.
with all_rows as (
select * from view_last_12_months inner join users on true
)
select id as user_id, all_rows.year, all_rows.month, count_all
from all_rows left outer join view_rounds_count_last_year last12
on last12.month = all_rows.month
and last12.year = all_rows.year
and all_rows.id = last12.user_id

Related

Select each distinct of table 1 for every value of table 2

I'm essentially working with three tables; first: a Month/Year calendar, second: Customer data (that does have a parent/child relationship with itself), and third: sales data. I would like to be able to show sales for each customer for each month in a date range, regardless of months where there may have been no sales for one or more customer.
I can get queries to show every month/year in my range, and calculate sales totals for months with sales. However, since the account numbers are joining to the calendar through a table that doesn't have values for every month, I can't figure out how to list the accounts with null sales.
The closest I've gotten so far:
with cumulative as (
select MONTH(s.docdate) [Month]
, YEAR(s.docdate) [Year]
, s.account [Account]
, sum(s.amount) [sales]
from sales s
group by MONTH(s.docdate), YEAR(s.docdate), s.account
)
select c.monthno [Month]
, c.year [Year]
, (select account from customers where account=s.account) [Account]
, s.sales
from cumulative s
right join calendar c
on datefromparts(s.year, s.month, 1) = datefromparts(c.year,c.monthno,1)
order by c.year, c.monthno
resulting with;
Month Year Account sales
1 2020 1 25
1 2020 2 90
2 2020 null null
3 2020 3 45
3 2020 4 65
4 2020 null null
5 2020 1 120
5 2020 2 45
6 2020 null null
7 2020 null null
etc.
example setup here: https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=b8ae260f2901693bf4cca75fb2451649
If I try to use a left or right join to bring in the customer table, collapses results to only months and accounts with sales values.
with cumulative as (
select MONTH(s.docdate) [Month]
, YEAR(s.docdate) [Year]
, s.account [Account]
, sum(s.amount) [sales]
from sales s
group by MONTH(s.docdate), YEAR(s.docdate), s.account
)
select c.monthno [Month]
, c.year [Year]
, c2.account [Account]
, s.sales
from cumulative s
right join calendar c
on datefromparts(s.year, s.month, 1) = datefromparts(c.year,c.monthno,1)
right join customers c2
on s.account=c2.account
order by c.year, c.monthno
gives:
Month Year Account sales
1 2020 1 25
1 2020 2 90
3 2020 3 45
3 2020 4 65
5 2020 2 45
5 2020 1 120
1 2021 2 75
Output I'd like to see:
Month Year Account sales
1 2020 1 25
1 2020 2 90
1 2020 3 null
1 2020 4 null
2 2020 1 null
2 2020 2 null
2 2020 3 null
2 2020 4 null
How can I get every account number from customer to show up for each month in calendar?
If you combine Calendar and Customers with a cross join, you get a complete set of accounts and dates. Make that a CTE, and then use your Cumulative CTE to gather those results. Now you can select from the Calendar/Customers table with a left join to the sales data:
with BaseTable
as (
select c.MonthNo as Month
, c.Year
, cust.Account
from [CALENDAR] c
cross join Customers cust
), cumulative as (
select MONTH(s.docdate) [Month]
, YEAR(s.docdate) [Year]
, s.account [Account]
, sum(s.amount) [sales]
from sales s
group by MONTH(s.docdate), YEAR(s.docdate), s.account
)
select bt.Month
, bt.Year
, bt.Account
, c.sales
from BaseTable bt
left join cumulative c
on c.month = bt.Month
and c.Year = bt.Year
and c.Account = bt.Account

How to use 2 columns as "key" to get MAX value of selection (and on to next "key") in a SQL query

using a SQL query I am trying to get a max value from multiple rows, using 2 columns as 'key', and then sum them and move on t next 'key'
Here is an example table. It has years, userid and points. Each year has several weeks.
What I want to do is to take each users MAX points for each year and SUM them.
year
userid
week
points
2020
1
1
3
2020
1
3
3
2020
1
3
5
2020
1
4
12
2020
2
1
4
2020
2
2
4
2020
2
3
6
2020
2
4
10
2021
1
1
4
2021
1
2
5
2021
1
3
8
2021
1
4
9
2021
2
1
3
2021
2
2
6
2021
2
3
7
2021
2
4
13
I'd like the result for each year to be
User 1:
2020, 1, 12
2021, 1, 9
User 2:
2020, 2, 10
2021, 2, 13
...and after summing them, sorted by points:
userid
points
2
33
1
21
...and so forth (adding on users and years)
Any help is very much appreciated.
Per Gordon's helpful answer this is the query:
SELECT username, userdb.userid, SUM(points) as points FROM (SELECT standing.*, row_number() over (partition by standing.userid, year ORDER BY points desc) AS seqnum FROM standing) t JOIN userdb on userdb.userid = t.userid WHERE seqnum = 1 GROUP BY userid ORDER BY points DESC
You can use two levels of aggregation:
select userid, sum(max_points)
from (select userid, year, max(points) as max_points
from t
group by userid, year
) uy
group by userid;
Alternatively, you could handle this by filtering such as by using a window function:
select userid, sum(points)
from (select t.*,
row_number() over (partition by userid, year order by points desc) as seqnum
from t
) t
where seqnum = 1
group by userid;

fetching records for previous month

item loc year month quantity startdate
XYZ A 2020 1 3 23-06-2020
ABC B 2020 2 218 24-06-2020
SDC C 2020 6 107 25-06-2020
QWE D 2020 7 144 25-06-2020
XYZ A 2019 12 89 23-06-2020
ABC B 2019 11 218 24-06-2020
SDC C 2020 5 117 25-06-2020
QWE D 2020 6 144 25-06-2020
if i consider the above table then my output should look like this:
item loc year month quantity startdate
XYZ A 2020 1 89 23-06-2020
ABC B 2020 2 3 24-06-2020
SDC C 2020 6 117 25-06-2020
QWE D 2020 7 144 25-06-2020
so u can see that only quantities values changed and that we are taking from previos months and rest columns values are as it is.
It looks like you want window function lag(). For your sample data, this would produce the desired results:
select *
from (
select
item,
loc,
year,
month,
lag(quantity) over(partition by item, loc order by year, month) quantity,
startdate
from mytable
) t
where quantity is not null
Consider query which works in Access database:
SELECT Table1.*, (SELECT TOP 1 quantity FROM Table1 AS Dupe
WHERE Dupe.item = Table1.item AND Dupe.loc = Table1.loc
AND DateSerial(Dupe.[Year],Dupe.[Month],1)<DateSerial(Table1.[Year],Table1.[Month],1)
ORDER BY DateSerial(Dupe.[Year],Dupe.[Month],1)) AS PrevQty
FROM Table1;
If you want to return 0 when there is a gap in month sequence, consider:
SELECT Table1.*, Nz((SELECT quantity FROM Table1 AS Dupe
WHERE Dupe.item = Table1.item AND Dupe.loc = Table1.loc
AND DateSerial(Dupe.[Year],Dupe.[Month],1)=DateAdd("m",-1,DateSerial(Table1.[Year],Table1.[Month],1))
ORDER BY DateSerial(Dupe.[Year],Dupe.[Month],1)),0) AS PrevQty
FROM Table1;
Or
SELECT Q1.*, Nz(Q2.quantity,0) AS PrevQty FROM (
SELECT Table1.*, DateSerial([Year],[Month],1) AS FD FROM Table1) AS Q1
LEFT JOIN (
SELECT Table1.*, DateAdd("m",+1,DateSerial([Year],[Month],1)) AS PD FROM Table1) AS Q2
ON Q1.FD=Q2.PD AND Q1.item=Q2.item and Q1.loc=Q2.loc;

Return zero value for all the month in series with count zero

This is my query:
SELECT STAFF.stf_first_name + '' + STAFF.stf_last_name As Name,
DATENAME(month,RES_HAB_DATA.reshabdata_data_date) As Month,
SUM(case when RES_HAB_DATA.reshabdata_duration > 0
then (RES_HAB_DATA.reshabdata_duration/15) else 0 end) As ServiceDeliveryTime,
MONTH(RES_HAB_DATA.reshabdata_data_date) As MonthNumber
FROM RES_HAB_DATA
JOIN RES_HAB ON RES_HAB_DATA.reshab_id = RES_HAB.reshab_id
JOIN STAFF ON RES_HAB_DATA.staff_id = STAFF.stf_id
WHERE RES_HAB.serv_id = 30
AND RES_HAB_DATA.reshabdata_data_date >= '1/1/2015'
GROUP BY STAFF.stf_last_name,
STAFF.stf_first_name,
DATENAME(month,RES_HAB_DATA.reshabdata_data_date),
MONTH(RES_HAB_DATA.reshabdata_data_date)
ORDER BY MonthNumber
Which produces result set as:
Name Month ServiceDeliveryTime MonthNumber
----------------------------------------------------------------------------
mb January 52 1
MikeCasey January 10 1
MikeCasey February 4 2
PrecisionCareSupport February 0 2
MikeCasey March 4 3
PrecisionCareSupport March 0 3
MikeCasey April 8 4
PrecisionCareSupport April 0 4
MikeCasey May 16 5
MikeCasey July 4 7
PrecisionCareSupport July 1 7
PrecisionCareSupport August 0 8
MikeCasey September 10 9
MikeCasey October 12 10
I am generating a chart and would like to generate series for that chart but the series should be formed in a way that each series label must have all the tick values(zero if missing respective month). In Simple words,I want resultset as:
Name Month ServiceDeliveryTime MonthNumber
----------------------------------------------------------------------------
mb January 52 1
mb February 0 2
mb March 0 3
mb April 0 4
- - 0 5
Upto December then series will continue for Client MikeCasey upto December and so on...for all the series Labels.If any of the tick is missing for that client there will be value zero for that month.
How Can I produce this result set ? I want some uniform solution because there can be number of such queries for different charts.
Mr Shaw, try this
;WITH
(SELECT STAFF.stf_first_name + '' + STAFF.stf_last_name As Name,
DATENAME(month,RES_HAB_DATA.reshabdata_data_date) As Month,
SUM(case when RES_HAB_DATA.reshabdata_duration > 0
then (RES_HAB_DATA.reshabdata_duration/15) else 0 end) As ServiceDeliveryTime,
MONTH(RES_HAB_DATA.reshabdata_data_date) As MonthNumber
FROM RES_HAB_DATA
JOIN RES_HAB ON RES_HAB_DATA.reshab_id = RES_HAB.reshab_id
JOIN STAFF ON RES_HAB_DATA.staff_id = STAFF.stf_id
WHERE RES_HAB.serv_id = 30
AND RES_HAB_DATA.reshabdata_data_date >= '1/1/2015'
GROUP BY STAFF.stf_last_name,
STAFF.stf_first_name,
DATENAME(month,RES_HAB_DATA.reshabdata_data_date),
MONTH(RES_HAB_DATA.reshabdata_data_date)
) AS mytable
SELECT
myTableName.Name
,mytableMonth.Month
,ISNULL(mytable.ServiceDeliveryTime,0)
,mutableMonth.MonthNumber
FROM
(SELECT DISTINCT Name from mytable) mytableName
CROSS JOIN (SELECT DISTINCT Month,MonthNumber FROM mytable) mytableMonth
LEFT INNER JOIN mytable ON mytableName.Name = mytable.Name AND mytableMonth.Month = mytable.Month AND mytableMonthNumber = mytable.MonthNumber
ORDER BY mytableName.Name, mytableMonth.MonthNumber
I have taken all distinct months and names from your data and done a cross join.
;WITH mytable(Name,Month,ServiceDeliveryTime,MonthNumber) AS
(
SELECT STAFF.stf_first_name + '' + STAFF.stf_last_name As Name,
DATENAME(month,RES_HAB_DATA.reshabdata_data_date) As Month,
SUM(case when RES_HAB_DATA.reshabdata_duration > 0
then (RES_HAB_DATA.reshabdata_duration/15) else 0 end) As ServiceDeliveryTime,
MONTH(RES_HAB_DATA.reshabdata_data_date) As MonthNumber
FROM RES_HAB_DATA
JOIN RES_HAB ON RES_HAB_DATA.reshab_id = RES_HAB.reshab_id
JOIN STAFF ON RES_HAB_DATA.staff_id = STAFF.stf_id
WHERE RES_HAB.serv_id = 30
AND RES_HAB_DATA.reshabdata_data_date >= '1/1/2015'
GROUP BY STAFF.stf_last_name,
STAFF.stf_first_name,
DATENAME(month,RES_HAB_DATA.reshabdata_data_date),
MONTH(RES_HAB_DATA.reshabdata_data_date)
)
SELECT
myTableName.Name
,mytableMonth.Month_Name
,ISNULL(mytable.ServiceDeliveryTime,0) as ServiceDeliveryTime
,mytableMonth.id
FROM
(SELECT DISTINCT Name from mytable) mytableName
CROSS JOIN (SELECT DISTINCT Month_Name,id FROM MyMonths) mytableMonth
LEFT JOIN mytable ON mytableName.Name = mytable.Name AND mytableMonth.Month_Name = mytable.Month AND mytable.MonthNumber = mytable.MonthNumber
ORDER BY mytableName.Name, mytableMonth.id
MyMonths table is already created table with id as MonthNumber and Month_Name as Month.
Cheers!

Outer join - oracle

I have 2 tabels
Current Ecpense table
-----------------------
Month-----Type-------spent
Feb 12 Shopping 100
Feb 12 Food 200
Jan 12 Shopping 456
Jan 12 Food 452
Jan 12 Fuel 120
Jan 12 Rent 900
Previous Expense
-----------------------
Type------ spent
Shopping 100
Food 100
Fuel 100
Rent 100
Now i want to join these two tables, the expected result is;
Month-----Type-------spent-----Previous Spent
Feb 12 Shopping 100 100
Feb 12 Food 200 100
Feb 12 Fuel 0 100
Feb 12 Rent 0 100
Jan 12 Shopping 456 100
Jan 12 Food 452 100
Jan 12 Fuel 120 100
Jan 12 Rent 900 100
Is there a way to do this?
Try:
select m.month,
p.type,
coalesce(c.spent,0) spent,
p.spent previous_spent
from (select distinct month from current_expense) m
cross join previous_expense p
left join current_expense c
on m.month = c.month and p.type = c.type
Common Oracle syntax
Select a.*, b.spent 'previous Sent'
from current_expense a, previous_expense b
where a.type = b.type
or the same thing in standard
Select a.*, b.spent 'previous Sent'
from current_expense as a
inner join previous_expense as b on a.type = b.type
I tend to write in SQL92 (more SQLness and less Oracle-ness)
Here is my answer:
select
z.month as month ,
z.type as type ,
nvl(c.spent,0) as spent ,
nvl(p.spent,0) as previous_spent
from
(select
x.month as month ,
y.type as type
from
(select distinct month
from current_expense) x
cross join
(select distinct type
from current_expense) y) z
left outer join current_expense c
on z.month = c.month and
z.type = z.type
left outer join previous_expense p
on z.type = p.type;