SQL Double Pivot With Sum - sql

I have a dataset in SQL server management studio 17 that requires some pivoting to collapse multiple rows into one row for ease of reading and saving space. Per unique id there are a set of codes which act as operators on the values, with the intention of displaying the results in a total column at the end. I've performed a primary pivot with one set of codes, but it looks like I need to do a second one to do further collapsing/amalgamation.
Here is some unpivoted data
PO_NUM u_id CODE_1 CODE_2 VALUE
---------------------------------------
0M274316 1 3 9;13 150
0N274316 1 9 9 200000
0O274316 1 6 9 210000
0P274316 1 6 13 21000
0Q274316 1 6 9 50000.5
0R274316 1 15 9 0
0M274317 2 3 9;13 150
0N274317 2 9 9 300000
0O274317 2 6 9 220000
0P274317 2 6 13 22000
0Q274317 2 6 8 90000.5
0R274317 2 15 8 0
With a sample pivot with the first unique id as an example I get:
PO_NUM uid CODE_1 CODE_2 6 9 15
-----------------------------------------------------
0M274316 1 3 9;13 210000 200000 0
0M274316 1 9 21000 NULL NULL
0M274316 1 13 50000.5 NULL NULL
What I need to do is another pivot to have '9;13' as columns with sums being used with the values. I have this initial pivot command as:
SELECT PO_NUM, CODE_1, CODE_2, VALUE from table
Pivot (max(VALUE) for CODE_2 in ([6],[9],[15])) pvt1
Which merges the data as expected when I don't include the CODE_1 vals. But trying to add the second pivot with CODE_2 just further messes the data. How do I pivot a second time using the pivoted data from the first command?
Expected output would be:
PO_NUM uid 6 9 15 9;13 9(2) 13 total
------------------------------------------------------------------------
0M274316 1 281000.5 200000 0 150 null null 481150.5
Basically they rows have been collapsed into one with the codes being broken out into columns. In reality the columns will be renamed to avoid conflicts with names, therefore I used 9(2) just to show the data is still there.
Sorry about the edits.

If I understand correctly, just use conditional aggregation:
select po_num, u_id, code_2,
max(case when code_1 = 3 then value end) as [3],
max(case when code_1 = 6 then value end) as [6],
max(case when code_1 = 9 then value end) as [9],
max(case when code_1 = 15 then value end) as [15]
from t
group by po_num, u_id, code_2;
I think this would be expressed using pivot as:
SELECT PO_NUM, u_id, CODE_2, [3], [6], [9], [15]
from table
Pivot (max(VALUE) for CODE_2 in ([3], [6], [9], [15])) pvt1;

You can try this.
DECLARE #MyTable TABLE(PO_NUM VARCHAR(10), u_id int, CODE_1 varchar(10), CODE_2 varchar(10), VALUE DECIMAL(18,2))
INSERT INTO #MyTable VALUES
('0M274316', 1, '3', '9;13', 150),
('0N274316', 1, '9', '9', 200000),
('0O274316', 1, '6', '9', 210000),
('0P274316', 1, '6', '13', 21000),
('0Q274316', 1, '6', '9', 50000.5),
('0R274316', 1, '15', '9', 0),
('0M274317', 2, '3', '9;13', 150),
('0N274317', 2, '9', '9', 300000),
('0O274317', 2, '6', '9', 220000),
('0P274317', 2, '6', '13', 22000),
('0Q274317', 2, '6', '8', 90000.5),
('0R274317', 2, '15', '8', 0)
SELECT *, [6] + [9] + [15] + [9;13] + [9(2)] + [13] as total FROM (
SELECT PO_NUM,u_id, CODE + ( CASE WHEN DENSE_RANK() OVER( PARTITION BY CODE ORDER BY C ) > 1 THEN '(' + CAST ( C AS varchar(50)) + ')' ELSE '' END ) CODE, VALUE
FROM (
SELECT MIN(PO_NUM) OVER(PARTITION BY u_id) PO_NUM, u_id, 1 C, CODE_1 CODE, VALUE FROM #MyTable
UNION ALL
SELECT MIN(PO_NUM) OVER(PARTITION BY u_id) PO_NUM, u_id, 2 C, CODE_2 CODE, VALUE from #MyTable
) T
) SRC
PIVOT(SUM(VALUE) FOR CODE IN ( [6], [9], [15], [9;13], [9(2)], [13])) PVT
Result:
PO_NUM u_id 6 9 15 9;13 9(2) 13 total
---------- ----------- ------------ ----------- --------- --------- ----------- ----------- -------------
0M274316 1 281000.50 200000.00 0.00 150.00 460000.50 21000.00 962151.00
0M274317 2 332000.50 300000.00 0.00 150.00 520000.00 22000.00 1174150.50

