I have the following CTE that does what I want it to do.
;WITH numbering AS
(
SELECT SrcID, AsOfDate, PID,
dense_rank() OVER (PARTITION BY SrcID ORDER BY PID) AS rowno
FROM RAW_DATA
)
SELECT SrcID,
MAX(CASE rowno WHEN 1 THEN PID END) AS PID1,
MAX(CASE rowno WHEN 2 THEN PID END) AS PID2,
MAX(CASE rowno WHEN 3 THEN PID END) AS PID3,
MAX(CASE rowno WHEN 4 THEN PID END) AS PID4
FROM numbering
GROUP BY SrcID
I need to use that, as well as the AsOfDate and PID, but I don’t want to display these in the CTE, as that throws off populating PID1, PID2, PID3, and PID4 . . . all of which is correct now. I do need the SrcID and AsOfDate to do an update to another table, named ‘RAW_DATA’. How can I run the CTE to generate the specific data set that I need, and then do an update to the RAW_DATA table, based on joins between SrcID and AsOfDate?
I think it should be something like this:
;WITH numbering AS
(
SELECT SrcID, AsOfDate, PID,
dense_rank() OVER (PARTITION BY SrcID ORDER BY PID) AS rowno
FROM RAW_DATA
)
SELECT SrcID,
MAX(CASE rowno WHEN 1 THEN PID END) AS PID1,
MAX(CASE rowno WHEN 2 THEN PID END) AS PID2,
MAX(CASE rowno WHEN 3 THEN PID END) AS PID3,
MAX(CASE rowno WHEN 4 THEN PID END) AS PID4
FROM numbering
GROUP BY SrcID
INSERT INTO RAW_DATA(SrcID, AsOfDate, PID, PID1, PID2, PID3, PID4)
Select *
FROM RAW_DATA INNER JOIN numbering
ON RAW_DATA.SrcID = numbering.SrcID
AND RAW_DATA.AsofDate = numbering.AsofDate
However, that throws this error: Invalid object name 'numbering'. I am on SQL Server 2008.
Update
Modifying my original post just a bit here.
Jeffrey, I'm testing your solution:
--drop table Count_Unique_PID
;WITH numbering AS
(
SELECT SrcID, AsOfDate, PID,PID1,PID2,PID3,PID4,
dense_rank() OVER (PARTITION BY AsOfDate, SrcID ORDER BY PID) AS rowno
FROM RAW_DATA
)
SELECT SrcID,AsOfDate, PID,
MAX(CASE rowno WHEN 1 THEN PID END) AS PID1,
MAX(CASE rowno WHEN 2 THEN PID END) AS PID2,
MAX(CASE rowno WHEN 3 THEN PID END) AS PID3,
MAX(CASE rowno WHEN 4 THEN PID END) AS PID4
INTO Count_Unique_PID
FROM numbering
GROUP BY SrcID,AsOfDate, PID
SELECT SrcID,
AsOfDate,
PID,
PID1,
PID2,
PID3,
PID4
FROM Count_Unique_PID
GROUP BY SrcID,AsOfDate, PID,PID1,PID2,PID3,PID4
UPDATE RAW_DATA
SET PID1 = B.PID1,
PID2 = B.PID2,
PID3 = B.PID3,
PID4 = B.PID4
FROM RAW_DATA AS A INNER JOIN Count_Unique_PID As B
ON A.SrcID = B.SrcID
AND A.AsofDate = B.AsofDate
This runs, but it blows up my rows from 357,518 to 724,150. The number of records should stay the same; it should remain 357,518 after the Update is done...something is still not quite right here. Maybe I am missing a Group By somewhere, or something like that. I don't see what the actual problem is. Any additional thoughts on this?
CTEs can only be referenced by the immediately-following statement. If you need the results later, you could insert the CTE into a temp table, then select from that temp table and then insert from that temp table:
;WITH numbering AS
(
SELECT SrcID, AsOfDate, PID,
dense_rank() OVER (PARTITION BY SrcID ORDER BY PID) AS rowno
FROM RAW_DATA
)
SELECT SrcID,
MAX(CASE rowno WHEN 1 THEN PID END) AS PID1,
MAX(CASE rowno WHEN 2 THEN PID END) AS PID2,
MAX(CASE rowno WHEN 3 THEN PID END) AS PID3,
MAX(CASE rowno WHEN 4 THEN PID END) AS PID4
INTO #tmp
FROM numbering
GROUP BY SrcID
SELECT SrcID,
PID1,
PID2,
PID3,
PID4
FROM #tmp
GROUP BY SrcID
INSERT INTO RAW_DATA(SrcID, AsOfDate, PID, PID1, PID2, PID3, PID4)
Select *
FROM RAW_DATA INNER JOIN #tmp
ON RAW_DATA.SrcID = #tmp.SrcID
AND RAW_DATA.AsofDate = #tmp.AsofDate
You need encapsulate your query as a second cte:
sintaxis is
WITH cte1 as (
SELECT ...
), cte2 as (
SELECT *
FROM cte1
.....
)
INSERT INTO table_name
SELECT *
FROM cte2
Your SELECT statement is doing nothing for the insert (maybe it's for validation). Simply remove the select statement and your insert should work.
;WITH numbering AS
(
SELECT SrcID, AsOfDate, PID,
dense_rank() OVER (PARTITION BY SrcID ORDER BY PID) AS rowno
FROM RAW_DATA
)
INSERT INTO RAW_DATA(SrcID, AsOfDate, PID, PID1, PID2, PID3, PID4)
Select *
FROM
RAW_DATA INNER JOIN numbering
ON RAW_DATA.SrcID = numbering.SrcID
AND RAW_DATA.AsofDate = numbering.AsofDate
If you need to run a select on the CTE as well, you should use a temp table to it will exist as long as the query is running.
Related
Query #1:
SELECT
id,
SUM(spent_time) AS spent_time_01,
COUNT(vpno) AS spend_time_cnt_01
FROM
(SELECT
LEAD(create_ts) OVER (PARTITION BY id ORDER BY CAST(vpno AS INT)) AS lead,
DATEDIFF('second', create_ts::timestamp, LEAD::timestamp) AS spent_time,
*
FROM
table_1) A
WHERE
item_category = 'A'
AND pid IS NOT NULL
GROUP BY
id
Query #2:
SELECT
id,
SUM(spent_time) AS spent_time_02,
COUNT(vpno) AS spend_time_cnt_02
FROM
(SELECT
LEAD(create_ts) OVER (PARTITION BY id ORDER BY CAST(vpno AS INT)) AS lead,
DATEDIFF('second', create_ts::timestamp, LEAD::timestamp) AS spent_time,
*
FROM
table_1) A
WHERE
item_category = 'B'
AND pid IS NOT NULL
GROUP BY
id
I tried the below but I'm not getting the correct results. I think I'm missing something in the query.
SUM(CASE WHEN pid IS NOT NULL
AND vpno IS NOT NULL
AND item_category = 'A' THEN spent_time END) AS spent_time_01
,COUNT_IF(vpno IS NOT NULL
AND item_category = 'A'
AND pid IS NOT NULL) AS spend_time_cnt
Someone kindly guide me on this.
Common part to be turn into CTE and item_category moved as conditional aggregation:
WITH cte AS (
SELECT *,
LEAD(create_ts) OVER (PARTITION BY id ORDER BY vpno::INT) AS lead,
DATEDIFF('second', create_ts::timestamp, LEAD::timestamp) AS spent_time
FROM table_1
)
SELECT id,
SUM(CASE WHEN item_category = 'A' THEN spent_time END) AS spent_time_01,
COUNT(CASE WHEN item_category = 'A' THEN vpno END) AS spend_time_cnt_01,
SUM(CASE WHEN item_category = 'B' THEN spent_time END) AS spent_time_02,
COUNT(CASE WHEN item_category = 'B' THEN vpno END) AS spend_time_cnt_02
FROM cte
WHERE pid IS NOT NULL
AND item_category IN ('A', 'B')
GROUP BY id;
Well if we are trying for Code Golf (smaller code) Lukasz answer can be rewritten a couple line less, and smaller by moving the lead value into the only place it is used, and not have a CTE as it's only used once, and just have it as a subselect. Moving the WHERE filters into the select, and swapping from CASE to IFF. But these are points I have made before..
Also by only asking for the columns you want in the sub-select, the compile can be faster on cold meta data. but those are details that don't matter here.
SELECT id,
SUM(iff(item_category = 'A', spent_time, null)) AS spent_time_01,
COUNT(iff(item_category = 'A', vpno, null)) AS spend_time_cnt_01,
SUM(iff(item_category = 'B', spent_time, null)) AS spent_time_02,
COUNT(iff(item_category = 'B', vpno, null)) AS spend_time_cnt_02
FROM (
SELECT id, item_category,
DATEDIFF('second', create_ts::timestamp, (LEAD(create_ts) OVER (PARTITION BY id ORDER BY vpno::INT))::timestamp) AS spent_time
FROM table_1
WHERE pid IS NOT NULL
AND item_category IN ('A', 'B')
)
GROUP BY id;
But I do tend to prefer CTE for readability and I don't like over long lines, so even I will give my points vote to Lukasz's answer.
I want to get the MIN and MAX from a certain values and put them in columns beside each other. Here's my query but I don't know how to transpose the values...
SELECT *
, MIN([CID]) OVER (PARTITION BY [TID] ORDER BY [TID]) MinID
, MAX([CID]) OVER (PARTITION BY [TID] ORDER BY [TID]) MaxID
Given:
TID CID DATE
123456789 1 01JAN
123456789 2 02JAN
123456789 3 03JAN
123456789 4 04JAN
Result:
TID CID DATE MIN MAX DATEMIN DATEMAX
123456789 1 01JAN 1 4 01JAN 04JAN
Isn't simple aggregation good enough here?
select
tid,
min(cid) min_cid,
max(cid) max_cid,
min(date) min_date,
max(date) max_date
from mytable
group by tid
Or, if the cids and dates are not changing accordingly, you can use conditional aggregation:
select
tid,
max(case when rn_asc = 1 then cid end) cid_at_min_date,
max(case when rn_desc = 1 then cid end) cid_at_max_date,
min(date) min_date,
max(date) max_date
from (
select
t.*,
row_number() over(partition by tid order by cdate asc ) rn_asc,
row_number() over(partition by tid order by cdate desc) rn_desc
from mytable t
) t
where 1 in (rn_asc, rn_desc)
group by tid
This orders records by cdate, and gives you the cids that correspond to the minimum and maximum date. You can easily adapt the query if you want things the other way around (basically, switch cid and cdate).
I'm trying to create a query that will distribute the row results in specific number of columns (ex. 4). See below screenshot.
It is presentation matter and it should be handled on application level.
But if you insist:
SELECT column1 = MIN(CASE WHEN grp=1 THEN PartNumber+CHAR(13)+SerialNumber END)
,column2 = MIN(CASE WHEN grp=2 THEN PartNumber+CHAR(13)+SerialNumber END)
,column3 = MIN(CASE WHEN grp=3 THEN PartNumber+CHAR(13)+SerialNumber END)
,column4 = MIN(CASE WHEN grp=4 THEN PartNumber+CHAR(13)+SerialNumber END)
FROM (SELECT *,ROW_NUMBER() OVER(PARTITION BY rn ORDER BY rn) AS grp
FROM (SELECT *,(ROW_NUMBER() OVER(ORDER BY 1/0)-1)/4 AS rn FROM tab)s)sub
GROUP BY rn;
DBFiddle Demo
I am writing a script that will run on SQL Server 2014.
I have a table of transactions recording transfers from one work center to another. The simplified table is below:
DECLARE #transactionTable TABLE (wono varchar(10),transferDate date
,fromWC varchar(10),toWC varchar(10),qty float)
INSERT INTO #transactionTable
SELECT '0000000123','5/10/2018','STAG','PP-B',10
UNION
SELECT '0000000123','5/11/2018','PP-B','PP-T',5
UNION
SELECT '0000000123','5/11/2018','PP-T','TEST',3
UNION
SELECT '0000000123','5/12/2018','PP-B','PP-T',5
UNION
SELECT '0000000123','5/12/2018','PP-T','TEST',5
UNION
SELECT '0000000123','5/13/2018','PP-T','TEST',2
UNION
SELECT '0000000123','5/13/2018','TEST','FGI',8
UNION
SELECT '0000000123','5/14/2018','TEST','FGI',2
SELECT *,
fromTotal = -SUM(qty) OVER(PARTITION BY fromWC ORDER BY wono, transferdate, fromWC),
toTotal = SUM(qty) OVER(PARTITION BY toWC ORDER BY wono, transferdate, toWC)
FROM #transactionTable
ORDER BY wono, transferDate, fromWC
I want to get a running balance of the fromWC and toWC after each transaction.
Given the records above, the end result should be this:
I believe it is possible to use SUM(qty) OVER(PARTITION BY..., but I am not sure how to write the statement. When I try to get the increase and decrease, each line always results in 0.
How do I write the SUM statement to achieve the desired results?
UPDATE
This image shows each transaction, the resulting WC qty, and highlights the corresponding from and to work centers for each transaction.
For example, looking at the second record on 5/11, 3 were transferred from PP-T to TEST. After the transaction, there were 5 in PP-B, 2 in PP-T, and 3 in TEST.
I can get close, except starting balances:
SELECT wono, transferDate, fromWC, toWC, qty,
SUM( CASE WHEN WC = fromWC THEN RunningTotal ELSE 0 END ) AS FromQTY,
SUM( CASE WHEN WC = toWC THEN RunningTotal ELSE 0 END ) AS ToQTY
FROM( -- b
SELECT *, SUM(Newqty) OVER(PARTITION BY WC ORDER BY wono,transferdate, fromWC, toWC) AS RunningTotal
FROM(-- a
SELECT wono, transferDate, fromWC, toWC, fromWC AS WC, qty, -qty AS Newqty, 'From' AS RecType
FROM #transactionTable
UNION ALL
SELECT wono, transferDate, fromWC, toWC, toWC AS WC, qty, qty AS Newqty, 'To' AS RecType
FROM #transactionTable
) AS a
) AS b
GROUP BY wono, transferDate, fromWC, toWC, qty
My logic assumes that all balances start at 0, therefore "STAG" balance will be -10.
How the query works:
"Unpivot" the input record set into "From" and "To" records with quantities negated for "From" records.
Calculate running totals for each "WC".
Combine "Unpivoted" records back into original shape
Solution 2
WITH CTE
AS(
SELECT *,
ROW_NUMBER() OVER( ORDER BY wono, transferDate, fromWC, toWC ) AS Sequence
FROM #transactionTable
),
CTE2
AS(
SELECT *,
fromTotal = -SUM(qty) OVER(PARTITION BY fromWC ORDER BY Sequence),
toTotal = SUM(qty) OVER(PARTITION BY toWC ORDER BY Sequence)
FROM CTE
)
SELECT a.Sequence, b.Sequence, c.Sequence, a.wono, a.transferDate, a.fromWC, a.toWC, a.qty, a.fromTotal + ISNULL( b.toTotal, 0 ) AS FromTotal, a.toTotal + ISNULL( c.fromTotal, 0 ) AS ToTotal
FROM CTE2 AS a
OUTER APPLY( SELECT TOP 1 * FROM CTE2 WHERE wono = a.wono AND Sequence < a.Sequence AND toWC = a.fromWC ORDER BY Sequence DESC ) AS b
OUTER APPLY( SELECT TOP 1 * FROM CTE2 WHERE wono = a.wono AND Sequence < a.Sequence AND fromWC = a.toWC ORDER BY Sequence DESC ) AS c
ORDER BY a.Sequence
Note: This solution would benefit greatly from an "ID" column, that mirrors transaction order OR at least you will need an index on wono, transferDate, fromWC, toWC
I have a table as
CREATE TABLE #FinalRates
(
id int primary key identity(1,1),
RateDesc nvarchar(50),
Amt decimal(18,2)
)
insert into #FinalRates values('100',200)
insert into #FinalRates values('100',300)
insert into #FinalRates values('50-80',100)
insert into #FinalRates values('50-80',300)
insert into #FinalRates values('30-50',500)
insert into #FinalRates values('30-50',250)
Looking for an output as
RateDesc Amount1 Amount2
100 200 300
50-80 100 300
30-50 500 250
I have done this as
;with cte as(
select
RateDesc
,Amounts=
STUFF((Select ','+ cast(cast(Amt as int) as varchar(10))
from #FinalRates T1
where T1.RateDesc=T2.RateDesc
FOR XML PATH('')),1,1,'')
from #FinalRates T2
group by T2.RateDesc
)
select
RateDesc,
Amount1 = PARSENAME(REPLACE(Amounts,',','.'),2),
Amount2 = PARSENAME(REPLACE(Amounts,',','.'),1)
From Cte
Drop table #FinalRates
Can the same be done using PIVOT?
That's so complicated. How about this?
select ratedesc,
max(case when seqnum = 1 then amt end) as Amount1,
max(case when seqnum = 2 then amt end) as Amount2
from (select ft.*,
row_number() over (partition by ratedesc order by id) as seqnum
from #finalrates fr
) fr
group by ratedesc;
You could use a similar approach using pivot but conditional aggregation often performs better.
Plus, if you know you have no holes in id, you can do:
select ratedesc,
max(case when id % 2 = 1 then amt end) as Amount1,
max(case when id % 2 = 0 then amt end) as Amount2
from #finalrates fr
group by ratedesc;
Using PIVOT,
Assuming you have 2 Amt for each RateDesc.
Select RateDesc, [Amount1], [Amount2] From
(
Select RateDesc, Amt
, 'Amount' + cast(row_number() over (partition by RateDesc order by Amt) as varchar(5)) RowVal
from #FinalRates
) x
PIVOT
(
MAX(Amt) For RowVal in ([Amount1], [Amount2])
) p