SQL - Combine multiple rows into single row with dynamic number of columns - sql

I'm trying to setup a query that selects multiple rows from a table and combines similar rows into a single row with multiple columns. I believe I can do this with pivot however each row won't have the same number of columns and that's where I'm running into problems. I gave an example below of what I mean.
This:
Account Period Amount
01 0001 1111
01 0002 2222
01 0003 3333
02 0001 1111
03 0001 1111
04 0001 1111
04 0002 2222
Should be come this:
Account 0001 0002 0003
01 1111 2222 3333
02 1111
03 1111
04 1111 2222
Here is my initial query that's pulling all the data together:
WITH CTE AS(
SELECT
a.Period, a.Account, SUM(a.Amount) Amount
FROM
LedgerAP a
WHERE
a.Period >= 201500
GROUP BY a.Period, a.Account
UNION
SELECT
b.Period, b.Account, SUM(b.Amount) Amount
FROM
LedgerAR b
WHERE
b.Period >= 201500
GROUP BY b.Period, b.Account
UNION
SELECT
c.Period, c.Account, SUM(c.Amount)
FROM
LedgerEx c
WHERE
c.Period >= 201500
GROUP BY c.Period, c.Account
UNION
SELECT
d.Period, d.Account, SUM(d.Amount)
FROM
LedgerMisc d
WHERE
d.Period >= 201500
GROUP BY d.Period, d.Account
)
SELECT account,
max(case when period = #Budgetyear + '01' then SUM(amount) end) Amount1,
max(case when period = #Budgetyear + '02' then SUM(amount) end) Amount2,
max(case when period = #Budgetyear + '03' then SUM(amount) end) Amount3,
max(case when period = #Budgetyear + '04' then SUM(amount) end) Amount4,
max(case when period = #Budgetyear + '05' then SUM(amount) end) Amount5,
max(case when period = #Budgetyear + '06' then SUM(amount) end) Amount6,
max(case when period = #Budgetyear + '07' then SUM(amount) end) Amount7,
max(case when period = #Budgetyear + '08' then SUM(amount) end) Amount8,
max(case when period = #Budgetyear + '09' then SUM(amount) end) Amount9,
max(case when period = #Budgetyear + '10' then SUM(amount) end) Amount10,
max(case when period = #Budgetyear + '11' then SUM(amount) end) Amount11,
max(case when period = #Budgetyear + '12' then SUM(amount) end) Amount12
FROM CTE
GROUP BY account
ORDER BY account ASC
Now how can I go about organizing this like I have shown above? Any help would be amazing!

Credit to #Bluefeet's solution here, you can build up dynamic pivot something like this:
create table table1 (Account varchar(2), Period varchar(4), Amount int)
insert into table1 values
('01', '0001', 1111),
('01', '0002', 2222),
('01', '0003', 3333),
('02', '0001', 1111),
('03', '0001', 1111),
('04', '0001', 1111),
('04', '0002', 2222);
Dynamic Query:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(t.period)
FROM table1 t
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT Account, ' + #cols + ' from
(
select Account
, Period
, Amount
from table1
) x
pivot
(
max(amount)
for period in (' + #cols + ')
) p '
execute(#query)
GO
Result:
+---------+------+--------+--------+
| Account | 0001 | 0002 | 0003 |
+---------+------+--------+--------+
| 01 | 1111 | 2222 | 3333 |
| 02 | 1111 | (null) | (null) |
| 03 | 1111 | (null) | (null) |
| 04 | 1111 | 2222 | (null) |
+---------+------+--------+--------+
SQL Fiddle Demo

Your basic pivot:
SELECT
SUM(case Period when '0001' then Amount end) as '0001',
SUM(case Period when '0002' then Amount end) as '0002',
SUM(case Period when '0003' then Amount end) as '0003'
FROM LedgerAP
GROUP BY Account
To create that query dynamically, which is helpful if you have many values for Period:
DECLARE #SQL varchar(max) = 'SELECT '
;WITH Periods AS
(
SELECT DISTINCT Period
FROM LedgerAP
)
SELECT #SQL = #SQL +
'SUM(case Period when ''' + Period + ''' then Amount end) as ''' + Period + ''','
SET #SQL = LEFT(#SQL,LEN(#SQL) - 1) + ' FROM LedgerAP GROUP BY Account'
EXEC(#SQL)

Related

Pivoting unique users for each month, each year

I'm learning about PIVOT function and I want to try it in my DB, in the table DDOT I have events (rows) made by users during X month Y year in the YYYYMM format.
id_ev iddate id_user ...
------------------------
1 201901 321
2 201902 654
3 201903 987
4 201901 321
5 201903 987
I'm basing my query on the MS Documentation and I'm not getting errors but I'm not able to fill it with the SUM of those unique events (users). In simple words I want to know how many users (unique) checked up each month (x axis) in the year (y axis). However, I'm getting NULL as result
YYYY jan feb mar
----------------------------
2019 NULL NULL NULL
I'm expecting a full table with what I mentionted before.
YYYY jan feb mar
----------------------------
2019 2 1 1
In the code I've tried with different aggregate functions but this block is the closest to a result from SQL.
CREATE TABLE ddot
(
id_ev int NOT NULL ,
iddate int NOT NULL ,
id_user int NOT NULL
);
INSERT INTO DDOT
(
[id_ev], [iddate], [id_user]
)
VALUES
(
1, 201901, 321
),
(
2, 201902, 654
),
(
3, 201903, 987
),
(
4, 201901, 321
),
(
5, 201903, 987
)
GO
SELECT *
FROM (
SELECT COUNT(DISTINCT id_user) [TOT],
DATENAME(YEAR, CAST(iddate+'01' AS DATETIME)) [YYYY], --concat iddate 01 to get full date
DATENAME(MONTH, CAST(iddate+'01' AS DATETIME)) [MMM]
FROM DDOT
GROUP BY DATENAME(YEAR, CAST(iddate+'01' AS DATETIME)),
DATENAME(MONTH, CAST(iddate+'01' AS DATETIME))
) AS DOT_COUNT
PIVOT(
SUM([TOT])
FOR MMM IN (jan, feb, mar)
) AS PVT
Ideally you should be using an actual date in the iddate column, and not a string (number?). We can workaround this using the string functions:
SELECT
CONVERT(varchar(4), LEFT(iddate, 4)) AS YYYY,
COUNT(CASE WHEN CONVERT(varchar(2), RIGHT(iddate, 2)) = '01' THEN 1 END) AS jan,
COUNT(CASE WHEN CONVERT(varchar(2), RIGHT(iddate, 2)) = '02' THEN 1 END) AS feb,
COUNT(CASE WHEN CONVERT(varchar(2), RIGHT(iddate, 2)) = '03' THEN 1 END) AS mar,
...
FROM DDOT
GROUP BY
CONVERT(varchar(4), LEFT(iddate, 4));
Note that if the iddate column already be text, then we can remove all the ugly calls to CONVERT above:
SELECT
LEFT(iddate, 4) AS YYYY,
COUNT(CASE WHEN RIGHT(iddate, 2) = '01' THEN 1 END) AS jan,
COUNT(CASE WHEN RIGHT(iddate, 2) = '02' THEN 1 END) AS feb,
COUNT(CASE WHEN RIGHT(iddate, 2) = '03' THEN 1 END) AS mar,
...
FROM DDOT
GROUP BY
LEFT(iddate, 4);

How to convert columns to rows in SQL Server without pivot and value is dynamic

I have some values in rows like :
Month | Product | SalesQty
-------+---------+---------
Jan-17 | ABC | 3
Feb-17 | ABC | 6
Apr-17 | ABC | 19
But i want to show the some values in columns like:
Model| Apr-17 | May-17 | Jun-17 | Jul-17
ABC 1 2 12 0
BCS 212 12 12 112
Months must be generated dynamically. Static month will not help me in this situation.
Why not Use pivot? it is simpler than other solutions like case expression:
SELECT *
FROM table
PIVOT
(
SUM(SalesQty)
FOR Month IN([Apr-17] ,[May-17], [Jun-17], [Jul-17])
) AS p;
To do it dynamically you can use the same query with dynamic sql like this:
DECLARE #cols AS NVARCHAR(MAX);
DECLARE #query AS NVARCHAR(MAX);
select #cols = STUFF((SELECT distinct ',' +QUOTENAME(CONCAT(LEFT(datename(month, Month), 3),
CAST(DATEPART(day, month) AS NVARCHAR(2))))
FROM table1
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
, 1, 1, '');
SELECT #query = ' SELECT *
FROM
(
SELECT product, SalesQty,
CONCAT(LEFT(datename(month, Month), 3),
CAST(DATEPART(day, month) AS NVARCHAR(2))) AS Month
FROM table1
) AS t
PIVOT
(
SUM(SalesQty)
FOR Month IN( ' + #cols + ' )
) AS p';
execute(#query);
dynamic demo
If you don't want to use PIVOT then you can use CASE expression like this:
SELECT product,
SUM(CASE WHEN month = 'Jan17' THEN SalesQty ELSE 0 END) AS Jan17,
SUM(CASE WHEN month = 'Jan17' THEN SalesQty ELSE 0 END) AS Jun17,
SUM(CASE WHEN month = 'Jan17' THEN SalesQty ELSE 0 END) AS Jul17
FROM
(
SELECT product, SalesQty,
CONCAT(LEFT(datename(month, Month), 3),
CAST(DATEPART(day, month) AS NVARCHAR(2))) AS Month
FROM table1
) AS t
GROUP BY Product;
Then to do this dynamically, you just need to replace the case expression part to by dynamic in the cols names variable.

dynamic pivot issue in sql server 2012

I'm trying to implement a dynamic pivoting in SQL to represent my results given below
ID Charge Message Amt Of Billing
4563 WEB FEE 9.75
4563 MONTHLY FEE 6
4563 CLUB FEE 9.95
4648 MONTHLY FEE 6
4648 ACCOUNT FEE 5
4648 CLUB FEE 9.95
4648 WEB FEE 9.75
4650 MONTHLY FEE 6
4650 WEB FEE 9.75
4650 CLUB FEE 9.95
into a desired representation like this.
ID ACCOUNT FEE MONTHLY FEE CLUB FEE WEB FEE
4563 6 9.95 9.75
4648 5 6 9.95 9.75
4650 6 9.95 9.75
Your help is highly appreciated.
For a dynamic pivot, you could use something like this:
declare #cols nvarchar(max);
declare #sql nvarchar(max);
select #cols = stuff((
select distinct
', ' + quotename(isnull(nullif(ChargeMessage,''),'unknown'))
from t
order by 1
for xml path (''), type).value('.','nvarchar(max)')
,1,1,'')
select #sql ='
select Id, ' + #cols +'
from (
select Id, ChargeMessage= isnull(nullif(ChargeMessage,''''),''unknown''), AmtOfBilling
from t
) as t
pivot (sum([AmtOfBilling]) for [ChargeMessage] in (' + #cols +')) p'
select #sql
exec(#sql);
rextester demo: http://rextester.com/NRRGA52425
returns: (included an empty string for test data)
+------+-------------+----------+-------------+---------+---------+
| Id | ACCOUNT FEE | CLUB FEE | MONTHLY FEE | unknown | WEB FEE |
+------+-------------+----------+-------------+---------+---------+
| 4563 | NULL | 9,95 | 6,00 | NULL | 9,75 |
| 4648 | 5,00 | 9,95 | 6,00 | NULL | 9,75 |
| 4650 | NULL | 9,95 | 6,00 | 9,95 | 9,75 |
+------+-------------+----------+-------------+---------+---------+
Query that is generated:
select Id, [ACCOUNT FEE], [CLUB FEE], [MONTHLY FEE], [unknown], [WEB FEE]
from (
select Id, ChargeMessage= isnull(nullif(ChargeMessage,''),'unknown'), AmtOfBilling
from t
) as t
pivot (sum([AmtOfBilling]) for [ChargeMessage]
in ( [ACCOUNT FEE], [CLUB FEE], [MONTHLY FEE], [unknown], [WEB FEE])) p
dynamic conditional aggregation:
declare #cols nvarchar(max);
declare #sql nvarchar(max);
select #cols = stuff((
select distinct
char(10)+' , '
+ quotename(isnull(nullif(ChargeMessage,''),'unknown'))
+' = sum(case when ChargeMessage = '''+ChargeMessage+''' then AmtOfBilling end)'
from t
order by 1
for xml path (''), type).value('.','nvarchar(max)')
,1,0,'')
select #sql ='
select Id'+#cols+'
from t
group by Id'
select #sql
exec(#sql);
Query Generated:
select Id
, [ACCOUNT FEE] = sum(case when ChargeMessage = 'ACCOUNT FEE' then AmtOfBilling end)
, [CLUB FEE] = sum(case when ChargeMessage = 'CLUB FEE' then AmtOfBilling end)
, [MONTHLY FEE] = sum(case when ChargeMessage = 'MONTHLY FEE' then AmtOfBilling end)
, [unknown] = sum(case when ChargeMessage = '' then AmtOfBilling end)
, [WEB FEE] = sum(case when ChargeMessage = 'WEB FEE' then AmtOfBilling end)
from t
group by Id

Select sum with other table in SQL

How do I select sum with other table if I have data like below:
Table Member
MemberID Name DateJoin
M0001 John 01/01/2015
M0002 Willy 03/20/2016
M0003 Teddy 02/01/2017
etc....
Table Transaction
MemberID TransDate Total
M0002 02/01/2015 100000
M0002 02/28/2015 222000
M0001 01/01/2016 150000
M0001 01/26/2017 160000
M0002 01/25/2017 160000
M0003 02/01/2017 9000
I want the result as a sum of how many times the member transaction in shop in years 2015-2017
The result I want it's:
MemberID 2015 2016 2017
M0001 0 1 1
M0002 2 0 1
M0003 0 0 1
How many members will appear in Result although don't have transaction too.
try dynamic sql .
--load in #temp table
select MemberID , datepart (yyyy ,TransDate ) as TransDate ,COUNT(*)as cnt into #temp from [Transaction]
group by MemberID , datepart (yyyy ,TransDate )
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(c.TransDate)
FROM #temp c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT MemberID, ' + #cols + ' from
(
select MemberID
, cnt
, TransDate
from #temp
) x
pivot
(
max(cnt)
for TransDate in (' + #cols + ')
) p '
execute(#query)
drop #temp -- cleanup of #temp table
CREATE TABLE #Table1
([MemberID] varchar(5), [Name] varchar(5), [DateJoin] datetime)
;
INSERT INTO #Table1
([MemberID], [Name], [DateJoin])
VALUES
('M0001', 'John', '2015-01-01 00:00:00'),
('M0002', 'Willy', '2016-03-20 00:00:00'),
('M0003', 'Teddy', '2017-02-01 00:00:00')
;
CREATE TABLE #Table2
([MemberID] varchar(5), [TransDate] datetime, [Total] int)
;
INSERT INTO #Table2
([MemberID], [TransDate], [Total])
VALUES
('M0002', '2015-02-01 00:00:00', 100000),
('M0002', '2015-02-28 00:00:00', 222000),
('M0001', '2016-01-01 00:00:00', 150000),
('M0001', '2017-01-26 00:00:00', 160000),
('M0002', '2017-01-25 00:00:00', 160000),
('M0003', '2017-02-01 00:00:00', 9000)
;
select MemberID,[2015], [2016], [2017]
from
(
select a.MemberID,a.name,a.DateJoin,year(b.TransDate)[year],b.Total from #Table1 A join
#Table2 B on a.MemberID=b.MemberID
) src
pivot
(
count(total)
for year in ([2015], [2016], [2017])
) piv;
output
MemberID 2015 2016 2017
M0001 0 1 1
M0002 2 0 1
M0003 0 0 1
IN 2000
SELECT MEMBERID, COUNT(CASE WHEN YEAR=2015 THEN YEAR END ) AS [2015],
COUNT(CASE WHEN YEAR=2016 THEN YEAR END ) AS [2016],
COUNT(CASE WHEN YEAR=2017 THEN YEAR END ) AS [2017]
FROM (
SELECT A.MEMBERID,A.NAME,A.DATEJOIN,YEAR(B.TRANSDATE)[YEAR],B.TOTAL FROM #TABLE1 A JOIN
#TABLE2 B ON A.MEMBERID=B.MEMBERID)A
GROUP BY MEMBERID
It seems there is no information you need from table member. So select from table transaction alone and count conditionally.
select
memberid,
count(case when year(transdate) = 2015 then 1 end) as [2015],
count(case when year(transdate) = 2016 then 1 end) as [2016],
count(case when year(transdate) = 2017 then 1 end) as [2017]
from transaction
group by memberid
order by memberid;
If you want to include members that don't have any transaction, then you do need a join (an outer join that is):
select
m.memberid,
count(case when year(t.transdate) = 2015 then 1 end) as [2015],
count(case when year(t.transdate) = 2016 then 1 end) as [2016],
count(case when year(t.transdate) = 2017 then 1 end) as [2017]
from member m
left join transaction t on t.memberid = m.memberid
group by m.memberid
order by m.memberid;

How to apply pivot in below scenarios

I have below table
Name Month Year Count
----------------------------
xxx 12 2012 24
xxx 1 2013 42
xxx 2 2013 23
yyy 12 2012 34
yyy 1 2013 12
yyy 2 2013 54
I would like to convert it into below format,
Name Dec-12 Jan-13 Feb-13
--------------------------------
xxx 24 42 23
yyy 34 12 54
How to apply pivot?
Since you are using SQL Server there are several ways that you can pivot the data from rows into columns.
If your values are limited or you have a known number of values, then you can hard-code the values with a static pivot:
select name, [Dec_12], [Jan_13], [Feb_13]
from
(
select name,
left(datename(month, dateadd(month, month, 0) -1), 3) +'_'+right(cast(year as varchar(4)), 2) MY,
[count]
from yourtable
) src
pivot
(
sum(count)
for my in ([Dec_12], [Jan_13], [Feb_13])
) piv;
See SQL Fiddle with Demo.
Now, if you have an unknown number of values, then you will need to implement dynamic SQL to generate the result:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(my)
from
(
select left(datename(month, dateadd(month, month, 0) -1), 3) +'_'+right(cast(year as varchar(4)), 2) my,
CAST(
CAST(year AS VARCHAR(4)) +
RIGHT('0' + CAST(month AS VARCHAR(2)), 2) +
'01'
AS DATETIME) fulldate
from yourtable
) t
group by my, fulldate
order by fulldate
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT name, ' + #cols + '
from
(
select name,
left(datename(month, dateadd(month, month, 0) -1), 3) +''_''+right(cast(year as varchar(4)), 2) MY,
[count]
from yourtable
) x
pivot
(
sum(count)
for my in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo.
This difference with this and the static version is if you need an unknown number of dates or want this to automatically update with new dates when they are available, this will return the new data without changing the code.
The result of both queries is:
| NAME | DEC_12 | JAN_13 | FEB_13 |
-----------------------------------
| xxx | 24 | 42 | 23 |
| yyy | 34 | 12 | 54 |
Try this:
WITH CTE
AS
(
SELECT
Name,
CAST(Month AS VARCHAR(2)) + '-' + CAST(Year AS VARCHAR(4)) AS MonthYear,
[Count]
FROM tablename
)
SELECT
Name,
[12-2012] AS 'Dec-12',
[1-2013] AS 'Jan-13',
[2-2013] AS 'Feb-13'
FROM CTE
PIVOT
(
MAX([Count])
FOR MonthYear IN([12-2012],
[1-2013],
[2-2013])
) AS p;
SQL Fiddle Demo
SELECT t.name
, MAX(CASE
WHEN t.month=12 AND t.year = 2012
THEN count
ELSE NULL
END) AS "Dec_12"
, MAX(CASE
WHEN t.month=1 AND t.year = 2013
THEN count
ELSE NULL
END) AS "Jan_13"
, MAX(CASE
WHEN t.month=2 AND t.year = 2013
THEN count
ELSE NULL
END) AS "Feb_13"
FROM table t
GROUP BY t.name
;