Related

Convert Rows to columns with variable number [duplicate]

This question already has answers here:
Group by column and multiple Rows into One Row multiple columns
(2 answers)
Closed 6 months ago.
I have read stuff on pivot tables and I am still having problems getting this correct.
I have a table where column 1 is a Productnr, and column 2 is LHS, column 3 is another Productnr and lastly column 4 is RHS. Also the number of entries of Productnr2 (belonging to Productnr) is variable between 1 and 5. For example, if there are only 3 associated Productnr2 (see article no = 060013 in Productnr) then the last two columns should remain empty.
Productnr LHS Productnr2 RHS
060009 411 099088 5
060013 228 194139 25
060013 228 194141 17
060013 228 175823 75
060022 951 147071 90
060034 424 099088 14
060034 424 102704 88
060034 424 080034 82
060034 424 108436 87
060034 424 108437 58
I would like it to come out as a pivot table, like this:
Productnr | LHS | 1 | RHS | 2 | RHS | 3 | RHS | 4 | RHS | 5 | RHS |
-----
060009 411 099088 5
060013 228 194139 25 194141 17 175823 75
060022 951 147071 90
060034 424 099088 14 102704 88 080034 82 108436 87 108437 58
This can achieve by the following query. But this will work only for five different columns only, if more columns needed then need to increase the case count, but this is not recommended. Better try some dynamic queries for more columns.
CREATE TABLE #temp(Productnr INT, LHS INT, Productnr2 INT, RHS INT)
INSERT INTO #temp VALUES (060009,411,099088,5 )
INSERT INTO #temp VALUES (060013,228,194139,25)
INSERT INTO #temp VALUES (060013,228,194141,17)
INSERT INTO #temp VALUES (060013,228,175823,75)
INSERT INTO #temp VALUES (060022,951,147071,90)
INSERT INTO #temp VALUES (060034,424,099088,14)
INSERT INTO #temp VALUES (060034,424,102704,88)
INSERT INTO #temp VALUES (060034,424,080034,82)
INSERT INTO #temp VALUES (060034,424,108436,87)
INSERT INTO #temp VALUES (060034,424,108437,58)
;WITH CTE AS(
SELECT *
,ROW_NUMBER() OVER(PARTITION BY Productnr ORDER BY Productnr)RN
FROM #temp
)
SELECT Productnr,LHS
,MAX(CASE WHEN RN=1 THEN Productnr2 END) [1]
,MAX(CASE WHEN RN=1 THEN RHS END) [RHS]
,MAX(CASE WHEN RN=2 THEN Productnr2 END) [2]
,MAX(CASE WHEN RN=2 THEN RHS END) [RHS]
,MAX(CASE WHEN RN=3 THEN Productnr2 END) [3]
,MAX(CASE WHEN RN=3 THEN RHS END) [RHS]
,MAX(CASE WHEN RN=4 THEN Productnr2 END) [4]
,MAX(CASE WHEN RN=4 THEN RHS END) [RHS]
,MAX(CASE WHEN RN=5 THEN Productnr2 END) [5]
,MAX(CASE WHEN RN=5 THEN RHS END) [RHS]
FROM CTE
GROUP BY Productnr,LHS
DROP TABLE #temp
Output:
Productnr LHS 1 RHS 2 RHS 3 RHS 4 RHS 5 RHS
----------- ----------- ----------- ----------- ----------- ----------- ----------- ----------- ----------- ----------- ----------- -----------
60009 411 99088 5 NULL NULL NULL NULL NULL NULL NULL NULL
60013 228 194139 25 194141 17 175823 75 NULL NULL NULL NULL
60022 951 147071 90 NULL NULL NULL NULL NULL NULL NULL NULL
60034 424 99088 14 102704 88 80034 82 108436 87 108437 58

SQL- Cumulative sum based on condition

