SQL join table to itself to get data for previous year
up vote
2
down vote
favorite
SQL. How can I join table to itself to get desired results just as shown in the table below. The logic is that I want to have Units for the same product and corresponding month of previous year.
The simple left join on source table to itself on key a.[year]=b.[year]+1
(and of course month to month and product to product) would cause the loss of the data where we had values in the previous year and do not have now.
sql sql-server tsql join self-join
add a comment |
up vote
2
down vote
favorite
SQL. How can I join table to itself to get desired results just as shown in the table below. The logic is that I want to have Units for the same product and corresponding month of previous year.
The simple left join on source table to itself on key a.[year]=b.[year]+1
(and of course month to month and product to product) would cause the loss of the data where we had values in the previous year and do not have now.
sql sql-server tsql join self-join
Why does 2017, 4, 1 has 2933?
– Salman A
Nov 19 at 11:23
@SalmanA Corrected. Thx.
– Przemyslaw Remin
Nov 19 at 11:34
Do you really need the rows for 2017? You could simply have an output with 5 rows likeproduct, month, 2018_units, 2017_units
– Salman A
Nov 19 at 15:07
@SalmanA yes, I do need the data in that structure for every year - there are more years then just 2018, and 2017.
– Przemyslaw Remin
Nov 20 at 9:16
add a comment |
up vote
2
down vote
favorite
up vote
2
down vote
favorite
SQL. How can I join table to itself to get desired results just as shown in the table below. The logic is that I want to have Units for the same product and corresponding month of previous year.
The simple left join on source table to itself on key a.[year]=b.[year]+1
(and of course month to month and product to product) would cause the loss of the data where we had values in the previous year and do not have now.
sql sql-server tsql join self-join
SQL. How can I join table to itself to get desired results just as shown in the table below. The logic is that I want to have Units for the same product and corresponding month of previous year.
The simple left join on source table to itself on key a.[year]=b.[year]+1
(and of course month to month and product to product) would cause the loss of the data where we had values in the previous year and do not have now.
sql sql-server tsql join self-join
sql sql-server tsql join self-join
edited Nov 19 at 13:33
asked Nov 19 at 10:30
Przemyslaw Remin
87332162
87332162
Why does 2017, 4, 1 has 2933?
– Salman A
Nov 19 at 11:23
@SalmanA Corrected. Thx.
– Przemyslaw Remin
Nov 19 at 11:34
Do you really need the rows for 2017? You could simply have an output with 5 rows likeproduct, month, 2018_units, 2017_units
– Salman A
Nov 19 at 15:07
@SalmanA yes, I do need the data in that structure for every year - there are more years then just 2018, and 2017.
– Przemyslaw Remin
Nov 20 at 9:16
add a comment |
Why does 2017, 4, 1 has 2933?
– Salman A
Nov 19 at 11:23
@SalmanA Corrected. Thx.
– Przemyslaw Remin
Nov 19 at 11:34
Do you really need the rows for 2017? You could simply have an output with 5 rows likeproduct, month, 2018_units, 2017_units
– Salman A
Nov 19 at 15:07
@SalmanA yes, I do need the data in that structure for every year - there are more years then just 2018, and 2017.
– Przemyslaw Remin
Nov 20 at 9:16
Why does 2017, 4, 1 has 2933?
– Salman A
Nov 19 at 11:23
Why does 2017, 4, 1 has 2933?
– Salman A
Nov 19 at 11:23
@SalmanA Corrected. Thx.
– Przemyslaw Remin
Nov 19 at 11:34
@SalmanA Corrected. Thx.
– Przemyslaw Remin
Nov 19 at 11:34
Do you really need the rows for 2017? You could simply have an output with 5 rows like
product, month, 2018_units, 2017_units
– Salman A
Nov 19 at 15:07
Do you really need the rows for 2017? You could simply have an output with 5 rows like
product, month, 2018_units, 2017_units
– Salman A
Nov 19 at 15:07
@SalmanA yes, I do need the data in that structure for every year - there are more years then just 2018, and 2017.
– Przemyslaw Remin
Nov 20 at 9:16
@SalmanA yes, I do need the data in that structure for every year - there are more years then just 2018, and 2017.
– Przemyslaw Remin
Nov 20 at 9:16
add a comment |
5 Answers
5
active
oldest
votes
up vote
3
down vote
accepted
A full join should be sufficient
select distinct
coalesce(a.year, b.year+1) as year
, coalesce(a.month, b.month) as month
, coalesce(a.product, b.product) as product
, a.units as units
, b.units as units_prev
from yourtable a
full join yourtable b on a.[year] = b.[year]+1 and a.[month] = b.[month] and a.product = b.product
Your expected results though are slightly off from the description 2018, month 2, product 2 does not exist with a prior value of 2933.
DB Fiddle : https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=d01dc5bd626854b083be0864f2d5b0e4
Result :
year month product units units_prev
2017 1 1 1721
2017 2 1 4915
2017 4 2 2933
2017 5 1 5230
2018 1 1 1721
2018 1 2 7672
2018 2 1 5216 4915
2018 3 1 8911
2018 4 2 2933
2018 5 1 5230
2019 1 2 7672
2019 2 1 5216
2019 3 1 8911
If you need to filter out futures like that, then you can add an additional where predicate, something like :
where coalesce(a.year, b.year+1) <= year(getdate())
Surprising! You do not join on [month]? Is that so or you forgot about it?
– Przemyslaw Remin
Nov 19 at 10:38
Forgot during edits :)
– Andrew
Nov 19 at 10:38
ok, what was catching me out was the coalesce of the year, it should of had b,year+1 - technically, in 2019, month 3 for product 1 there is a units_prev of 8911 - but you don't give it in your expected results
– Andrew
Nov 19 at 11:07
yeap, you are right. I corrected it.
– Przemyslaw Remin
Nov 19 at 11:15
add a comment |
up vote
2
down vote
year month
Use cross join
to generate the rows, left join
to bring in the data and then lag()
to get the "previous" value:
select y.year, m.month, p.product, t.units,
lag(t.units) over (partition by p.product, m.month order by y.year) as prev_units
from (select distinct year from t) y cross join
(select distinct month from t) m cross join
(select distinct product from t) p left join
t
on t.year = y.year and t.month = m.month and t.product = p.producct;
I like its simplicity but it should be improved. It produces three nulls in the product column on my data. In my expected results there should be no rows for the months of the product 2 where there is no sales in neither 2017 and 2018. I think your Cartesian would produce rows for that. Can you please put aliases in the second line of your code?
– Przemyslaw Remin
Nov 19 at 12:10
@PrzemyslawRemin . . . Yes, I see that you also want theproduct
to be cross joined as well. The query is fixed.
– Gordon Linoff
Nov 19 at 13:13
From the expected results shown, I gather they are rather looking for(select distinct year from t) y cross join (select distinct month, product from t) mp
.
– Thorsten Kettner
Nov 19 at 13:26
@GordonLinoff Almost done. Is additionalwhere coalesce(units,prev_units) is not null
the best way to get your query to desired results? I do not want the rows where there are no sales for this and previous year. The are some products that have sales only in specific months of the year. Since source table is couple of milion rows I do not want the weeds at once if possible.
– Przemyslaw Remin
Nov 19 at 13:39
@PrzemyslawRemin . . . Yes, I think that will do it.
– Gordon Linoff
Nov 19 at 14:00
|
show 2 more comments
up vote
1
down vote
I would go with LAG
, and a calendar table.
SELECT C.[Year],
C.[Month],
YPT.product,
YST.units,
YST.LAG(YST.units) OVER (PARTITION BY YTP.[product],C.[month] ORDER BY C.[year]) AS UnitsPrev
FROM CalendarTable C
CROSS JOIN YourProductTable YPT
LEFT JOIN YourSourceTable YST ON C.[Year] YST.[Year]
AND C.[Month] = YST.[Month]
AND YPT.Product = YST.Product
WHERE C.[day] = 1
AND C.[date] BETWEEN {SomeStartDate} AND {SomeEndDate];
This guessing a little on your design (it assumes you have a product table).
1
I do not have [Day].
– Przemyslaw Remin
Nov 19 at 10:57
@PrzemyslawReminC.[Day] = 1
notYST.[Day]
– Larnu
Nov 19 at 11:01
If, however, you mean that your Calendar Table doesn't have aday
column, @PrzemyslawRemin my question is why? That's a very important column to have in your calendar table, and I would suggest you make changes to it to include it.
– Larnu
Nov 19 at 11:18
I do not have [Day] column. Simply because I work on monthly data. My [Units] column is aggregated to full months.
– Przemyslaw Remin
Nov 19 at 12:14
So, @PrzemyslawRemin ,in your calendar table, are you only storing the first day of the month? If so, then just removeAND C.[Day] = 1
from theON
clause. If not, then why isn'tday
in your calendar table if your have a calendar table with every day of the month in it. However, based on your above statement, why does your calendar table NOT have every calendar day in it? That's why it's called a calendar table.
– Larnu
Nov 19 at 12:17
|
show 2 more comments
up vote
1
down vote
You could generate all possible combinations for year, month and product in your data using CROSS JOIN
. A simple LEFT JOIN
will give you the value or NULL if data for a specific combination exists.
DECLARE @t TABLE (year int, month int, product int, unit int);
INSERT INTO @t VALUES
(2017, 1, 1, 1721),
(2017, 2, 1, 4915),
(2017, 5, 1, 5230),
(2018, 2, 1, 5216),
(2018, 3, 1, 8911),
(2017, 4, 2, 2933),
(2018, 1, 2, 7672);
SELECT ally.year, allm.month, allp.product, curr.units, prev.units AS units_prev
FROM (SELECT DISTINCT year FROM @t) AS ally
CROSS JOIN (SELECT DISTINCT product FROM @t) AS allp
CROSS JOIN (SELECT DISTINCT month FROM @t) AS allm
LEFT JOIN @t AS curr ON curr.year = ally.year AND curr.product = allp.product AND curr.month = allm.month
LEFT JOIN @t AS prev ON prev.year = ally.year - 1 AND prev.product = allp.product AND prev.month = allm.month
Result:
| year | month | product | units | units_prev |
|------|-------|---------|-------|------------|
| 2017 | 1 | 1 | 1721 | NULL |
| 2017 | 2 | 1 | 4915 | NULL |
| 2017 | 3 | 1 | NULL | NULL |
| 2017 | 4 | 1 | NULL | NULL |
| 2017 | 5 | 1 | 5230 | NULL |
| 2017 | 1 | 2 | NULL | NULL |
| 2017 | 2 | 2 | NULL | NULL |
| 2017 | 3 | 2 | NULL | NULL |
| 2017 | 4 | 2 | 2933 | NULL |
| 2017 | 5 | 2 | NULL | NULL |
| 2018 | 1 | 1 | NULL | 1721 |
| 2018 | 2 | 1 | 5216 | 4915 |
| 2018 | 3 | 1 | 8911 | NULL |
| 2018 | 4 | 1 | NULL | NULL |
| 2018 | 5 | 1 | NULL | 5230 |
| 2018 | 1 | 2 | 7672 | NULL |
| 2018 | 2 | 2 | NULL | NULL |
| 2018 | 3 | 2 | NULL | NULL |
| 2018 | 4 | 2 | NULL | 2933 |
| 2018 | 5 | 2 | NULL | NULL |
Hi Salman, (1) you do not have [year] column in your final result. (2) I do not understand the clauseWHERE year = 2018 OR year = 2018 - 1
(it means select *). (3) [Year] column values are exemplary in source table. There are [Year] as 2016, 2015.... Please do not refer to values. (4) Can you please explain how your Cartesian product produces just 10 rows instead of 20 (see. answer of Gordon Linoff)?
– Przemyslaw Remin
Nov 20 at 9:09
Sorry I thought we're talking about two years only. It could be made to work with n years easily butLAG
is better than twoLEFT JOINS
.
– Salman A
Nov 20 at 9:20
Why do you thinkLAG
is better then twoLEFT JOINS
? The performance seems to be in favor of self-join: dba.stackexchange.com/questions/158374/…
– Przemyslaw Remin
Nov 20 at 9:41
It depends. But I've revised my answer nevertheless. It is now exactly the same as the other except that it uses LEFT JOIN to find previous row. Plan looks slightly better.
– Salman A
Nov 20 at 9:44
add a comment |
up vote
0
down vote
If you want the rows where nothing was sold in both 2017 and 2018 as in your desired results for March 2017 as well you need to generate out the month, year and join in the product to get the null values.
This query does it for the month and year, hopefully you should be able to add the Product as well if required
DECLARE @startMonth INT=1
DECLARE @endMonth INT=12
DECLARE @startYear INT=2017
DECLARE @endYear INT=2018
;
WITH months AS (
SELECT @startMonth AS m
UNION ALL
SELECT m+1 FROM months WHERE m+1<=@endMonth
),
years AS (
SELECT @startYear AS y
UNION ALL
SELECT y+1 FROM years WHERE y+1<=@endYear
),
monthYears AS (
SELECT m, y
FROM months, years
)
SELECT thisYear.[Year], thisYear.[Month], thisYear.[Product], thisYear.[Units], prevYear.[Units] as units_prev
FROM
(SELECT [Product], my.y as [Year], my.m as [Month], [Units]
FROM monthYears my
LEFT JOIN sales on my.m = [Month] and my.y = [Year]) as thisYear
LEFT OUTER JOIN
(SELECT [Product], my.y as [Year], my.m as [Month], my.y + 1 as NextYear, [Units]
FROM monthYears my
LEFT JOIN sales on my.m = [Month] and my.y = [Year]) as prevYear
on thisYear.Product = prevYear.Product
and (thisYEAR.[Year]) = prevYear.[NextYear]
and thisYEAR.[Month] = prevYear.[Month]
ORDER BY thisYear.[Year], thisYear.[Month], thisYear.[Product]
option (maxrecursion 12);
add a comment |
5 Answers
5
active
oldest
votes
5 Answers
5
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
3
down vote
accepted
A full join should be sufficient
select distinct
coalesce(a.year, b.year+1) as year
, coalesce(a.month, b.month) as month
, coalesce(a.product, b.product) as product
, a.units as units
, b.units as units_prev
from yourtable a
full join yourtable b on a.[year] = b.[year]+1 and a.[month] = b.[month] and a.product = b.product
Your expected results though are slightly off from the description 2018, month 2, product 2 does not exist with a prior value of 2933.
DB Fiddle : https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=d01dc5bd626854b083be0864f2d5b0e4
Result :
year month product units units_prev
2017 1 1 1721
2017 2 1 4915
2017 4 2 2933
2017 5 1 5230
2018 1 1 1721
2018 1 2 7672
2018 2 1 5216 4915
2018 3 1 8911
2018 4 2 2933
2018 5 1 5230
2019 1 2 7672
2019 2 1 5216
2019 3 1 8911
If you need to filter out futures like that, then you can add an additional where predicate, something like :
where coalesce(a.year, b.year+1) <= year(getdate())
Surprising! You do not join on [month]? Is that so or you forgot about it?
– Przemyslaw Remin
Nov 19 at 10:38
Forgot during edits :)
– Andrew
Nov 19 at 10:38
ok, what was catching me out was the coalesce of the year, it should of had b,year+1 - technically, in 2019, month 3 for product 1 there is a units_prev of 8911 - but you don't give it in your expected results
– Andrew
Nov 19 at 11:07
yeap, you are right. I corrected it.
– Przemyslaw Remin
Nov 19 at 11:15
add a comment |
up vote
3
down vote
accepted
A full join should be sufficient
select distinct
coalesce(a.year, b.year+1) as year
, coalesce(a.month, b.month) as month
, coalesce(a.product, b.product) as product
, a.units as units
, b.units as units_prev
from yourtable a
full join yourtable b on a.[year] = b.[year]+1 and a.[month] = b.[month] and a.product = b.product
Your expected results though are slightly off from the description 2018, month 2, product 2 does not exist with a prior value of 2933.
DB Fiddle : https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=d01dc5bd626854b083be0864f2d5b0e4
Result :
year month product units units_prev
2017 1 1 1721
2017 2 1 4915
2017 4 2 2933
2017 5 1 5230
2018 1 1 1721
2018 1 2 7672
2018 2 1 5216 4915
2018 3 1 8911
2018 4 2 2933
2018 5 1 5230
2019 1 2 7672
2019 2 1 5216
2019 3 1 8911
If you need to filter out futures like that, then you can add an additional where predicate, something like :
where coalesce(a.year, b.year+1) <= year(getdate())
Surprising! You do not join on [month]? Is that so or you forgot about it?
– Przemyslaw Remin
Nov 19 at 10:38
Forgot during edits :)
– Andrew
Nov 19 at 10:38
ok, what was catching me out was the coalesce of the year, it should of had b,year+1 - technically, in 2019, month 3 for product 1 there is a units_prev of 8911 - but you don't give it in your expected results
– Andrew
Nov 19 at 11:07
yeap, you are right. I corrected it.
– Przemyslaw Remin
Nov 19 at 11:15
add a comment |
up vote
3
down vote
accepted
up vote
3
down vote
accepted
A full join should be sufficient
select distinct
coalesce(a.year, b.year+1) as year
, coalesce(a.month, b.month) as month
, coalesce(a.product, b.product) as product
, a.units as units
, b.units as units_prev
from yourtable a
full join yourtable b on a.[year] = b.[year]+1 and a.[month] = b.[month] and a.product = b.product
Your expected results though are slightly off from the description 2018, month 2, product 2 does not exist with a prior value of 2933.
DB Fiddle : https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=d01dc5bd626854b083be0864f2d5b0e4
Result :
year month product units units_prev
2017 1 1 1721
2017 2 1 4915
2017 4 2 2933
2017 5 1 5230
2018 1 1 1721
2018 1 2 7672
2018 2 1 5216 4915
2018 3 1 8911
2018 4 2 2933
2018 5 1 5230
2019 1 2 7672
2019 2 1 5216
2019 3 1 8911
If you need to filter out futures like that, then you can add an additional where predicate, something like :
where coalesce(a.year, b.year+1) <= year(getdate())
A full join should be sufficient
select distinct
coalesce(a.year, b.year+1) as year
, coalesce(a.month, b.month) as month
, coalesce(a.product, b.product) as product
, a.units as units
, b.units as units_prev
from yourtable a
full join yourtable b on a.[year] = b.[year]+1 and a.[month] = b.[month] and a.product = b.product
Your expected results though are slightly off from the description 2018, month 2, product 2 does not exist with a prior value of 2933.
DB Fiddle : https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=d01dc5bd626854b083be0864f2d5b0e4
Result :
year month product units units_prev
2017 1 1 1721
2017 2 1 4915
2017 4 2 2933
2017 5 1 5230
2018 1 1 1721
2018 1 2 7672
2018 2 1 5216 4915
2018 3 1 8911
2018 4 2 2933
2018 5 1 5230
2019 1 2 7672
2019 2 1 5216
2019 3 1 8911
If you need to filter out futures like that, then you can add an additional where predicate, something like :
where coalesce(a.year, b.year+1) <= year(getdate())
edited Nov 19 at 11:13
answered Nov 19 at 10:34
Andrew
21.8k35279
21.8k35279
Surprising! You do not join on [month]? Is that so or you forgot about it?
– Przemyslaw Remin
Nov 19 at 10:38
Forgot during edits :)
– Andrew
Nov 19 at 10:38
ok, what was catching me out was the coalesce of the year, it should of had b,year+1 - technically, in 2019, month 3 for product 1 there is a units_prev of 8911 - but you don't give it in your expected results
– Andrew
Nov 19 at 11:07
yeap, you are right. I corrected it.
– Przemyslaw Remin
Nov 19 at 11:15
add a comment |
Surprising! You do not join on [month]? Is that so or you forgot about it?
– Przemyslaw Remin
Nov 19 at 10:38
Forgot during edits :)
– Andrew
Nov 19 at 10:38
ok, what was catching me out was the coalesce of the year, it should of had b,year+1 - technically, in 2019, month 3 for product 1 there is a units_prev of 8911 - but you don't give it in your expected results
– Andrew
Nov 19 at 11:07
yeap, you are right. I corrected it.
– Przemyslaw Remin
Nov 19 at 11:15
Surprising! You do not join on [month]? Is that so or you forgot about it?
– Przemyslaw Remin
Nov 19 at 10:38
Surprising! You do not join on [month]? Is that so or you forgot about it?
– Przemyslaw Remin
Nov 19 at 10:38
Forgot during edits :)
– Andrew
Nov 19 at 10:38
Forgot during edits :)
– Andrew
Nov 19 at 10:38
ok, what was catching me out was the coalesce of the year, it should of had b,year+1 - technically, in 2019, month 3 for product 1 there is a units_prev of 8911 - but you don't give it in your expected results
– Andrew
Nov 19 at 11:07
ok, what was catching me out was the coalesce of the year, it should of had b,year+1 - technically, in 2019, month 3 for product 1 there is a units_prev of 8911 - but you don't give it in your expected results
– Andrew
Nov 19 at 11:07
yeap, you are right. I corrected it.
– Przemyslaw Remin
Nov 19 at 11:15
yeap, you are right. I corrected it.
– Przemyslaw Remin
Nov 19 at 11:15
add a comment |
up vote
2
down vote
year month
Use cross join
to generate the rows, left join
to bring in the data and then lag()
to get the "previous" value:
select y.year, m.month, p.product, t.units,
lag(t.units) over (partition by p.product, m.month order by y.year) as prev_units
from (select distinct year from t) y cross join
(select distinct month from t) m cross join
(select distinct product from t) p left join
t
on t.year = y.year and t.month = m.month and t.product = p.producct;
I like its simplicity but it should be improved. It produces three nulls in the product column on my data. In my expected results there should be no rows for the months of the product 2 where there is no sales in neither 2017 and 2018. I think your Cartesian would produce rows for that. Can you please put aliases in the second line of your code?
– Przemyslaw Remin
Nov 19 at 12:10
@PrzemyslawRemin . . . Yes, I see that you also want theproduct
to be cross joined as well. The query is fixed.
– Gordon Linoff
Nov 19 at 13:13
From the expected results shown, I gather they are rather looking for(select distinct year from t) y cross join (select distinct month, product from t) mp
.
– Thorsten Kettner
Nov 19 at 13:26
@GordonLinoff Almost done. Is additionalwhere coalesce(units,prev_units) is not null
the best way to get your query to desired results? I do not want the rows where there are no sales for this and previous year. The are some products that have sales only in specific months of the year. Since source table is couple of milion rows I do not want the weeds at once if possible.
– Przemyslaw Remin
Nov 19 at 13:39
@PrzemyslawRemin . . . Yes, I think that will do it.
– Gordon Linoff
Nov 19 at 14:00
|
show 2 more comments
up vote
2
down vote
year month
Use cross join
to generate the rows, left join
to bring in the data and then lag()
to get the "previous" value:
select y.year, m.month, p.product, t.units,
lag(t.units) over (partition by p.product, m.month order by y.year) as prev_units
from (select distinct year from t) y cross join
(select distinct month from t) m cross join
(select distinct product from t) p left join
t
on t.year = y.year and t.month = m.month and t.product = p.producct;
I like its simplicity but it should be improved. It produces three nulls in the product column on my data. In my expected results there should be no rows for the months of the product 2 where there is no sales in neither 2017 and 2018. I think your Cartesian would produce rows for that. Can you please put aliases in the second line of your code?
– Przemyslaw Remin
Nov 19 at 12:10
@PrzemyslawRemin . . . Yes, I see that you also want theproduct
to be cross joined as well. The query is fixed.
– Gordon Linoff
Nov 19 at 13:13
From the expected results shown, I gather they are rather looking for(select distinct year from t) y cross join (select distinct month, product from t) mp
.
– Thorsten Kettner
Nov 19 at 13:26
@GordonLinoff Almost done. Is additionalwhere coalesce(units,prev_units) is not null
the best way to get your query to desired results? I do not want the rows where there are no sales for this and previous year. The are some products that have sales only in specific months of the year. Since source table is couple of milion rows I do not want the weeds at once if possible.
– Przemyslaw Remin
Nov 19 at 13:39
@PrzemyslawRemin . . . Yes, I think that will do it.
– Gordon Linoff
Nov 19 at 14:00
|
show 2 more comments
up vote
2
down vote
up vote
2
down vote
year month
Use cross join
to generate the rows, left join
to bring in the data and then lag()
to get the "previous" value:
select y.year, m.month, p.product, t.units,
lag(t.units) over (partition by p.product, m.month order by y.year) as prev_units
from (select distinct year from t) y cross join
(select distinct month from t) m cross join
(select distinct product from t) p left join
t
on t.year = y.year and t.month = m.month and t.product = p.producct;
year month
Use cross join
to generate the rows, left join
to bring in the data and then lag()
to get the "previous" value:
select y.year, m.month, p.product, t.units,
lag(t.units) over (partition by p.product, m.month order by y.year) as prev_units
from (select distinct year from t) y cross join
(select distinct month from t) m cross join
(select distinct product from t) p left join
t
on t.year = y.year and t.month = m.month and t.product = p.producct;
edited Nov 19 at 13:12
answered Nov 19 at 11:51
Gordon Linoff
749k34285391
749k34285391
I like its simplicity but it should be improved. It produces three nulls in the product column on my data. In my expected results there should be no rows for the months of the product 2 where there is no sales in neither 2017 and 2018. I think your Cartesian would produce rows for that. Can you please put aliases in the second line of your code?
– Przemyslaw Remin
Nov 19 at 12:10
@PrzemyslawRemin . . . Yes, I see that you also want theproduct
to be cross joined as well. The query is fixed.
– Gordon Linoff
Nov 19 at 13:13
From the expected results shown, I gather they are rather looking for(select distinct year from t) y cross join (select distinct month, product from t) mp
.
– Thorsten Kettner
Nov 19 at 13:26
@GordonLinoff Almost done. Is additionalwhere coalesce(units,prev_units) is not null
the best way to get your query to desired results? I do not want the rows where there are no sales for this and previous year. The are some products that have sales only in specific months of the year. Since source table is couple of milion rows I do not want the weeds at once if possible.
– Przemyslaw Remin
Nov 19 at 13:39
@PrzemyslawRemin . . . Yes, I think that will do it.
– Gordon Linoff
Nov 19 at 14:00
|
show 2 more comments
I like its simplicity but it should be improved. It produces three nulls in the product column on my data. In my expected results there should be no rows for the months of the product 2 where there is no sales in neither 2017 and 2018. I think your Cartesian would produce rows for that. Can you please put aliases in the second line of your code?
– Przemyslaw Remin
Nov 19 at 12:10
@PrzemyslawRemin . . . Yes, I see that you also want theproduct
to be cross joined as well. The query is fixed.
– Gordon Linoff
Nov 19 at 13:13
From the expected results shown, I gather they are rather looking for(select distinct year from t) y cross join (select distinct month, product from t) mp
.
– Thorsten Kettner
Nov 19 at 13:26
@GordonLinoff Almost done. Is additionalwhere coalesce(units,prev_units) is not null
the best way to get your query to desired results? I do not want the rows where there are no sales for this and previous year. The are some products that have sales only in specific months of the year. Since source table is couple of milion rows I do not want the weeds at once if possible.
– Przemyslaw Remin
Nov 19 at 13:39
@PrzemyslawRemin . . . Yes, I think that will do it.
– Gordon Linoff
Nov 19 at 14:00
I like its simplicity but it should be improved. It produces three nulls in the product column on my data. In my expected results there should be no rows for the months of the product 2 where there is no sales in neither 2017 and 2018. I think your Cartesian would produce rows for that. Can you please put aliases in the second line of your code?
– Przemyslaw Remin
Nov 19 at 12:10
I like its simplicity but it should be improved. It produces three nulls in the product column on my data. In my expected results there should be no rows for the months of the product 2 where there is no sales in neither 2017 and 2018. I think your Cartesian would produce rows for that. Can you please put aliases in the second line of your code?
– Przemyslaw Remin
Nov 19 at 12:10
@PrzemyslawRemin . . . Yes, I see that you also want the
product
to be cross joined as well. The query is fixed.– Gordon Linoff
Nov 19 at 13:13
@PrzemyslawRemin . . . Yes, I see that you also want the
product
to be cross joined as well. The query is fixed.– Gordon Linoff
Nov 19 at 13:13
From the expected results shown, I gather they are rather looking for
(select distinct year from t) y cross join (select distinct month, product from t) mp
.– Thorsten Kettner
Nov 19 at 13:26
From the expected results shown, I gather they are rather looking for
(select distinct year from t) y cross join (select distinct month, product from t) mp
.– Thorsten Kettner
Nov 19 at 13:26
@GordonLinoff Almost done. Is additional
where coalesce(units,prev_units) is not null
the best way to get your query to desired results? I do not want the rows where there are no sales for this and previous year. The are some products that have sales only in specific months of the year. Since source table is couple of milion rows I do not want the weeds at once if possible.– Przemyslaw Remin
Nov 19 at 13:39
@GordonLinoff Almost done. Is additional
where coalesce(units,prev_units) is not null
the best way to get your query to desired results? I do not want the rows where there are no sales for this and previous year. The are some products that have sales only in specific months of the year. Since source table is couple of milion rows I do not want the weeds at once if possible.– Przemyslaw Remin
Nov 19 at 13:39
@PrzemyslawRemin . . . Yes, I think that will do it.
– Gordon Linoff
Nov 19 at 14:00
@PrzemyslawRemin . . . Yes, I think that will do it.
– Gordon Linoff
Nov 19 at 14:00
|
show 2 more comments
up vote
1
down vote
I would go with LAG
, and a calendar table.
SELECT C.[Year],
C.[Month],
YPT.product,
YST.units,
YST.LAG(YST.units) OVER (PARTITION BY YTP.[product],C.[month] ORDER BY C.[year]) AS UnitsPrev
FROM CalendarTable C
CROSS JOIN YourProductTable YPT
LEFT JOIN YourSourceTable YST ON C.[Year] YST.[Year]
AND C.[Month] = YST.[Month]
AND YPT.Product = YST.Product
WHERE C.[day] = 1
AND C.[date] BETWEEN {SomeStartDate} AND {SomeEndDate];
This guessing a little on your design (it assumes you have a product table).
1
I do not have [Day].
– Przemyslaw Remin
Nov 19 at 10:57
@PrzemyslawReminC.[Day] = 1
notYST.[Day]
– Larnu
Nov 19 at 11:01
If, however, you mean that your Calendar Table doesn't have aday
column, @PrzemyslawRemin my question is why? That's a very important column to have in your calendar table, and I would suggest you make changes to it to include it.
– Larnu
Nov 19 at 11:18
I do not have [Day] column. Simply because I work on monthly data. My [Units] column is aggregated to full months.
– Przemyslaw Remin
Nov 19 at 12:14
So, @PrzemyslawRemin ,in your calendar table, are you only storing the first day of the month? If so, then just removeAND C.[Day] = 1
from theON
clause. If not, then why isn'tday
in your calendar table if your have a calendar table with every day of the month in it. However, based on your above statement, why does your calendar table NOT have every calendar day in it? That's why it's called a calendar table.
– Larnu
Nov 19 at 12:17
|
show 2 more comments
up vote
1
down vote
I would go with LAG
, and a calendar table.
SELECT C.[Year],
C.[Month],
YPT.product,
YST.units,
YST.LAG(YST.units) OVER (PARTITION BY YTP.[product],C.[month] ORDER BY C.[year]) AS UnitsPrev
FROM CalendarTable C
CROSS JOIN YourProductTable YPT
LEFT JOIN YourSourceTable YST ON C.[Year] YST.[Year]
AND C.[Month] = YST.[Month]
AND YPT.Product = YST.Product
WHERE C.[day] = 1
AND C.[date] BETWEEN {SomeStartDate} AND {SomeEndDate];
This guessing a little on your design (it assumes you have a product table).
1
I do not have [Day].
– Przemyslaw Remin
Nov 19 at 10:57
@PrzemyslawReminC.[Day] = 1
notYST.[Day]
– Larnu
Nov 19 at 11:01
If, however, you mean that your Calendar Table doesn't have aday
column, @PrzemyslawRemin my question is why? That's a very important column to have in your calendar table, and I would suggest you make changes to it to include it.
– Larnu
Nov 19 at 11:18
I do not have [Day] column. Simply because I work on monthly data. My [Units] column is aggregated to full months.
– Przemyslaw Remin
Nov 19 at 12:14
So, @PrzemyslawRemin ,in your calendar table, are you only storing the first day of the month? If so, then just removeAND C.[Day] = 1
from theON
clause. If not, then why isn'tday
in your calendar table if your have a calendar table with every day of the month in it. However, based on your above statement, why does your calendar table NOT have every calendar day in it? That's why it's called a calendar table.
– Larnu
Nov 19 at 12:17
|
show 2 more comments
up vote
1
down vote
up vote
1
down vote
I would go with LAG
, and a calendar table.
SELECT C.[Year],
C.[Month],
YPT.product,
YST.units,
YST.LAG(YST.units) OVER (PARTITION BY YTP.[product],C.[month] ORDER BY C.[year]) AS UnitsPrev
FROM CalendarTable C
CROSS JOIN YourProductTable YPT
LEFT JOIN YourSourceTable YST ON C.[Year] YST.[Year]
AND C.[Month] = YST.[Month]
AND YPT.Product = YST.Product
WHERE C.[day] = 1
AND C.[date] BETWEEN {SomeStartDate} AND {SomeEndDate];
This guessing a little on your design (it assumes you have a product table).
I would go with LAG
, and a calendar table.
SELECT C.[Year],
C.[Month],
YPT.product,
YST.units,
YST.LAG(YST.units) OVER (PARTITION BY YTP.[product],C.[month] ORDER BY C.[year]) AS UnitsPrev
FROM CalendarTable C
CROSS JOIN YourProductTable YPT
LEFT JOIN YourSourceTable YST ON C.[Year] YST.[Year]
AND C.[Month] = YST.[Month]
AND YPT.Product = YST.Product
WHERE C.[day] = 1
AND C.[date] BETWEEN {SomeStartDate} AND {SomeEndDate];
This guessing a little on your design (it assumes you have a product table).
edited Nov 19 at 12:27
answered Nov 19 at 10:43
Larnu
14.1k31530
14.1k31530
1
I do not have [Day].
– Przemyslaw Remin
Nov 19 at 10:57
@PrzemyslawReminC.[Day] = 1
notYST.[Day]
– Larnu
Nov 19 at 11:01
If, however, you mean that your Calendar Table doesn't have aday
column, @PrzemyslawRemin my question is why? That's a very important column to have in your calendar table, and I would suggest you make changes to it to include it.
– Larnu
Nov 19 at 11:18
I do not have [Day] column. Simply because I work on monthly data. My [Units] column is aggregated to full months.
– Przemyslaw Remin
Nov 19 at 12:14
So, @PrzemyslawRemin ,in your calendar table, are you only storing the first day of the month? If so, then just removeAND C.[Day] = 1
from theON
clause. If not, then why isn'tday
in your calendar table if your have a calendar table with every day of the month in it. However, based on your above statement, why does your calendar table NOT have every calendar day in it? That's why it's called a calendar table.
– Larnu
Nov 19 at 12:17
|
show 2 more comments
1
I do not have [Day].
– Przemyslaw Remin
Nov 19 at 10:57
@PrzemyslawReminC.[Day] = 1
notYST.[Day]
– Larnu
Nov 19 at 11:01
If, however, you mean that your Calendar Table doesn't have aday
column, @PrzemyslawRemin my question is why? That's a very important column to have in your calendar table, and I would suggest you make changes to it to include it.
– Larnu
Nov 19 at 11:18
I do not have [Day] column. Simply because I work on monthly data. My [Units] column is aggregated to full months.
– Przemyslaw Remin
Nov 19 at 12:14
So, @PrzemyslawRemin ,in your calendar table, are you only storing the first day of the month? If so, then just removeAND C.[Day] = 1
from theON
clause. If not, then why isn'tday
in your calendar table if your have a calendar table with every day of the month in it. However, based on your above statement, why does your calendar table NOT have every calendar day in it? That's why it's called a calendar table.
– Larnu
Nov 19 at 12:17
1
1
I do not have [Day].
– Przemyslaw Remin
Nov 19 at 10:57
I do not have [Day].
– Przemyslaw Remin
Nov 19 at 10:57
@PrzemyslawRemin
C.[Day] = 1
not YST.[Day]
– Larnu
Nov 19 at 11:01
@PrzemyslawRemin
C.[Day] = 1
not YST.[Day]
– Larnu
Nov 19 at 11:01
If, however, you mean that your Calendar Table doesn't have a
day
column, @PrzemyslawRemin my question is why? That's a very important column to have in your calendar table, and I would suggest you make changes to it to include it.– Larnu
Nov 19 at 11:18
If, however, you mean that your Calendar Table doesn't have a
day
column, @PrzemyslawRemin my question is why? That's a very important column to have in your calendar table, and I would suggest you make changes to it to include it.– Larnu
Nov 19 at 11:18
I do not have [Day] column. Simply because I work on monthly data. My [Units] column is aggregated to full months.
– Przemyslaw Remin
Nov 19 at 12:14
I do not have [Day] column. Simply because I work on monthly data. My [Units] column is aggregated to full months.
– Przemyslaw Remin
Nov 19 at 12:14
So, @PrzemyslawRemin ,in your calendar table, are you only storing the first day of the month? If so, then just remove
AND C.[Day] = 1
from the ON
clause. If not, then why isn't day
in your calendar table if your have a calendar table with every day of the month in it. However, based on your above statement, why does your calendar table NOT have every calendar day in it? That's why it's called a calendar table.– Larnu
Nov 19 at 12:17
So, @PrzemyslawRemin ,in your calendar table, are you only storing the first day of the month? If so, then just remove
AND C.[Day] = 1
from the ON
clause. If not, then why isn't day
in your calendar table if your have a calendar table with every day of the month in it. However, based on your above statement, why does your calendar table NOT have every calendar day in it? That's why it's called a calendar table.– Larnu
Nov 19 at 12:17
|
show 2 more comments
up vote
1
down vote
You could generate all possible combinations for year, month and product in your data using CROSS JOIN
. A simple LEFT JOIN
will give you the value or NULL if data for a specific combination exists.
DECLARE @t TABLE (year int, month int, product int, unit int);
INSERT INTO @t VALUES
(2017, 1, 1, 1721),
(2017, 2, 1, 4915),
(2017, 5, 1, 5230),
(2018, 2, 1, 5216),
(2018, 3, 1, 8911),
(2017, 4, 2, 2933),
(2018, 1, 2, 7672);
SELECT ally.year, allm.month, allp.product, curr.units, prev.units AS units_prev
FROM (SELECT DISTINCT year FROM @t) AS ally
CROSS JOIN (SELECT DISTINCT product FROM @t) AS allp
CROSS JOIN (SELECT DISTINCT month FROM @t) AS allm
LEFT JOIN @t AS curr ON curr.year = ally.year AND curr.product = allp.product AND curr.month = allm.month
LEFT JOIN @t AS prev ON prev.year = ally.year - 1 AND prev.product = allp.product AND prev.month = allm.month
Result:
| year | month | product | units | units_prev |
|------|-------|---------|-------|------------|
| 2017 | 1 | 1 | 1721 | NULL |
| 2017 | 2 | 1 | 4915 | NULL |
| 2017 | 3 | 1 | NULL | NULL |
| 2017 | 4 | 1 | NULL | NULL |
| 2017 | 5 | 1 | 5230 | NULL |
| 2017 | 1 | 2 | NULL | NULL |
| 2017 | 2 | 2 | NULL | NULL |
| 2017 | 3 | 2 | NULL | NULL |
| 2017 | 4 | 2 | 2933 | NULL |
| 2017 | 5 | 2 | NULL | NULL |
| 2018 | 1 | 1 | NULL | 1721 |
| 2018 | 2 | 1 | 5216 | 4915 |
| 2018 | 3 | 1 | 8911 | NULL |
| 2018 | 4 | 1 | NULL | NULL |
| 2018 | 5 | 1 | NULL | 5230 |
| 2018 | 1 | 2 | 7672 | NULL |
| 2018 | 2 | 2 | NULL | NULL |
| 2018 | 3 | 2 | NULL | NULL |
| 2018 | 4 | 2 | NULL | 2933 |
| 2018 | 5 | 2 | NULL | NULL |
Hi Salman, (1) you do not have [year] column in your final result. (2) I do not understand the clauseWHERE year = 2018 OR year = 2018 - 1
(it means select *). (3) [Year] column values are exemplary in source table. There are [Year] as 2016, 2015.... Please do not refer to values. (4) Can you please explain how your Cartesian product produces just 10 rows instead of 20 (see. answer of Gordon Linoff)?
– Przemyslaw Remin
Nov 20 at 9:09
Sorry I thought we're talking about two years only. It could be made to work with n years easily butLAG
is better than twoLEFT JOINS
.
– Salman A
Nov 20 at 9:20
Why do you thinkLAG
is better then twoLEFT JOINS
? The performance seems to be in favor of self-join: dba.stackexchange.com/questions/158374/…
– Przemyslaw Remin
Nov 20 at 9:41
It depends. But I've revised my answer nevertheless. It is now exactly the same as the other except that it uses LEFT JOIN to find previous row. Plan looks slightly better.
– Salman A
Nov 20 at 9:44
add a comment |
up vote
1
down vote
You could generate all possible combinations for year, month and product in your data using CROSS JOIN
. A simple LEFT JOIN
will give you the value or NULL if data for a specific combination exists.
DECLARE @t TABLE (year int, month int, product int, unit int);
INSERT INTO @t VALUES
(2017, 1, 1, 1721),
(2017, 2, 1, 4915),
(2017, 5, 1, 5230),
(2018, 2, 1, 5216),
(2018, 3, 1, 8911),
(2017, 4, 2, 2933),
(2018, 1, 2, 7672);
SELECT ally.year, allm.month, allp.product, curr.units, prev.units AS units_prev
FROM (SELECT DISTINCT year FROM @t) AS ally
CROSS JOIN (SELECT DISTINCT product FROM @t) AS allp
CROSS JOIN (SELECT DISTINCT month FROM @t) AS allm
LEFT JOIN @t AS curr ON curr.year = ally.year AND curr.product = allp.product AND curr.month = allm.month
LEFT JOIN @t AS prev ON prev.year = ally.year - 1 AND prev.product = allp.product AND prev.month = allm.month
Result:
| year | month | product | units | units_prev |
|------|-------|---------|-------|------------|
| 2017 | 1 | 1 | 1721 | NULL |
| 2017 | 2 | 1 | 4915 | NULL |
| 2017 | 3 | 1 | NULL | NULL |
| 2017 | 4 | 1 | NULL | NULL |
| 2017 | 5 | 1 | 5230 | NULL |
| 2017 | 1 | 2 | NULL | NULL |
| 2017 | 2 | 2 | NULL | NULL |
| 2017 | 3 | 2 | NULL | NULL |
| 2017 | 4 | 2 | 2933 | NULL |
| 2017 | 5 | 2 | NULL | NULL |
| 2018 | 1 | 1 | NULL | 1721 |
| 2018 | 2 | 1 | 5216 | 4915 |
| 2018 | 3 | 1 | 8911 | NULL |
| 2018 | 4 | 1 | NULL | NULL |
| 2018 | 5 | 1 | NULL | 5230 |
| 2018 | 1 | 2 | 7672 | NULL |
| 2018 | 2 | 2 | NULL | NULL |
| 2018 | 3 | 2 | NULL | NULL |
| 2018 | 4 | 2 | NULL | 2933 |
| 2018 | 5 | 2 | NULL | NULL |
Hi Salman, (1) you do not have [year] column in your final result. (2) I do not understand the clauseWHERE year = 2018 OR year = 2018 - 1
(it means select *). (3) [Year] column values are exemplary in source table. There are [Year] as 2016, 2015.... Please do not refer to values. (4) Can you please explain how your Cartesian product produces just 10 rows instead of 20 (see. answer of Gordon Linoff)?
– Przemyslaw Remin
Nov 20 at 9:09
Sorry I thought we're talking about two years only. It could be made to work with n years easily butLAG
is better than twoLEFT JOINS
.
– Salman A
Nov 20 at 9:20
Why do you thinkLAG
is better then twoLEFT JOINS
? The performance seems to be in favor of self-join: dba.stackexchange.com/questions/158374/…
– Przemyslaw Remin
Nov 20 at 9:41
It depends. But I've revised my answer nevertheless. It is now exactly the same as the other except that it uses LEFT JOIN to find previous row. Plan looks slightly better.
– Salman A
Nov 20 at 9:44
add a comment |
up vote
1
down vote
up vote
1
down vote
You could generate all possible combinations for year, month and product in your data using CROSS JOIN
. A simple LEFT JOIN
will give you the value or NULL if data for a specific combination exists.
DECLARE @t TABLE (year int, month int, product int, unit int);
INSERT INTO @t VALUES
(2017, 1, 1, 1721),
(2017, 2, 1, 4915),
(2017, 5, 1, 5230),
(2018, 2, 1, 5216),
(2018, 3, 1, 8911),
(2017, 4, 2, 2933),
(2018, 1, 2, 7672);
SELECT ally.year, allm.month, allp.product, curr.units, prev.units AS units_prev
FROM (SELECT DISTINCT year FROM @t) AS ally
CROSS JOIN (SELECT DISTINCT product FROM @t) AS allp
CROSS JOIN (SELECT DISTINCT month FROM @t) AS allm
LEFT JOIN @t AS curr ON curr.year = ally.year AND curr.product = allp.product AND curr.month = allm.month
LEFT JOIN @t AS prev ON prev.year = ally.year - 1 AND prev.product = allp.product AND prev.month = allm.month
Result:
| year | month | product | units | units_prev |
|------|-------|---------|-------|------------|
| 2017 | 1 | 1 | 1721 | NULL |
| 2017 | 2 | 1 | 4915 | NULL |
| 2017 | 3 | 1 | NULL | NULL |
| 2017 | 4 | 1 | NULL | NULL |
| 2017 | 5 | 1 | 5230 | NULL |
| 2017 | 1 | 2 | NULL | NULL |
| 2017 | 2 | 2 | NULL | NULL |
| 2017 | 3 | 2 | NULL | NULL |
| 2017 | 4 | 2 | 2933 | NULL |
| 2017 | 5 | 2 | NULL | NULL |
| 2018 | 1 | 1 | NULL | 1721 |
| 2018 | 2 | 1 | 5216 | 4915 |
| 2018 | 3 | 1 | 8911 | NULL |
| 2018 | 4 | 1 | NULL | NULL |
| 2018 | 5 | 1 | NULL | 5230 |
| 2018 | 1 | 2 | 7672 | NULL |
| 2018 | 2 | 2 | NULL | NULL |
| 2018 | 3 | 2 | NULL | NULL |
| 2018 | 4 | 2 | NULL | 2933 |
| 2018 | 5 | 2 | NULL | NULL |
You could generate all possible combinations for year, month and product in your data using CROSS JOIN
. A simple LEFT JOIN
will give you the value or NULL if data for a specific combination exists.
DECLARE @t TABLE (year int, month int, product int, unit int);
INSERT INTO @t VALUES
(2017, 1, 1, 1721),
(2017, 2, 1, 4915),
(2017, 5, 1, 5230),
(2018, 2, 1, 5216),
(2018, 3, 1, 8911),
(2017, 4, 2, 2933),
(2018, 1, 2, 7672);
SELECT ally.year, allm.month, allp.product, curr.units, prev.units AS units_prev
FROM (SELECT DISTINCT year FROM @t) AS ally
CROSS JOIN (SELECT DISTINCT product FROM @t) AS allp
CROSS JOIN (SELECT DISTINCT month FROM @t) AS allm
LEFT JOIN @t AS curr ON curr.year = ally.year AND curr.product = allp.product AND curr.month = allm.month
LEFT JOIN @t AS prev ON prev.year = ally.year - 1 AND prev.product = allp.product AND prev.month = allm.month
Result:
| year | month | product | units | units_prev |
|------|-------|---------|-------|------------|
| 2017 | 1 | 1 | 1721 | NULL |
| 2017 | 2 | 1 | 4915 | NULL |
| 2017 | 3 | 1 | NULL | NULL |
| 2017 | 4 | 1 | NULL | NULL |
| 2017 | 5 | 1 | 5230 | NULL |
| 2017 | 1 | 2 | NULL | NULL |
| 2017 | 2 | 2 | NULL | NULL |
| 2017 | 3 | 2 | NULL | NULL |
| 2017 | 4 | 2 | 2933 | NULL |
| 2017 | 5 | 2 | NULL | NULL |
| 2018 | 1 | 1 | NULL | 1721 |
| 2018 | 2 | 1 | 5216 | 4915 |
| 2018 | 3 | 1 | 8911 | NULL |
| 2018 | 4 | 1 | NULL | NULL |
| 2018 | 5 | 1 | NULL | 5230 |
| 2018 | 1 | 2 | 7672 | NULL |
| 2018 | 2 | 2 | NULL | NULL |
| 2018 | 3 | 2 | NULL | NULL |
| 2018 | 4 | 2 | NULL | 2933 |
| 2018 | 5 | 2 | NULL | NULL |
edited Nov 20 at 9:43
answered Nov 19 at 15:25
Salman A
173k65331419
173k65331419
Hi Salman, (1) you do not have [year] column in your final result. (2) I do not understand the clauseWHERE year = 2018 OR year = 2018 - 1
(it means select *). (3) [Year] column values are exemplary in source table. There are [Year] as 2016, 2015.... Please do not refer to values. (4) Can you please explain how your Cartesian product produces just 10 rows instead of 20 (see. answer of Gordon Linoff)?
– Przemyslaw Remin
Nov 20 at 9:09
Sorry I thought we're talking about two years only. It could be made to work with n years easily butLAG
is better than twoLEFT JOINS
.
– Salman A
Nov 20 at 9:20
Why do you thinkLAG
is better then twoLEFT JOINS
? The performance seems to be in favor of self-join: dba.stackexchange.com/questions/158374/…
– Przemyslaw Remin
Nov 20 at 9:41
It depends. But I've revised my answer nevertheless. It is now exactly the same as the other except that it uses LEFT JOIN to find previous row. Plan looks slightly better.
– Salman A
Nov 20 at 9:44
add a comment |
Hi Salman, (1) you do not have [year] column in your final result. (2) I do not understand the clauseWHERE year = 2018 OR year = 2018 - 1
(it means select *). (3) [Year] column values are exemplary in source table. There are [Year] as 2016, 2015.... Please do not refer to values. (4) Can you please explain how your Cartesian product produces just 10 rows instead of 20 (see. answer of Gordon Linoff)?
– Przemyslaw Remin
Nov 20 at 9:09
Sorry I thought we're talking about two years only. It could be made to work with n years easily butLAG
is better than twoLEFT JOINS
.
– Salman A
Nov 20 at 9:20
Why do you thinkLAG
is better then twoLEFT JOINS
? The performance seems to be in favor of self-join: dba.stackexchange.com/questions/158374/…
– Przemyslaw Remin
Nov 20 at 9:41
It depends. But I've revised my answer nevertheless. It is now exactly the same as the other except that it uses LEFT JOIN to find previous row. Plan looks slightly better.
– Salman A
Nov 20 at 9:44
Hi Salman, (1) you do not have [year] column in your final result. (2) I do not understand the clause
WHERE year = 2018 OR year = 2018 - 1
(it means select *). (3) [Year] column values are exemplary in source table. There are [Year] as 2016, 2015.... Please do not refer to values. (4) Can you please explain how your Cartesian product produces just 10 rows instead of 20 (see. answer of Gordon Linoff)?– Przemyslaw Remin
Nov 20 at 9:09
Hi Salman, (1) you do not have [year] column in your final result. (2) I do not understand the clause
WHERE year = 2018 OR year = 2018 - 1
(it means select *). (3) [Year] column values are exemplary in source table. There are [Year] as 2016, 2015.... Please do not refer to values. (4) Can you please explain how your Cartesian product produces just 10 rows instead of 20 (see. answer of Gordon Linoff)?– Przemyslaw Remin
Nov 20 at 9:09
Sorry I thought we're talking about two years only. It could be made to work with n years easily but
LAG
is better than two LEFT JOINS
.– Salman A
Nov 20 at 9:20
Sorry I thought we're talking about two years only. It could be made to work with n years easily but
LAG
is better than two LEFT JOINS
.– Salman A
Nov 20 at 9:20
Why do you think
LAG
is better then two LEFT JOINS
? The performance seems to be in favor of self-join: dba.stackexchange.com/questions/158374/…– Przemyslaw Remin
Nov 20 at 9:41
Why do you think
LAG
is better then two LEFT JOINS
? The performance seems to be in favor of self-join: dba.stackexchange.com/questions/158374/…– Przemyslaw Remin
Nov 20 at 9:41
It depends. But I've revised my answer nevertheless. It is now exactly the same as the other except that it uses LEFT JOIN to find previous row. Plan looks slightly better.
– Salman A
Nov 20 at 9:44
It depends. But I've revised my answer nevertheless. It is now exactly the same as the other except that it uses LEFT JOIN to find previous row. Plan looks slightly better.
– Salman A
Nov 20 at 9:44
add a comment |
up vote
0
down vote
If you want the rows where nothing was sold in both 2017 and 2018 as in your desired results for March 2017 as well you need to generate out the month, year and join in the product to get the null values.
This query does it for the month and year, hopefully you should be able to add the Product as well if required
DECLARE @startMonth INT=1
DECLARE @endMonth INT=12
DECLARE @startYear INT=2017
DECLARE @endYear INT=2018
;
WITH months AS (
SELECT @startMonth AS m
UNION ALL
SELECT m+1 FROM months WHERE m+1<=@endMonth
),
years AS (
SELECT @startYear AS y
UNION ALL
SELECT y+1 FROM years WHERE y+1<=@endYear
),
monthYears AS (
SELECT m, y
FROM months, years
)
SELECT thisYear.[Year], thisYear.[Month], thisYear.[Product], thisYear.[Units], prevYear.[Units] as units_prev
FROM
(SELECT [Product], my.y as [Year], my.m as [Month], [Units]
FROM monthYears my
LEFT JOIN sales on my.m = [Month] and my.y = [Year]) as thisYear
LEFT OUTER JOIN
(SELECT [Product], my.y as [Year], my.m as [Month], my.y + 1 as NextYear, [Units]
FROM monthYears my
LEFT JOIN sales on my.m = [Month] and my.y = [Year]) as prevYear
on thisYear.Product = prevYear.Product
and (thisYEAR.[Year]) = prevYear.[NextYear]
and thisYEAR.[Month] = prevYear.[Month]
ORDER BY thisYear.[Year], thisYear.[Month], thisYear.[Product]
option (maxrecursion 12);
add a comment |
up vote
0
down vote
If you want the rows where nothing was sold in both 2017 and 2018 as in your desired results for March 2017 as well you need to generate out the month, year and join in the product to get the null values.
This query does it for the month and year, hopefully you should be able to add the Product as well if required
DECLARE @startMonth INT=1
DECLARE @endMonth INT=12
DECLARE @startYear INT=2017
DECLARE @endYear INT=2018
;
WITH months AS (
SELECT @startMonth AS m
UNION ALL
SELECT m+1 FROM months WHERE m+1<=@endMonth
),
years AS (
SELECT @startYear AS y
UNION ALL
SELECT y+1 FROM years WHERE y+1<=@endYear
),
monthYears AS (
SELECT m, y
FROM months, years
)
SELECT thisYear.[Year], thisYear.[Month], thisYear.[Product], thisYear.[Units], prevYear.[Units] as units_prev
FROM
(SELECT [Product], my.y as [Year], my.m as [Month], [Units]
FROM monthYears my
LEFT JOIN sales on my.m = [Month] and my.y = [Year]) as thisYear
LEFT OUTER JOIN
(SELECT [Product], my.y as [Year], my.m as [Month], my.y + 1 as NextYear, [Units]
FROM monthYears my
LEFT JOIN sales on my.m = [Month] and my.y = [Year]) as prevYear
on thisYear.Product = prevYear.Product
and (thisYEAR.[Year]) = prevYear.[NextYear]
and thisYEAR.[Month] = prevYear.[Month]
ORDER BY thisYear.[Year], thisYear.[Month], thisYear.[Product]
option (maxrecursion 12);
add a comment |
up vote
0
down vote
up vote
0
down vote
If you want the rows where nothing was sold in both 2017 and 2018 as in your desired results for March 2017 as well you need to generate out the month, year and join in the product to get the null values.
This query does it for the month and year, hopefully you should be able to add the Product as well if required
DECLARE @startMonth INT=1
DECLARE @endMonth INT=12
DECLARE @startYear INT=2017
DECLARE @endYear INT=2018
;
WITH months AS (
SELECT @startMonth AS m
UNION ALL
SELECT m+1 FROM months WHERE m+1<=@endMonth
),
years AS (
SELECT @startYear AS y
UNION ALL
SELECT y+1 FROM years WHERE y+1<=@endYear
),
monthYears AS (
SELECT m, y
FROM months, years
)
SELECT thisYear.[Year], thisYear.[Month], thisYear.[Product], thisYear.[Units], prevYear.[Units] as units_prev
FROM
(SELECT [Product], my.y as [Year], my.m as [Month], [Units]
FROM monthYears my
LEFT JOIN sales on my.m = [Month] and my.y = [Year]) as thisYear
LEFT OUTER JOIN
(SELECT [Product], my.y as [Year], my.m as [Month], my.y + 1 as NextYear, [Units]
FROM monthYears my
LEFT JOIN sales on my.m = [Month] and my.y = [Year]) as prevYear
on thisYear.Product = prevYear.Product
and (thisYEAR.[Year]) = prevYear.[NextYear]
and thisYEAR.[Month] = prevYear.[Month]
ORDER BY thisYear.[Year], thisYear.[Month], thisYear.[Product]
option (maxrecursion 12);
If you want the rows where nothing was sold in both 2017 and 2018 as in your desired results for March 2017 as well you need to generate out the month, year and join in the product to get the null values.
This query does it for the month and year, hopefully you should be able to add the Product as well if required
DECLARE @startMonth INT=1
DECLARE @endMonth INT=12
DECLARE @startYear INT=2017
DECLARE @endYear INT=2018
;
WITH months AS (
SELECT @startMonth AS m
UNION ALL
SELECT m+1 FROM months WHERE m+1<=@endMonth
),
years AS (
SELECT @startYear AS y
UNION ALL
SELECT y+1 FROM years WHERE y+1<=@endYear
),
monthYears AS (
SELECT m, y
FROM months, years
)
SELECT thisYear.[Year], thisYear.[Month], thisYear.[Product], thisYear.[Units], prevYear.[Units] as units_prev
FROM
(SELECT [Product], my.y as [Year], my.m as [Month], [Units]
FROM monthYears my
LEFT JOIN sales on my.m = [Month] and my.y = [Year]) as thisYear
LEFT OUTER JOIN
(SELECT [Product], my.y as [Year], my.m as [Month], my.y + 1 as NextYear, [Units]
FROM monthYears my
LEFT JOIN sales on my.m = [Month] and my.y = [Year]) as prevYear
on thisYear.Product = prevYear.Product
and (thisYEAR.[Year]) = prevYear.[NextYear]
and thisYEAR.[Month] = prevYear.[Month]
ORDER BY thisYear.[Year], thisYear.[Month], thisYear.[Product]
option (maxrecursion 12);
answered Nov 19 at 11:48
PhilS
1515
1515
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53372649%2fsql-join-table-to-itself-to-get-data-for-previous-year%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Why does 2017, 4, 1 has 2933?
– Salman A
Nov 19 at 11:23
@SalmanA Corrected. Thx.
– Przemyslaw Remin
Nov 19 at 11:34
Do you really need the rows for 2017? You could simply have an output with 5 rows like
product, month, 2018_units, 2017_units
– Salman A
Nov 19 at 15:07
@SalmanA yes, I do need the data in that structure for every year - there are more years then just 2018, and 2017.
– Przemyslaw Remin
Nov 20 at 9:16