pivot and cascade null columns - sql-server

I have a table that holds values for particular months:
| MFG | DATE | FACTOR |
-----------------------------
| 1 | 2013-01-01 | 1 |
| 2 | 2013-01-01 | 0.8 |
| 2 | 2013-02-01 | 1 |
| 2 | 2013-12-01 | 1.55 |
| 3 | 2013-01-01 | 1 |
| 3 | 2013-04-01 | 1.3 |
| 3 | 2013-05-01 | 1.2 |
| 3 | 2013-06-01 | 1.1 |
| 3 | 2013-07-01 | 1 |
| 4 | 2013-01-01 | 0.9 |
| 4 | 2013-02-01 | 1 |
| 4 | 2013-12-01 | 1.8 |
| 5 | 2013-01-01 | 1.4 |
| 5 | 2013-02-01 | 1 |
| 5 | 2013-10-01 | 1.3 |
| 5 | 2013-11-01 | 1.2 |
| 5 | 2013-12-01 | 1.5 |
What I would like to do is pivot these using a calendar table (already defined):
And finally, cascade the NULL columns to use the previous value.
What I've got so far is a query that will populate the NULLs with the last value for mfg = 3. Each mfg will always have a value for the first of the year. My question is; how do I pivot this and extend to all mfg?
SELECT c.[date],
f.[factor],
Isnull(f.[factor], (SELECT TOP 1 factor
FROM factors
WHERE [date] < c.[date]
AND [factor] IS NOT NULL
AND mfg = 3
ORDER BY [date] DESC)) AS xFactor
FROM (SELECT [date]
FROM calendar
WHERE Datepart(yy, [date]) = 2013
AND Datepart(d, [date]) = 1) c
LEFT JOIN (SELECT [date],
[factor]
FROM factors
WHERE mfg = 3) f
ON f.[date] = c.[date]
Result
| DATE | FACTOR | XFACTOR |
---------------------------------
| 2013-01-01 | 1 | 1 |
| 2013-02-01 | (null) | 1 |
| 2013-03-01 | (null) | 1 |
| 2013-04-01 | 1.3 | 1.3 |
| 2013-05-01 | 1.2 | 1.2 |
| 2013-06-01 | 1.1 | 1.1 |
| 2013-07-01 | 1 | 1 |
| 2013-08-01 | (null) | 1 |
| 2013-09-01 | (null) | 1 |
| 2013-10-01 | (null) | 1 |
| 2013-11-01 | (null) | 1 |
| 2013-12-01 | (null) | 1 |
SQL Fiddle

Don't know if you need the dates to be dynamic from the calender table or if mfg can be more than 5 but this should give you some ideas.
select *
from (
select c.date,
t.mfg,
(
select top 1 f.factor
from factors as f
where f.date <= c.date and
f.mfg = t.mfg and
f.factor is not null
order by f.date desc
) as factor
from calendar as c
cross apply(values(1),(2),(3),(4),(5)) as t(mfg)
) as t
pivot (
max(t.factor) for t.date in ([20130101], [20130201], [20130301],
[20130401], [20130501], [20130601],
[20130701], [20130801], [20130901],
[20131001], [20131101], [20131201])
) as P
SQL Fiddle

Related

SQL server multi-period comparison