I have a scenario in which I have to calculate the counter based on below data. If the status is A, B,C than counter should be 0 which is working fine.
If STATUS is D counter should do a cumulative sum with the exception that if status is changed in between(like in 201907) , the counter should reset again and sum should start again with 1,2,3 and so on. Any possible help is appreciated on same.
Input - 3 columns - Customer_No, Date, Status
CUSTOMER_NO Date STATUS
1234 201901 A
1234 201902 B
1234 201903 C
1234 201904 D
1234 201905 D
1234 201906 D
1234 201907 C
1234 201908 D
1234 201910 D
1234 201911 D
1234 201912 D
expected Output - Input columns + Counter Column
CUSTOMER_NO Date STATUS COUNTER
----------------------------------------
1234 201901 A 0
1234 201902 B 0
1234 201903 C 0
1234 201904 D 1
1234 201905 D 2
1234 201906 D 3
1234 201907 C 0
1234 201908 D 1
1234 201910 D 2
1234 201911 D 3
1234 201912 D 4
Sample data
Thanks
You can create a numbering like a serial number for the ordering purpose using the ROW_NUMBER() function as shown below.
create table SampleData(CUSTOMER_NO int
, STATUS char(1)
, COUNTER int)
insert into SampleData Values
(1234, 'A', 0),
(1234, 'B', 0),
(1234, 'C', 0),
(1234, 'D', 1),
(1234, 'D', 2),
(1234, 'D', 3),
(1234, 'C', 0),
(1234, 'D', 1),
(1234, 'D', 2),
(1234, 'D', 3),
(1234, 'D', 4)
;with cte as(
Select *
, ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS RN
from SampleData
)
select CUSTOMER_NO
, STATUS
, COUNTER
, (SELECT SUM(case STATUS when 'D' then Counter else 0 end) FROM cte t2 WHERE t2.RN <= cte.RN) AS Needed
from cte
Live db<>fiddle demo.
This is a similar approach this Gordon's, however, uses CTEs and ROW_NUMBER to make the islands first, and then 0's if there is only 1 row in that island using a windowed COUNT and a CASE expression:
WITH Grps AS(
SELECT ID,
CUSTOMER_NO,
[STATUS],
ROW_NUMBER() OVER (PARTITION BY CUSTOMER_NO ORDER BY ID) -
ROW_NUMBER() OVER (PARTITION BY CUSTOMER_NO, [STATUS] ORDER BY ID) AS Grp
FROM (VALUES(1,1234,'A'),
(2,1234,'B'),
(3,1234,'C'),
(4,1234,'D'),
(5,1234,'D'),
(6,1234,'D'),
(7,1234,'C'),
(8,1234,'D'),
(9,1234,'D'),
(10,1234,'D'),
(11,1234,'D'))V(ID,CUSTOMER_NO,[STATUS]))
SELECT ID,
CUSTOMER_NO,
[STATUS],
Grp,
CASE WHEN COUNT(ID) OVER (PARTITION BY CUSTOMER_NO, [STATUS], Grp) = 1 THEN 0
ELSE ROW_NUMBER() OVER (PARTITION BY CUSTOMER_NO, [STATUS], Grp ORDER BY ID) - 1
END AS [COUNTER]
FROM Grps;
As Gordon mentioned, as well, if you don't have some kind of sequential ID/Key, you can't do this with your data. You will need to implement some kind of sequential ID, and hope that your data retains it's "insert order".
This is a variant of a gaps-and-islands problem. For this particular incarnation, you can identify the islands by counting the number of non-D statuses before a given row.
After identifying the groups, use case and row_number():
select t.*,
(case when status = 'D'
then row_number() over (partition by customer_no, grp, status order by date)
else 0
end) as counter
from (select t.*,
sum(case when status <> 'D' then 1 else 0 end) over (partition by customer_no order by date) as grp
from t
) t

SQL How do I transpose and group data into static columns? [duplicate]

This question already has answers here:
TSQL Pivot without aggregate function
(9 answers)
Closed 4 years ago.
I have a table with the following data:
UID LAST FIRST FUND AMOUNT STATUS
1 Smith John C 100 1
1 Smith John B 250 1
1 Smith John E 150 1
2 Jones Meg B 275 1
2 Jones Meg F 150 1
3 Carter Bill A 100 1
I would like to transpose the FUND, AMOUNT and STATUS values for each UID into a single row for each UID. The resulting table would have columns added for FUND_1, AMT_1, STATUS_1, FUND_2, AMT_2, STATUS_2, FUND_3, AMT_3, STATUS_3. Each UID may or may not have a total of 3 funds. If they do not, the remaining fund, amt, and status columns are left blank. The resulting table would appear as:
UID LAST FIRST FUND_1 AMT_1 STATUS_1 FUND_2 AMT_2 STATUS_2 FUND_3 AMT_3 STATUS_3
1 Smith John C 100 1 B 250 1 E 150 1
2 Jones Meg B 275 1 F 150 1
3 Carter Bill A 100 1
For clarification, this is how the data would move from the existing table to the resulting table for UID 1:
It seems I am unable to use PIVOT because FUND_1, FUND_2, FUND_3 will be different fund categories for each person. The question, TSQL Pivot without aggregate function helps but doesn't answer my question since I have multiple rows in what would be the the DBColumnName in that question.
This is a pretty common conditional aggregation. Notice how I posted consumable data as a table and insert statements. To be honest it took longer to do that portion than the actual code to select the data. You should do this in the future. Also you should avoid using keywords as column names.
declare #Something table
(
UID int
, LAST varchar(10)
, FIRST varchar(10)
, FUND char(1)
, AMOUNT int
, STATUS int
)
insert #Something values
(1, 'Smith', 'John', 'C', 100, 1)
, (1, 'Smith', 'John', 'B', 250, 1)
, (1, 'Smith', 'John', 'E', 150, 1)
, (2, 'Jones', 'Meg', 'B', 275, 1)
, (2, 'Jones', 'Meg', 'F', 150, 1)
, (3, 'Carter', 'Bill', 'A', 100, 1)
;
with SortedValues as
(
select *
, RowNum = ROW_NUMBER() over(partition by UID order by (select null))
from #Something
)
select UID
, Last
, First
, Fund_1 = max(case when RowNum = 1 then Fund end)
, Amt_1 = max(case when RowNum = 1 then Amount end)
, Status_1 = max(case when RowNum = 1 then Status end)
, Fund_2 = max(case when RowNum = 2 then Fund end)
, Amt_2 = max(case when RowNum = 2 then Amount end)
, Status_2 = max(case when RowNum = 2 then Status end)
, Fund_3 = max(case when RowNum = 3 then Fund end)
, Amt_3 = max(case when RowNum = 3 then Amount end)
, Status_3 = max(case when RowNum = 3 then Status end)
from SortedValues
group by UID
, Last
, First
order by UID
, Last
, First

SQL Server : data between specific range