I have the following table T1 (sample shown), which shows the category for each client (each with a unique ID) on a specific date and his category on the next date:
+------------+----------------+----------+---------------+
| DATE | ID | STAGE | STAGE_NEXT |
+------------+----------------+----------+---------------+
| 2014-07-01 | 10010101841033 | 1 | 1 |
| 2015-07-01 | 74610108542146 | 1 | 1 |
| 2014-10-01 | 47970108841775 | 3 | 3 |
| 2014-10-01 | 48870108841816 | 2 | 3 |
| 2014-10-01 | 32910097439541 | 1 | 1 |
| 2016-04-01 | 46930097440855 | 2 | 3 |
| 2016-04-01 | 47380097440931 | 2 | 3 |
| 2016-04-01 | 54560097441411 | 3 | 3 |
+------------+----------------+----------+---------------+
Table info:
- Rows: 513,000
- Date range: 2013-01-01 to 2019-10-01
- Stages: 1 - 3
I need to create a new column in T1, which will flag the date a client moved to Stage 1 if at any point he was in Stage 3. For example if we take 1 client from T1 by using this code:
SELECT [DATE], ID, STAGE, STAGE_NEXT
FROM T1
WHERE ID = '74610108542146'
ORDER BY [DATE]
We get the following result:
+------------+----------------+-------+------------+
| DATE | ID | STAGE | STAGE_NEXT |
+------------+----------------+-------+------------+
| 2015-07-01 | 74610108542146 | 1 | 1 |
| 2015-10-01 | 74610108542146 | 1 | 1 |
| 2016-01-01 | 74610108542146 | 1 | 2 |
| 2016-04-01 | 74610108542146 | 2 | 1 |
| 2016-07-01 | 74610108542146 | 1 | 1 |
| 2016-10-01 | 74610108542146 | 1 | 2 |
| 2017-01-01 | 74610108542146 | 2 | 3 |
| 2017-04-01 | 74610108542146 | 3 | 3 |
| 2017-07-01 | 74610108542146 | 3 | 2 |
| 2017-10-01 | 74610108542146 | 2 | 1 |
| 2018-01-01 | 74610108542146 | 1 | 1 |
| 2018-04-01 | 74610108542146 | 1 | NULL |
+------------+----------------+-------+------------+
After the new column with the flag is added to T1 we should be able to get the following result using this code on T1:
SELECT [DATE], ID, STAGE, STAGE_NEXT, FLAG
FROM T1
WHERE ID = '74610108542146'
ORDER BY [DATE]
+------------+----------------+-------+------------+------+
| DATE | ID | STAGE | STAGE_NEXT | FLAG |
+------------+----------------+-------+------------+------+
| 2015-07-01 | 74610108542146 | 1 | 1 | 0 |
| 2015-10-01 | 74610108542146 | 1 | 1 | 0 |
| 2016-01-01 | 74610108542146 | 1 | 2 | 0 |
| 2016-04-01 | 74610108542146 | 2 | 1 | 0 |
| 2016-07-01 | 74610108542146 | 1 | 1 | 0 |
| 2016-10-01 | 74610108542146 | 1 | 2 | 0 |
| 2017-01-01 | 74610108542146 | 2 | 3 | 0 |
| 2017-04-01 | 74610108542146 | 3 | 3 | 0 |
| 2017-07-01 | 74610108542146 | 3 | 2 | 0 |
| 2017-10-01 | 74610108542146 | 2 | 1 | 1 |
| 2018-01-01 | 74610108542146 | 1 | 1 | 0 |
| 2018-04-01 | 74610108542146 | 1 | NULL | 0 |
+------------+----------------+-------+------------+------+
If the client never moved to Stage 3 then the flag for the client is always 0
You could calculate and update the new FLAG column from a CTE.
The update statement uses the LAG function to use the previous STAGE in the calculation of FLAG.
;WITH CTE AS
(
SELECT ID, [DATE], FLAG,
CASE
WHEN STAGE = 2
AND STAGE_NEXT = 1
AND LAG(STAGE) OVER (PARTITION BY ID ORDER BY IIF(STAGE=2 AND STAGE_NEXT=2,0,1), [DATE]) = 3
THEN 1
ELSE 0
END AS CalcFlag
FROM T1
WHERE ID = '10010101841033' -- optional, to target only 1 ID
)
UPDATE CTE
SET FLAG = CalcFlag
WHERE (FLAG IS NULL OR FLAG != CalcFlag);
The IIF(STAGE=2 AND STAGE_NEXT=2,0,1) in the LAG is used to make the calculation also work when the stage 2 is repeated.
Test it on rextester here
Try this,
DECLARE #T1 table
(
[DATE] date,ID numeric(18,0),STAGE int,STAGE_NEXT int
)
INSERT INTO #T1 VALUES
('2013-01-01',10010101841033,1,1 ),
('2013-04-01',10010101841033,1,3 ),
('2013-07-01',10010101841033,3,3 ),
('2013-10-01',10010101841033,3,2 ),
('2014-01-01',10010101841033,2,1 ),
('2014-04-01',10010101841033,1,1 ),
('2014-07-01',10010101841033,1,1 ),
('2014-10-01',10010101841033,1,NULL),
('2014-07-01',47820108841771,1,2)
SELECT A.DATE,A.ID,A.STAGE,A.STAGE_NEXT,
CASE WHEN B.ID IS NOT NULL AND (STAGE_NEXT=1 AND STAGE>STAGE_NEXT) THEN 1 ELSE 0 END AS FLAG
FROM #T1 A
LEFT JOIN
(
SELECT DISTINCT ID AS ID
FROM #T1
WHERE STAGE_NEXT=3
)B
ON A.ID=B.ID

T-SQL: Values are grouped by month, if there is no value for a month the month should also appear and display "NULL"

i have a SQL that displays turnover, stock and other values for stores grouped by month. Logically, if there is no value for a month, the month doesn't appear. The target is that the empty month should appear and display "NULL" for the values. The empty months should range from the #FROM to the #TO parameter (201807 to 201907) in this case.
Before:
+-------+--------+----------+----------+-------+
| Store | Month | Incoming | Turnover | Stock |
+-------+--------+----------+----------+-------+
| 123 | 201810 | 5 | 4 | 1 |
| 123 | 201811 | 0 | 1 | 0 |
| 123 | 201901 | 25 | 5 | 20 |
| 123 | 201902 | 5 | 10 | 15 |
| 123 | 201903 | 8 | 9 | 14 |
| 123 | 201904 | 5 | 4 | 15 |
| 123 | 201905 | 10 | 5 | 20 |
+-------+--------+----------+----------+-------+
After:
+-------+--------+----------+----------+-------+
| Store | Month | Incoming | Turnover | Stock |
+-------+--------+----------+----------+-------+
| 123 | 201807 | NULL | NULL | NULL |
| 123 | 201808 | NULL | NULL | NULL |
| 123 | 201809 | NULL | NULL | NULL |
| 123 | 201810 | 5 | 4 | 1 |
| 123 | 201811 | 0 | 1 | 0 |
| 123 | 201812 | NULL | NULL | NULL |
| 123 | 201901 | 25 | 5 | 20 |
| 123 | 201902 | 5 | 10 | 15 |
| 123 | 201903 | 8 | 9 | 14 |
| 123 | 201904 | 5 | 4 | 15 |
| 123 | 201905 | 10 | 5 | 20 |
| 123 | 201906 | NULL | NULL | NULL |
| 123 | 201907 | NULL | NULL | NULL |
+-------+--------+----------+----------+-------+
Code Example: db<>fiddle
I have absolutely no idea how to solve this and will thank you in advance for your help! :)
You can try to use cte recursive make a calendar table, then do outer-join
;WITH CTE AS (
SELECT CAST(CAST(#FROM AS VARCHAR(10)) + '01' AS DATE) fromDt,
CAST(CAST(#TO AS VARCHAR(10)) + '01' AS DATE) toDt,
Store
FROM (SELECT DISTINCT Store FROM #Test) t1
UNION ALL
SELECT DATEADD(MONTH,1,fromDt),toDt,Store
FROM CTE
WHERE DATEADD(MONTH,1,fromDt) <= toDt
)
SELECT FORMAT(fromDt,'yyyyMM') Month,
c.Store,
t.Incoming,
t.Turnover,
t.Stock
FROM CTE c
LEFT JOIN #Test t on
c.fromDt = CAST(CAST(t.Month AS VARCHAR(10)) + '01' AS DATE)
and
c.Store = t.Store
sqlfiddle

Pivot on CTE Description

If anyone can point me in the right direction, I would appreciate it.
This is the result of a CTE query on multiple tables. I require to redefine the output and I can only think of using a pivot to do it.
Id | Parent_Id | Description | Account_Number | Year_of_Entry | Amount
-----------------------------------------------------------------------
1 | NULL | V | 001 | 2017 | 4
2 | 1 | W | 002 | 2017 | 2
3 | 2 | X | 003 | 2017 | 1
4 | 2 | Y | 004 | 2017 | 1
5 | 1 | Z | 005 | 2017 | 2
6 | 5 | T | 006 | 2017 | 2
7 | 6 | X | 007 | 2017 | 1
8 | 6 | Y | 008 | 2017 | 1
1 | NULL | V | 001 | 2016 | 8
2 | 1 | W | 002 | 2016 | 4
3 | 2 | X | 003 | 2016 | 2
4 | 2 | Y | 004 | 2016 | 2
5 | 1 | Z | 005 | 2016 | 4
6 | 5 | X | 006 | 2016 | 2
7 | 5 | Y | 007 | 2016 | 2
I would like to get an output that matches this one.
Id | Parent_Id | Description | Account_Number | Year_of_entry| Amount| X | Y
---------------------------------------------------------------------------------
1 | NULL | V | 001 | 2017 | 4 | 2 | 2
2 | 1 | W | 002 | 2017 | 2 | 1 | 1
5 | 1 | Z | 005 | 2017 | 2 | 1 | 1
6 | 5 | T | 006 | 2017 | 2 | 1 | 1
1 | NULL | V | 001 | 2016 | 8 | 4 | 4
2 | 1 | W | 002 | 2016 | 4 | 2 | 2
5 | 1 | Z | 005 | 2016 | 4 | 2 | 2
Current output with the CTE recursion query
Id | Parent_Id | Description | Account_Number | Year_of_entry| Amount| X | Y
---------------------------------------------------------------------------------
1 | NULL | V | 001 | 2017 | 4 | 0 | 0
2 | 1 | W | 002 | 2017 | 2 | 1 | 1
5 | 1 | Z | 005 | 2017 | 2 | 0 | 0
6 | 5 | T | 006 | 2017 | 2 | 1 | 1
1 | NULL | V | 001 | 2016 | 8 | 0 | 0
2 | 1 | W | 002 | 2016 | 4 | 2 | 2
5 | 1 | Z | 005 | 2016 | 4 | 2 | 2
Current output with #Daniel code
Id | Parent_Id | Description | Account_Number | Year_of_entry| Amount| X | Y
---------------------------------------------------------------------------------
2 | 1 | W | 002 | 2017 | 2 | 1 | 1
6 | 5 | T | 006 | 2017 | 2 | 1 | 1
2 | 1 | W | 002 | 2016 | 4 | 2 | 2
5 | 1 | Z | 005 | 2016 | 4 | 2 | 2
I have used isnull to convert to 0
EDIT : Thanks for the Help.
I ended up using 2 recursive CTEs to resolve this.
The first to get the X and Y values to the Parent.
The Second to pass all the totals up the tree to the root.
Thanks again for the assistance.
Regards
MJK
Use conditional logic with aggregation to create your x and y columns:
select a.Id, a.Parent_Id, a.Description, a.Account_Number, a.Year_of_Entry, a.Amount,
max(case when b.description in ('x','y')
then null else b.amount end) amount, sum(case when b.description='x' then b.amount else null end) X,
sum(case when b.description='y' then b.amount else null end) y from yourtable a
join yourtable b on (a.id=b.parent_id or a.parent_id is null) and a.Year_of_Entry=b.Year_of_Entry
where b. description in ('x','y')
group by a.Id, a.Parent_Id, a.Description, a.Account_Number, a.Year_of_Entry, a.Amount
order by a.Year_of_Entry desc, a.parent_id

Select a specific line if i have the same information

I have a table with a data as bellow :
+--------+----------+-------+------------+--------------+
| month | code | type | date | PersonID |
+--------+----------+-------+------------+--------------+
| 201501 | 178954 | 3 | 2014-12-3 | 10 |
| 201501 | 178954 | 3 | 2014-12-3 | 10 |
| 201501 | 178955 | 2 | 2014-12-13 | 10 |
| 201501 | 178955 | 2 | 2014-12-13 | 10 |
| 201501 | 178956 | 2 | 2014-12-11 | 10 |
| 201501 | 178958 | 1 | 2014-12-10 | 10 |
| 201501 | 178959 | 2 | 2014-12-12 | 15 |
| 201501 | 178959 | 2 | 2014-12-12 | 15 |
| 201501 | 178954 | 1 | 2014-12-11 | 13 |
| 201501 | 178954 | 1 | 2014-12-11 | 13 |
+--------+----------+-------+------------+--------------+
In my first 6 lines i have the same PersonID in the same Month What i want if i have the same personID in the same Month i want to select the person who have the type is 2 with the recent date in my case the output will be like as bellow:
+--------+--------+------+------------+----------+
| month | code | type| date | PersonID |
+--------+--------+------+------------+----------+
| 201501 | 178955 | 2 | 2014-12-13 | 10 |
| 201501 | 178959 | 2 | 2014-12-12 | 15 |
| 201501 | 178954 | 2 | 2014-12-11 | 13 |
+--------+--------+------+------------+----------+
Also if they are some duplicate rows i don't want to display it
They are any solution to that ?
Simply use GROUP BY:
https://msdn.microsoft.com/de-de/library/ms177673(v=sql.120).aspx
SELECT mont, code, ... FROM tabelname GROUP BY PersonID, date, ...
Note that you have to specifiy all columns in the group by.
SELECT DISTINCT A.month, A.code, A.type, B.date, B.PersonID FROM YourTable A
INNER JOIN (SELECT PersonID, MAX(date) as date FROM YourTable
GROUP BY PersonID) B
ON (A.PersonID = B.PersonID
AND A.date = B.date)
WHERE A.type = 2 ORDER BY B.date DESC, A.PersonID
Just in case you/others are still wondering.

Calculate forecast average using recursive CTE

I was trying to answer a question here, where I need to calculate a forecast of sales based on the 3 previous months which either can be actuals or forecast.
Month Actuals Forecast
1 10
2 15
3 17
4 14.00
5 15.33
6 15.44
7 14.93
Month 4 = (10+15+17)/3
Month 5 = (15+17+14)/3
Month 6 = (17+14+15.33)/3
Month 7 = (14+15.33+15.44)/3
I've been trying to do this using a recursive CTE:
;WITH cte([month],forecast) AS (
SELECT 1,CAST(10 AS DECIMAL(28,2))
UNION ALL
SELECT 2,CAST(15 AS DECIMAL(28,2))
UNION ALL
SELECT 3,CAST(17 AS DECIMAL(28,2))
UNION ALL
SELECT
[month]=[month]+1,
forecast=CAST(AVG(forecast) OVER (ORDER BY [month] ROWS BETWEEN 3 PRECEDING AND 1 PRECEDING) AS DECIMAL(28,2))
FROM
cte
WHERE
[month]<=12
)
SELECT * FROM cte WHERE month<=12;
Fiddle: http://sqlfiddle.com/#!6/9ac4a/3
But it doesn't work as expected, as It returns the following result:
| month | forecast |
|-------|----------|
| 1 | 10 |
| 2 | 15 |
| 3 | 17 |
| 4 | (null) |
| 5 | (null) |
| 6 | (null) |
| 7 | (null) |
| 8 | (null) |
| 9 | (null) |
| 10 | (null) |
| 11 | (null) |
| 12 | (null) |
| 3 | (null) |
| 4 | (null) |
| 5 | (null) |
| 6 | (null) |
| 7 | (null) |
| 8 | (null) |
| 9 | (null) |
| 10 | (null) |
| 11 | (null) |
| 12 | (null) |
| 2 | (null) |
| 3 | (null) |
| 4 | (null) |
| 5 | (null) |
| 6 | (null) |
| 7 | (null) |
| 8 | (null) |
| 9 | (null) |
| 10 | (null) |
| 11 | (null) |
| 12 | (null) |
Expected output:
| month | forecast |
|-------|----------|
| 1 | 10 |
| 2 | 15 |
| 3 | 17 |
| 4 | 14.00 |
| 5 | 15.33 |
| 6 | 15.44 |
| 7 | 14.93 |
| 8 | 15.23 |
| 9 | 15.20 |
| 10 | 15.12 |
| 11 | 15.18 |
| 12 | 15.17 |
Can someone tell me what's wrong with this query?
I propose something like this:
WITH T AS
(
SELECT 1 AS [month], CAST(10 AS DECIMAL(28,2)) AS [forecast], CAST(-5 AS DECIMAL(28,2)) AS three_months_ago_forecast, CAST(9 AS decimal(28,2)) AS two_months_ago_forecast, CAST(26 AS decimal(28,2)) as one_month_ago_forecast
UNION ALL
SELECT 2,CAST(15 AS DECIMAL(28,2)), CAST(9 AS decimal(28,2)), CAST(26 AS decimal(28,2)), CAST(10 AS DECIMAL(28,2))
UNION ALL
SELECT 3,CAST(17 AS DECIMAL(28,2)), CAST(26 AS decimal(28,2)), CAST(10 AS DECIMAL(28,2)), CAST(15 AS DECIMAL(28,2))
),
LT AS -- LastForecast
(
SELECT *
FROM T
WHERE [month] = 3
),
FF AS -- Future Forecast
(
SELECT *
FROM LT
UNION ALL
SELECT
FF.[month] + 1 AS [month],
CAST( (FF.forecast * 4 - FF.three_months_ago_forecast) / 3 AS decimal(28,2)) AS forecast,
FF.two_months_ago_forecast as three_months_ago_forecast,
FF.one_month_ago_forecast as two_months_ago_forecast,
FF.forecast as one_month_ago_forecast
FROM FF
WHERE
FF.[month] < 12
)
SELECT * FROM T
WHERE [month] < 3
UNION ALL
SELECT * FROM FF
Output:
+-------+----------+---------------------------+-------------------------+------------------------+
| month | forecast | three_months_ago_forecast | two_months_ago_forecast | one_month_ago_forecast |
+-------+----------+---------------------------+-------------------------+------------------------+
| 1 | 10.00 | -5.00 | 9.00 | 26.00 |
| 2 | 15.00 | 9.00 | 26.00 | 10.00 |
| 3 | 17.00 | 26.00 | 10.00 | 15.00 |
| 4 | 14.00 | 10.00 | 15.00 | 17.00 |
| 5 | 15.33 | 15.00 | 17.00 | 14.00 |
| 6 | 15.44 | 17.00 | 14.00 | 15.33 |
| 7 | 14.92 | 14.00 | 15.33 | 15.44 |
| 8 | 15.23 | 15.33 | 15.44 | 14.92 |
| 9 | 15.20 | 15.44 | 14.92 | 15.23 |
| 10 | 15.12 | 14.92 | 15.23 | 15.20 |
| 11 | 15.19 | 15.23 | 15.20 | 15.12 |
| 12 | 15.18 | 15.20 | 15.12 | 15.19 |
+-------+----------+---------------------------+-------------------------+------------------------+
Try this
WITH cte
AS (SELECT *
FROM (VALUES (1,10,NULL),
(2,15,NULL),
(3,17,NULL),
(4,NULL,14.00),
(5,NULL,15.33),
(6,NULL,15.44),
(7,NULL,14.93)) tc (month, act, fore))
SELECT mon,avg(res)
FROM cte a
CROSS apply (SELECT TOP 3 ( COALESCE(a.act, a.fore) ) AS res,
b.month AS mon
FROM cte b
WHERE a.month < b.month
ORDER BY a.month DESC) cs
GROUP BY mon
ORDER BY mon
or in Sql Server 2012+ use this
SELECT
[month]=[month]+1,
forecast=CAST(AVG(COALESCE(act,fore)) OVER (ORDER BY [month] ROWS BETWEEN 3 PRECEDING AND CURRENT row ) AS DECIMAL(28,2))
FROM
cte

Resources