I have a data which is something like this
stories value
--------------------------
0 2194940472.78964
1 1651820586.1447
2 627935051.75
3 586994698.4272
4 89132137.57
5 134608008
6 40759564
7 0
8 0
10 0
11 0
12 0
13 26060602
17 0
18 0
19 84522335
20 316478066.045
24 0
I want to sum it up as per the range
Output which I am expected
stories value
0-3 125201021
4-7 215453123
8-12 453121545
12-max(numstories) 21354322
I tried this but not able to figure it out what is wrong
select t.NumStories, SUM(t.bldnvalue)
from
(select
a.NumStories,
case
when a.NumStories between 0 and 3 then sum(a.BldgValue)
when a.NumStories between 4 and 7 then sum(a.BldgValue)
when a.NumStories between 8 and 12 then sum(a.BldgValue)
when a.NumStories between 13 and max(a.NumStories) then sum(a.BldgValue)
end as bldnvalue
from
dbo.EDM_CocaCola_Coca_Cola_Company_1_1 a
group by
a.NumStories) t
group by
t.NumStories
With this query I am getting this output
NumStories value
-------------------------------
0 2194940472.78964
3 586994698.4272
12 0
6 40759564
7 0
1 1651820586.1447
24 0
18 0
10 0
4 89132137.57
19 84522335
13 26060602
5 134608008
2 627935051.75
17 0
11 0
20 316478066.045
8 0
I like this result, I tried to use the BIN concept. I think the only issue would be with your max bin. I don't understand how you got your output sums. the first records value is '2,194,940,472.78964' which is bigger than your value in 0-3 bin
if OBJECT_ID('tempdb..#Test') is not null
drop table #Test;
Create table #Test (
Stories int
, Value float
)
insert into #Test
values
(0 , 2194940472.78964)
, (1 , 1651820586.1447 )
, (2 , 627935051.75 )
, (3 , 586994698.4272 )
, (4 , 89132137.57 )
, (5 , 134608008 )
, (6 , 40759564 )
, (7 , 0 )
, (8 , 0 )
, (10, 0 )
, (11, 0 )
, (12, 0 )
, (13, 26060602 )
, (17, 0 )
, (18, 0 )
, (19, 84522335 )
, (20, 316478066.045 )
, (24, 0 )
if OBJECT_ID('tempdb..#Bins') is not null
drop table #Bins;
create Table #Bins(
Label varchar(20)
, Min int
, Max int
)
insert into #Bins values
('0-3', 0, 3)
, ('4-7', 4, 7)
, ('8-12', 8, 12)
, ('13 - Max', 13, 999999999)
Select b.Label
, sum(t.Value) as Value
from #Test t
join #Bins b
on t.stories between b.Min and b.Max
Group by b.Label
order by 1
Output:
Label Value
-------------------- ----------------------
0-3 5061690809.11154
13 - Max 427061003.045
4-7 264499709.57
8-12 0
Just build the grouping string first that you want and group by that variable.
select
case
when a.NumStories between 0 and 3 then '0-3'
when a.NumStories between 4 and 7 then '4-7'
when a.NumStories between 8 and 12 then '8-12'
when a.NumStories >= 13 then '13-max'
end as stories,
sum(a.BldgValue) as value
from
dbo.EDM_CocaCola_Coca_Cola_Company_1_1 a
group by 1;
If you really want to print the max too, then you can put in a subquery in the "13-max" line as (SELECT MAX(BldgValue) FROM dbo.EDM_CocaCola_Coca_Cola_Company_1_1)
You can try this:
SELECT '0-3' AS stories,
SUM(value) AS value
FROM dbo.EDM_CocaCola_Coca_Cola_Company_1_1
WHERE stories BETWEEN 0 AND 3
UNION ALL
SELECT '4-7' AS stories,
SUM(value) AS value
FROM dbo.EDM_CocaCola_Coca_Cola_Company_1_1
WHERE stories BETWEEN 4 AND 7
UNION ALL
...
Here is solution with CTE that should work for any data set, without copying the code.
declare #YourTable table(stories int, value money)
declare #GroupMemberCount int=4
insert #YourTable (stories,value) values (0,5),(1,10),(2,11),(3,7),(4,18),(5,13),(7,15)
;with cte as
(
select c.stories+v.i*#GroupMemberCount FirstGroupMember, c.stories+v.i*#GroupMemberCount+#GroupMemberCount -1 LastGroupMember
,CAST(c.stories+v.i*#GroupMemberCount as varchar(50))
+'-'+CAST(c.stories+v.i*#GroupMemberCount+#GroupMemberCount -1 as varchar(50))GroupName
from (select MIN(stories) stories from #YourTable) c
cross join (values (0),(1),(2),(3),(4)/* and so on */) v(i)
where exists (select * from #YourTable yt where yt.stories>=c.stories+v.i*3)
)
select c.GroupName, SUM(yt.value)
from cte c
JOIN #YourTable yt ON yt.stories BETWEEN c.FirstGroupMember AND C.LastGroupMember
GROUP BY c.GroupName

PIVOT values from two columns to multiple columns

Table: Sample
ID Day Status MS
----------------------------
1 1 0 10
1 2 0 20
1 3 1 15
2 3 1 3
2 30 0 5
2 31 0 6
Expected Result:
ID Day1 Day2 Day3....Day30 Day31 Status1 Status2 Status3...Status30 Status31
---------------------------------------------------------------------------------------
1 10 20 15 NULL NULL 0 0 1 NULL NULL
2 NULL NULL 3 5 6 NULL NULL 1 0 0
I want to get the MS and Status value for each day from 1 to 31 for each ID.
I have used PIVOT to get the below result.
Result:
ID Day1 Day2 Day3....Day30 Day31
-------------------------------------
1 10 20 15 NULL NULL
2 NULL NULL 3 5 6
Query:
SELECT
ID
,[1] AS Day1
,[2] AS Day2
,[3] AS Day3
.
.
.
,[30] AS Day30
,[31] AS Day31
FROM
(
SELECT
ID
,[Day]
,MS
FROM
Sample
) AS A
PIVOT
(
MIN(MS)
FOR [Day] IN([1],[2],[3],...[30],[31])
) AS pvtTable
How can I merge the Status column with the result?.
Try this. Use Another Pivot to transpose Status column. Then use aggregate (Max or Min) in select column list with group by Id to get the Result.
CREATE TABLE #est
(ID INT,[Day] INT,[Status] INT,MS INT)
INSERT #est
VALUES (1,1,0,10),(1,2,0,20),(1,3,1,15 ),
(2,3,1,3),(2,30,0,5),(2,31,0,6)
SELECT ID,
Max([Day1]) [Day1],
Max([Day2]) [Day2],
Max([Day3]) [Day3],
Max([Day30]) [Day30],
Max([Day31]) [Day31],
Max([status1]) [status1],
Max([status2]) [status2],
Max([status3]) [status3],
Max([status30])[status30],
Max([status31])[status31]
FROM (SELECT Id,
'status' + CONVERT(VARCHAR(30), Day) col_stat,
'Day' + CONVERT(VARCHAR(30), Day) Col_Day,
[status],
ms
FROM #est) a
PIVOT (Min([ms])
FOR Col_Day IN([Day1],[Day2],[Day3],[Day30],[Day31])) piv
PIVOT (Min([status]) FOR col_stat IN ([status1],[status2],[status3],[status30],[status31])) piv1
GROUP BY id