SQL dynamically pivot and group results - sql-server-2008

I have a table set up like below:
CLIENTNAME MONTHANDYEAR RESOURCE COST
abc JAN2011 res1 1000
abc FEB2011 res1 2000
def JAN2011 res2 1500
def MAR2011 res1 2000
ghi MAR2011 res3 2500
I need an output like below. Months are to be generated dynamically in 3-month intervals. In this case, is there a way to pivot by MONTHANDYEAR as well as group by clientname?
RESOURCE CLIENTNAME JAN2011 FEB2011 MAR2011
res1 abc 1000 1000
res1 def 2000
res2 def 1500
res3 ghi 2500

This is what the PIVOT operator is for:
SELECT
Resource, ClientName,
[JAN2011], [FEB2011], [MAR2011]
FROM
(
SELECT
*
FROM tblname
) AS SourceTable
PIVOT
(
SUM(COST)
FOR MONTHANDYEAR IN ([JAN2011], [FEB2011], [MAR2011])
) AS PivotTable;
Since your months are selected dynamically using #startDate as a base month, you can use the following dynamic query:
DECLARE #startDate datetime
SET #startDate = '2011-01-01'
DECLARE #sql varchar(MAX)
SET #sql = 'SELECT
Resource, ClientName, [' +
REPLACE(SUBSTRING(CONVERT(varchar, #startDate, 13), 4, 8), ' ', '') + '], [' +
REPLACE(SUBSTRING(CONVERT(varchar, DATEADD(MONTH, 1, #startDate), 13), 4, 8), ' ', '') + '], [' +
REPLACE(SUBSTRING(CONVERT(varchar, DATEADD(MONTH, 2, #startDate), 13), 4, 8), ' ', '') + ']
FROM
(
SELECT
*
FROM tblName
) AS SourceTable
PIVOT
(
SUM(COST)
FOR MONTHANDYEAR IN (' +
QUOTENAME(REPLACE(SUBSTRING(CONVERT(varchar, #startDate, 13), 4, 8), ' ', '')) + ', ' +
QUOTENAME(REPLACE(SUBSTRING(CONVERT(varchar, DATEADD(MONTH, 1, #startDate), 13), 4, 8), ' ', '')) + ', ' +
QUOTENAME(REPLACE(SUBSTRING(CONVERT(varchar, DATEADD(MONTH, 2, #startDate), 13), 4, 8), ' ', '')) + ')
) AS PivotTable'
execute(#sql)
working sqlfiddle here

This data transformation can be done with the PIVOT function.
If you know the values, then you can hard-code the monthandyear dates:
select resource,
clientname,
isnull(jan2011, '') Jan2011,
isnull(feb2011, '') Feb2011,
isnull(mar2011, '') Mar2011
from
(
select clientname, monthandyear, resource, cost
from yourtable
) src
pivot
(
sum(cost)
for monthandyear in (Jan2011, Feb2011, Mar2011)
) piv;
See SQL Fiddle with Demo.
But if the dates are unknown, then you will need to use dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#colNames AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(monthandyear)
from yourtable
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select #colNames = STUFF((SELECT distinct ', isnull(' + QUOTENAME(monthandyear)+', 0) as '+QUOTENAME(monthandyear)
from yourtable
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT resource, clientname,' + #colNames + ' from
(
select clientname, monthandyear, resource, cost
from yourtable
) x
pivot
(
sum(cost)
for monthandyear in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo.
The result of both is:
| RESOURCE | CLIENTNAME | JAN2011 | FEB2011 | MAR2011 |
-------------------------------------------------------
| res1 | abc | 1000 | 2000 | 0 |
| res1 | def | 0 | 0 | 2000 |
| res2 | def | 1500 | 0 | 0 |
| res3 | ghi | 0 | 0 | 2500 |

SELECT Resource, Clientname
, SUM(CASE WHEN MonthAndYear = 'JAN2011' THEN COST ELSE 0 END) AS JAN2011
, SUM(CASE WHEN MonthAndYear = 'FEB2011' THEN COST ELSE 0 END) AS FEB2011
, SUM(CASE WHEN MonthAndYear = 'MAR2011' THEN COST ELSE 0 END) AS MAR2011
FROM yourtable
GROUP BY Resource, Clientname
You can also remove the ELSE 0 to return a NULL value for resource/clientname combinations without data

Related

How split multiples subvalues with multiples SubIndex in columns mysql?

CREATE TABLE tablename (id INT,C1 text);
INSERT INTO tablename VALUES
(1, '[AU 1] string 1; [AU 2] string 2; [AU 3] string 3.1; string 3.2; [AU 4] string 4.1; string 4.2; [AU 5] string 5'),
(2, '[AU 1; AU 2] string 1'),
(3, '[AU 1] string 1; [AU 2] string 2');
CREATE TABLE numbers (n INT PRIMARY KEY);
INSERT INTO numbers VALUES (1),(2),(3),(4),(5),(6);
As close as I got by following the examples of '#fthiella' and '#RGarcia'.
Please see fiddle here.
The result I get is different than expected in "I want output like this:"
I want output like this
| ID | AU | ORG |
| 1 |[AU 1]|string_1|
| 1 |[AU 2]|string_2|
| 1 |[AU 3]|string_3.1|
| 1 |[AU 3]|string_3.2|
| 1 |[AU 4]|string_4.1|
| 1 |[AU 4]|string_4.2|
| 1 |[AU 5]|string_5|
| 2 |[AU 1; AU 2]|string_1|
| 3 |[AU 1]|string_1|
| 3 |[AU 2]|string_2|
WITH RECURSIVE
cte1 AS ( SELECT id,
TRIM(TRAILING ';' FROM TRIM(SUBSTRING_INDEX(C1, '[', 2))) one_group,
SUBSTRING(C1 FROM LENGTH(SUBSTRING_INDEX(C1, '[', 2))) slack,
1 ordinality_au
FROM test
UNION ALL
SELECT id,
TRIM(TRAILING ';' FROM TRIM(SUBSTRING_INDEX(slack, '[', 2))),
SUBSTRING(slack FROM LENGTH(SUBSTRING_INDEX(slack, '[', 2))),
ordinality_au + 1
FROM cte1
WHERE LOCATE('[', slack) ),
cte2 AS ( SELECT id,
CONCAT(SUBSTRING_INDEX(one_group, ']', 1), ']') AU,
TRIM(SUBSTRING_INDEX(one_group, ']', -1)) ORG,
ordinality_au
FROM cte1 ),
cte3 AS ( SELECT id,
AU,
ordinality_au,
SUBSTRING_INDEX(ORG, ';', 1) ORG,
TRIM(TRIM(LEADING ';' FROM TRIM(LEADING SUBSTRING_INDEX(ORG, ';', 1) FROM ORG))) slack,
1 ordinality_org
FROM cte2
UNION ALL
SELECT id,
AU,
ordinality_au,
SUBSTRING_INDEX(slack, ';', 1),
TRIM(TRIM(LEADING ';' FROM TRIM(LEADING SUBSTRING_INDEX(slack, ';', 1) FROM slack))),
ordinality_org + 1
FROM cte3
WHERE TRIM(slack) != '' )
SELECT id,
AU,
ORG
FROM cte3
ORDER BY id, ordinality_au, ordinality_org;
https://dbfiddle.uk/?rdbms=mariadb_10.4&fiddle=a3258f8f1cd92eca0c480ea6673f13f1

Converting rows records to columns in mySQL

I'm trying to convert row data to columns. Data will be extracted from different tables. I tried using PIVOTbut I'm not much successful.
Lets consider column#1 as primary key in every table.
CREATE TABLE Table_pivot_01
([SSN ID] int, [Citizen_name] varchar(5), [Company] varchar(4))
;
INSERT INTO Table_pivot_01
([SSN ID], [Citizen_name], [Company])
VALUES
(12345, 'John', 'XYZ'),
(12346, 'Tom', 'ABC'),
(12347, 'Jerry', 'QWER'),
(12348, 'Joe', 'PQR'),
(12349, 'Josh', NULL)
;
CREATE TABLE Table_pivot_02
([Serial] int, [SSN_ID] int, [Family_details] varchar(9), [Family_members_name] varchar(10))
;
INSERT INTO Table_pivot_02
([Serial], [SSN_ID], [Family_details], [Family_members_name])
VALUES
(1010, 12345, 'Spouse', 'Mari'),
(1011, 12345, 'Child - 1', 'John Jr. 1'),
(1012, 12345, 'Child - 2', 'John Jr. 2'),
(1013, 12346, 'Spouse', 'Ken'),
(1014, 12347, 'Spouse', 'Suzen'),
(1015, 12347, 'Child - 1', 'Jerry Jr.1'),
(1016, 12347, 'Child - 2', 'Jerry Jr.2'),
(1017, 12347, 'Child - 3', 'Jerry Jr.3'),
(1018, 12348, 'Child - 1', 'Joe Jr.1'),
(1019, 12348, 'Child - 2', 'Joe Jr.2'),
(1020, 12349, 'Spouse', 'Zoe'),
(1021, 12349, 'Child - 1', 'Josh Jr.1'),
(1022, 12349, 'Child - 2', 'Josh Jr.2')
;
CREATE TABLE Table_pivot_03
([Row] int, [SSN_ID] int, [Address_type] varchar(8), [Address] varchar(22), [PhoneNumber_type] varchar(6), [PhoneNumber] varchar(18))
;
INSERT INTO Table_pivot_03
([Row], [SSN_ID], [Address_type], [Address], [PhoneNumber_type], [PhoneNumber])
VALUES
(121, 12345, 'Present', 'Address_John_Present', 'Home', 'John_Home_phone'),
(122, 12345, 'Office', 'Address_John_Office', 'Office', 'John_Office_phone'),
(123, 12345, 'Perement', 'Address_John_Perement', 'Fax', 'John_FAX_phone'),
(124, 12346, 'Present', 'Address_Tom_Present', 'Home', 'Tom_Home_phone'),
(125, 12346, 'Office', 'Address_Tom_Office', 'Office', 'Tom_Office_phone'),
(126, 12347, 'Office', 'Address_Jerry_Office', 'Home', 'Jerry_Home_phone'),
(127, 12347, 'Perement', 'Address_Jerry_Perement', 'Office', 'Jerry_Office_phone'),
(128, 12348, 'Present', 'Address_Joe_Present', 'Home', 'Joe_Home_phone'),
(129, 12348, 'Office', 'Address_Joe_Office', 'Office','Joe_Office_phone'),
(130, 12348, 'Perement' , 'Address_Josh_Perement','','' ),
(131, 12349, 'Present','Address_Josh_Present','Home','Josh_Home_phone'),
(132, 12349, 'Perement', 'Address_Josh_Perement' , 'Fax' ,'Josh_FAX_phone');
Table schema : http://rextester.com/MSXK16689
The Expected Output is:
How can I build the result in effective way?
MySQL Version
You've stated that you have tried using PIVOT but MySQL doesn't have a PIVOT function. In MySQL you need to use an aggregate function along with a conditional logic statement like CASE...WHEN or something similar. You also have several tables and several different columns you need to pivot which complicates this a bit. It also seems that you have an unknown number of new columns that need to be created, which adds another layer of complexity.
If you know all of the columns you want to be displayed in the final result, then you can easily type up a version of this query to be something like:
select
p1.`SSN_ID`,
p1.Citizen_name,
p1.Company,
max(case when p2.Family_details = 'Spouse' then Family_members_name end) Spouse,
max(case when p2.Family_details = 'Child - 1' then Family_members_name end) Child1,
max(case when p2.Family_details = 'Child - 2' then Family_members_name end) Child2,
max(case when p2.Family_details = 'Child - 3' then Family_members_name end) Child3,
max(case when p2.Family_details = 'Child - 4' then Family_members_name end) Child4,
max(case when p3.Address_type = 'Present' then p3.Address end) PresentAddress,
max(case when p3.Address_type = 'Office' then p3.Address end) OfficeAddress,
max(case when p3.Address_type = 'Perement' then p3.Address end) PermAddress,
max(case when p3.PhoneNumber_type = 'Home' then p3.PhoneNumber end) HomePhone,
max(case when p3.PhoneNumber_type = 'Office' then p3.PhoneNumber end) OfficePhone,
max(case when p3.PhoneNumber_type = 'Fax' then p3.PhoneNumber end) FaxPhone
from Table_pivot_01 p1
left join Table_pivot_02 p2
on p1.`SSN_ID` = p2.`SSN_ID`
left join Table_pivot_03 p3
on p1.`SSN_ID` = p3.`SSN_ID`
group by p1.`SSN_ID`,
p1.Citizen_name,
p1.Company;
Basically you create a new column in a max(case... statement and it will display the value. As mentioned, this gets a bit more complicated if you have unknown values you want as columns. In MySQL you need to use a Prepared Statement so you can use dynamic SQL. Your code would looks sort of like this:
SET #sql = NULL;
SET #sql1 = NULL;
SET #sql2 = NULL;
SET #sql3 = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
' max(case when p2.Family_details = ''',
Family_details,
''' then Family_members_name end) AS `',
Family_details, '`'
)
) INTO #sql1
FROM Table_pivot_02;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
' max(case when p3.Address_type = ''',
Address_type,
''' then Address end) AS `',
Address_type, '`'
)
) INTO #sql2
FROM Table_pivot_03;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
' max(case when p3.PhoneNumber_type = ''',
PhoneNumber_type,
''' then PhoneNumber end) AS `',
PhoneNumber_type, '`'
)
) INTO #sql3
FROM Table_pivot_03
where PhoneNumber_type <> '';
SET #sql = CONCAT('SELECT p1.`SSN_ID`,
p1.Citizen_name,
p1.Company, ', #sql1, ',', #sql2, ',', #sql3, '
from Table_pivot_01 p1
left join Table_pivot_02 p2
on p1.`SSN_ID` = p2.`SSN_ID`
left join Table_pivot_03 p3
on p1.`SSN_ID` = p3.`SSN_ID`
group by p1.`SSN_ID`,
p1.Citizen_name,
p1.Company');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
In this you are creating a long string of the max(case... statements that get concatenated together to then be executed by the database engine. There may be easier ways to get the result you want, but this does work. I've created a demo on rextester to show the code. Both of these produce a result:
+-------+--------+--------------+---------+--------+------------+------------+------------+----------------------+----------------------+------------------------+------------------+--------------------+----------------+
| Row | SSN_ID | Citizen_name | Company | Spouse | Child - 1 | Child - 2 | Child - 3 | Present | Office | Perement | Home | Office | Fax |
+-------+--------+--------------+---------+--------+------------+------------+------------+----------------------+----------------------+------------------------+------------------+--------------------+----------------+
| 1 | 12345 | John | XYZ | Mari | John Jr. 1 | John Jr. 2 | NULL | Address_John_Present | Address_John_Office | Address_John_Perement | John_Home_phone | John_Office_phone | John_FAX_phone |
| 2 | 12346 | Tom | ABC | Ken | NULL | NULL | NULL | Address_Tom_Present | Address_Tom_Office | NULL | Tom_Home_phone | Tom_Office_phone | NULL |
| 3 | 12347 | Jerry | QWER | Suzen | Jerry Jr.1 | Jerry Jr.2 | Jerry Jr.3 | NULL | Address_Jerry_Office | Address_Jerry_Perement | Jerry_Home_phone | Jerry_Office_phone | NULL |
| 4 | 12348 | Joe | PQR | NULL | Joe Jr.1 | Joe Jr.2 | NULL | Address_Joe_Present | Address_Joe_Office | Address_Josh_Perement | Joe_Home_phone | Joe_Office_phone | NULL |
| 5 | 12349 | Josh | NULL | Zoe | Josh Jr.1 | Josh Jr.2 | NULL | Address_Josh_Present | NULL | Address_Josh_Perement | Josh_Home_phone | NULL | Josh_FAX_phone |
+-------+--------+--------------+---------+--------+------------+------------+------------+----------------------+----------------------+------------------------+------------------+--------------------+----------------+
Based on your comment that you might have more than one phone number type per person, you'll need to create a row number for each group of phone types. Unfortunately, again MySQL doesn't have windowing function so you'll need to use user defined variables to get the final result. When you query for PhoneNumber_type you'll need to use something like:
select *
from
(
select SSN_ID, PhoneNumber_type, PhoneNumber,
#num:= case when #group = SSN_ID and #type = PhoneNumber_type then #num +1 else if(#group := SSN_ID, 1, 1) end rn,
#group:= SSN_ID,
#type:=PhoneNumber_type
from Table_pivot_03 t
CROSS JOIN (select #num:=0, #group:=null, #type:=null) c
where t.PhoneNumber_type <> ''
order by SSN_ID, PhoneNumber_type
) as x;
This creates a row number value for each user and phone type. You'd then integrate this into the dynamic SQL code:
SET #sql = NULL;
SET #sql1 = NULL;
SET #sql2 = NULL;
SET #sql3 = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
' max(case when p2.Family_details = ''',
Family_details,
''' then Family_members_name end) AS `',
Family_details, '`'
)
) INTO #sql1
FROM Table_pivot_02;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
' max(case when p3.Address_type = ''',
Address_type,
''' then Address end) AS `',
Address_type, '`'
)
) INTO #sql2
FROM Table_pivot_03;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
' max(case when p.PhoneNumber_type = ''',
PhoneNumber_type,
''' and rn = ', rn, ' then p.PhoneNumber end) AS `',
PhoneNumber_type, rn, '`'
)
) INTO #sql3
FROM
(
select SSN_ID, PhoneNumber_type, PhoneNumber,
#num:= case when #group = SSN_ID and #type = PhoneNumber_type then #num +1 else if(#group := SSN_ID, 1, 1) end rn,
#group:= SSN_ID,
#type:=PhoneNumber_type
from Table_pivot_03 t
CROSS JOIN (select #num:=0, #group:=null, #type:=null) c
where t.PhoneNumber_type <> ''
order by SSN_ID, PhoneNumber_type
) as x;
SET #sql = CONCAT('SELECT p1.`SSN_ID`,
p1.Citizen_name,
p1.Company, ', #sql1, ',', #sql2, ',', #sql3, '
from Table_pivot_01 p1
left join Table_pivot_02 p2
on p1.`SSN_ID` = p2.`SSN_ID`
left join Table_pivot_03 p3
on p1.SSN_ID = p3.SSN_Id
left join
(
select SSN_ID, PhoneNumber_type, PhoneNumber,
#num:= case when #group = SSN_ID and #type = PhoneNumber_type then #num +1 else if(#group := SSN_ID, 1, 1) end rn,
#group:= SSN_ID,
#type:=PhoneNumber_type
from Table_pivot_03 t
CROSS JOIN (select #num:=0, #group:=null, #type:=null) c
where t.PhoneNumber_type <> ''''
order by SSN_ID, PhoneNumber_type
) as p
on p1.SSN_ID = p.SSN_Id
group by p1.`SSN_ID`,
p1.Citizen_name,
p1.Company');
#select #sql;
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
See another demo.
SQL Server Version
Since you've now said that you need a SQL Server version here is that version. SQL Server has a few features that make doing this significantly easier including a PIVOT function, UNPIVOT function, and windowing functions like row_number. Here's a static version of the query with just a few columns pivoted:
select SSN_ID,
Citizen_name,
Company,
Spouse, [Child - 1], [Child - 2], [Child - 3], [Child - 4]
from
(
select SSN_ID,
Citizen_name,
Company,
col,
value
from
(
select
p1.SSN_ID,
p1.Citizen_name,
p1.Company,
p2.Family_details,
p2.Family_members_name,
p3.Address_type,
p3.Address,
PhoneNumber_type = p.PhoneNumber_type + cast(p.rn as varchar(10)),
p.PhoneNumber
from Table_pivot_01 p1
left join Table_pivot_02 p2
on p1.SSN_ID = p2.SSN_ID
left join Table_pivot_03 p3
on p1.SSN_ID = p3.SSN_ID
left join
(
select SSN_ID, PhoneNumber_type, PhoneNumber,
rn = row_number() over(partition by SSN_ID, PhoneNumber_type order by SSN_ID, PhoneNumber_type)
from Table_pivot_03
where PhoneNumber_type <> ''
) p
on p1.SSN_ID = p.SSN_ID
) d
cross apply
(
select 'Family_details', Family_details, Family_members_name union all
select 'Address_type', Address_type, Address union all
select 'PhoneNumber_type', PhoneNumber_type, PhoneNumber
) c(orig, col, value)
) src
pivot
(
max(value)
for col in (Spouse, [Child - 1], [Child - 2], [Child - 3], [Child - 4])
) piv
Then if you need a dynamic sql version the code would be like:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(Col)
from
(
select col, ord
from
(
select
p1.SSN_ID,
p1.Citizen_name,
p1.Company,
p2.Family_details,
p2.Family_members_name,
p3.Address_type,
p3.Address,
PhoneNumber_type = p.PhoneNumber_type + cast(p.rn as varchar(10)),
p.PhoneNumber
from Table_pivot_01 p1
left join Table_pivot_02 p2
on p1.SSN_ID = p2.SSN_ID
left join Table_pivot_03 p3
on p1.SSN_ID = p3.SSN_ID
left join
(
select SSN_ID, PhoneNumber_type, PhoneNumber,
rn = row_number() over(partition by SSN_ID, PhoneNumber_type order by SSN_ID, PhoneNumber_type)
from Table_pivot_03
where PhoneNumber_type <> ''
) p
on p1.SSN_ID = p.SSN_ID
) d
cross apply
(
select 'Family_details', Family_details, Family_members_name, 1 union all
select 'Address_type', Address_type, Address, 2 union all
select 'PhoneNumber_type', PhoneNumber_type, PhoneNumber, 3
) c(orig, col, value, ord)
) d
group by col, ord
order by ord, col
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)') ,1,1,'');
set #query = N'SELECT ' + #cols + N' from
(
select SSN_ID,
Citizen_name,
Company,
col,
value
from
(
select
p1.SSN_ID,
p1.Citizen_name,
p1.Company,
p2.Family_details,
p2.Family_members_name,
p3.Address_type,
p3.Address,
PhoneNumber_type = p.PhoneNumber_type + cast(p.rn as varchar(10)),
p.PhoneNumber
from Table_pivot_01 p1
left join Table_pivot_02 p2
on p1.SSN_ID = p2.SSN_ID
left join Table_pivot_03 p3
on p1.SSN_ID = p3.SSN_ID
left join
(
select SSN_ID, PhoneNumber_type, PhoneNumber,
rn = row_number() over(partition by SSN_ID, PhoneNumber_type order by SSN_ID, PhoneNumber_type)
from Table_pivot_03
where PhoneNumber_type <> ''''
) p
on p1.SSN_ID = p.SSN_ID
) d
cross apply
(
select ''Family_details'', Family_details, Family_members_name union all
select ''Address_type'', Address_type, Address union all
select ''PhoneNumber_type'', PhoneNumber_type, PhoneNumber
) c(orig, col, value)
) src
pivot
(
max(value)
for col in (' + #cols + N')
) p '
exec sp_executesql #query;
Here is another demo.

How to switch rows to columns and vice versa in SQL Server 2008

I have problem to switch rows to columns and vice versa in SQL Server 2008, I have tried any queries to a solution but I did not get a proper results.
I've a table as following:
declare #tmpTable table
(name varchar(20), date_ date, sales_code char(1), sales smallint, earned int)
insert into #tmpTable
values ('Robert', '2016/8/1', 'A', 2, 30),
('Robert', '2016/8/1', 'B', 3, 45),
('Robert', '2016/8/2', 'B', 1, 15),
('Robert', '2016/8/3', 'B', 2, 30),
('Jhon', '2016/8/1', 'A', 3, 45),
('Jhon', '2016/8/2', 'A', 3, 45),
('Jhon', '2016/8/3', 'B', 2, 30)
select * from #tmpTable;
Result:
Name date_ sales_code sales earned
------ ---------- ---------- ----- ------
Robert 2016-08-01 A 2 30
Robert 2016-08-01 B 3 45
Robert 2016-08-02 B 1 15
Robert 2016-08-03 B 2 30
Jhon 2016-08-01 A 3 45
Jhon 2016-08-02 A 3 45
Jhon 2016-08-03 B 2 30
Then, I have next query :
select * from (
select name, 'sales_code' as category, date_, sales_code from (
select * from (
SELECT name, date_
,STUFF((SELECT ', ' + sales_code [text()]
FROM #tmpTable
WHERE date_ = t.date_
FOR XML PATH(''), TYPE)
.value('.','NVARCHAR(MAX)'),1,2,' ') sales_code
,STUFF((SELECT ', ' + convert(varchar(max), sales) [text()]
FROM #tmpTable
WHERE date_ = t.date_
FOR XML PATH(''), TYPE)
.value('.','NVARCHAR(MAX)'),1,2,' ') sales
,STUFF((SELECT ', ' + convert(varchar(max), earned) [text()]
FROM #tmpTable
WHERE date_ = t.date_
FOR XML PATH(''), TYPE)
.value('.','NVARCHAR(MAX)'),1,2,' ') earned
FROM #tmpTable t
GROUP BY name, date_
) as a
) as a
) as a
pivot (
max(sales_code) FOR date_ IN ([2016/8/1], [2016/8/2], [2016/8/3])
)as pv
union all
select * from (
select name, 'sales' as category, date_, sales from (
select * from (
SELECT name, date_
,STUFF((SELECT ', ' + sales_code [text()]
FROM #tmpTable
WHERE date_ = t.date_
FOR XML PATH(''), TYPE)
.value('.','NVARCHAR(MAX)'),1,2,' ') sales_code
,STUFF((SELECT ', ' + convert(varchar(max), sales) [text()]
FROM #tmpTable
WHERE date_ = t.date_
FOR XML PATH(''), TYPE)
.value('.','NVARCHAR(MAX)'),1,2,' ') sales
,STUFF((SELECT ', ' + convert(varchar(max), earned) [text()]
FROM #tmpTable
WHERE date_ = t.date_
FOR XML PATH(''), TYPE)
.value('.','NVARCHAR(MAX)'),1,2,' ') earned
FROM #tmpTable t
GROUP BY name, date_
) as a
) as a
) as a
pivot (
max(sales) FOR date_ IN ([2016/8/1], [2016/8/2], [2016/8/3])
)as pv
union all
select * from (
select name, 'earned' as category, date_, earned from (
select * from (
SELECT name, date_
,STUFF((SELECT ', ' + sales_code [text()]
FROM #tmpTable
WHERE date_ = t.date_
FOR XML PATH(''), TYPE)
.value('.','NVARCHAR(MAX)'),1,2,' ') sales_code
,STUFF((SELECT ', ' + convert(varchar(max), sales) [text()]
FROM #tmpTable
WHERE date_ = t.date_
FOR XML PATH(''), TYPE)
.value('.','NVARCHAR(MAX)'),1,2,' ') sales
,STUFF((SELECT ', ' + convert(varchar(max), earned) [text()]
FROM #tmpTable
WHERE date_ = t.date_
FOR XML PATH(''), TYPE)
.value('.','NVARCHAR(MAX)'),1,2,' ') earned
FROM #tmpTable t
GROUP BY name, date_
) as a
) as a
) as a
pivot (
max(earned) FOR date_ IN ([2016/8/1], [2016/8/2], [2016/8/3])
)as pv
It will display the result:
name category 2016/8/1 2016/8/2 2016/8/3
------- -------- -------- ------- --------
Jhon sales_code A, B, A B, A B, B
Robert sales_code A, B, A B, A B, B
Jhon sales 2, 3, 3 1, 3 2, 2
Robert sales 2, 3, 3 1, 3 2, 2
Jhon earned 30, 45, 45 15, 45 30, 30
Robert earned 30, 45, 45 15, 45 30, 30
But, I would like to get the following result:
name category 2016/8/1 2016/8/2 2016/8/3
---- -------- -------- -------- --------
Robert sales_code A, B B B
Robert sales 2, 3 1 2
Robert earned 30, 45 15 30
Jhon sales_code A A B
Jhon sales 3 3 2
Jhon earned 45 45 30
Thanks a lot for any help.
first you need to unpivot your data.. to do this, all data types much match, so you need to convert the 2 numeric columns to varchars.
still use stuff before you unpivot to get the combined values per name, date_ but use distinct to only get the name, date_ values once.
after you unpivot, you just need to pivot again.
SELECT *
FROM ( SELECT DISTINCT
Name,
date_,
sales_code = STUFF((SELECT ', ' + sales_code
FROM #tmpTable t2
WHERE t2.Name = t.Name AND t2.date_ = t.date_
FOR XML PATH('')), 1, 2, ''),
sales = STUFF((SELECT ', ' + CONVERT(VARCHAR, sales)
FROM #tmpTable t2
WHERE t2.Name = t.Name AND t2.date_ = t.date_
FOR XML PATH('')), 1, 2, ''),
earned = STUFF((SELECT ', ' + CONVERT(VARCHAR, earned)
FROM #tmpTable t2
WHERE t2.Name = t.Name AND t2.date_ = t.date_
FOR XML PATH('')), 1, 2, '')
FROM #tmpTable t) t
UNPIVOT (
val
FOR category IN (sales_code, sales, earned)
) up
PIVOT (
MAX(val)
FOR date_ IN ([2016-08-01], [2016-08-02], [2016-08-03])
) p
ORDER BY name DESC,
category DESC

Counting distinct multi-column patterns

I'm using SQL Server 2014 and i nee some help with a hard query.
I have the following table (MyTable). These columns names are just for the example. They are actually totally different from each other.
id int,
col1 int,
col2 int,
..
..
..
col70 int
For each pairs of sequential columns {(col1, col2), (col2_col3)...(col69_col70)}, i need to calculate the following: The number of different pairs that each values has - col_i is the static column, and col_i+1 is the other one. Each value need to be divided by the total amount of records in the table. For example:
col1 | col2
45 | 789
56 | 345
99 | 234
45 | 789
45 | 222
89 | 678
89 | 345
45 | 789
90 | 234
12 | 567
Calculation:
((45, 789)+(45, 222))/10
(56, 345)/10
(99, 234)/10
(45, 789)+(45, 222)/10
(45, 789)+(45, 222)/10
(89, 678)+(89, 345)/10
(89, 678)+(89, 345)/10
((45, 789)+(45, 222))/10
(90, 234)/10
(12, 567)/10
Output:
col1_col2
0.2
0.1
0.1
0.2
0.2
0.2
0.2
0.2
0.1
0.1
Explanation for the first records:
45 is the value of the static column ,so now i'll check how many different combination we can find with col2:
45 | 789
45 | 789
45 | 222
45 | 789
Total distinct combinations divided by number of records in the table: 2/10 = 0.2
This calculation need for each pairs of sequential columns. Any recommendation? Is there's a smart way to calculate it automatically instead of writing a query with line for each pair?
An example assuming you have a primary key:
create table my_table
(column_id int not null,
column1 int not null,
column2 int not null);
insert into my_table
(column_id, column1, column2)
values
(1, 45,789),
(2, 56,345),
(3, 99,234),
(4, 45,789),
(5, 45,222),
(6, 89,678),
(7, 89,345),
(8, 45,789),
(9, 90,234),
(10, 12,567);
declare #column_a as nvarchar(100) = N'column1';
declare #column_b as nvarchar(100) = N'column2';
declare #result_column as nvarchar(100) = N'column1_2';
declare #sql_string as nvarchar(4000)
set #sql_string =
'select a.column_id,
1.0 * count( distinct b.' + #column_b + ') / (count(a.' + #column_a + ') over ()) as ' + #result_column
+ ' from my_table a
inner join my_table b
on a.' + #column_a + ' = b.' + #column_a +
' group by a.column_id, a.' + #column_a +
' order by a.column_id';
-- print #sql_string;
execute(#sql_string);
If there's no primary key you could use the rownumber() function to create an identifier, but the result order would change. The print command can be useful for checking the dynamic sql string, here commented out.
Putting the dynamic SQL into a stored procedure:
create procedure column_freq #column_a nvarchar(100), #column_b nvarchar(100), #result_column nvarchar(100)
as
begin
declare #sql_string as nvarchar(4000);
set #sql_string =
'select a.column_id,
1.0 * count( distinct b.' + #column_b + ') / (count(a.' + #column_a + ') over ()) as ' + #result_column
+ ' from my_table a
inner join my_table b
on a.' + #column_a + ' = b.' + #column_a +
' group by a.column_id, a.' + #column_a +
' order by a.column_id';
execute(#sql_string);
end;
go
exec column_freq N'column1', N'column2', N'column1_2';
go

SQL: Using ISNULL with dynamic pivot

I want to make all the NULL values produced by the pivot to become 0s. I have placed ISNULL in every place imaginable, but does not seem to have any effect. Are pivots compatible with ISNULL? Code below:
DECLARE #startDate datetime
SET #startDate = '2013-01-01'
DECLARE #sql varchar(MAX)
SET #sql = 'SELECT
CLIENTNAME, PROJECTNAME, RESOURCE, [' +
REPLACE(SUBSTRING(CONVERT(varchar, #startDate, 13), 4, 8), ' ', '') + '], [' +
REPLACE(SUBSTRING(CONVERT(varchar, DATEADD(MONTH, 1, #startDate), 13), 4, 8), ' ', '') + '], [' +
REPLACE(SUBSTRING(CONVERT(varchar, DATEADD(MONTH, 2, #startDate), 13), 4, 8), ' ', '') + '], [' +
REPLACE(SUBSTRING(CONVERT(varchar, DATEADD(MONTH, 3, #startDate), 13), 4, 8), ' ', '') + '], [' +
REPLACE(SUBSTRING(CONVERT(varchar, DATEADD(MONTH, 4, #startDate), 13), 4, 8), ' ', '') + '], [' +
REPLACE(SUBSTRING(CONVERT(varchar, DATEADD(MONTH, 5, #startDate), 13), 4, 8), ' ', '') + ']
FROM
(
SELECT
CLIENTNAME, PROJECTNAME, RESOURCE, FORECASTTOTAL
FROM viewprojscheduling_group
) AS SourceTable
PIVOT
(
SUM(FORECASTTOTAL)
FOR SCHEDULEDDATE IN (' +
QUOTENAME(REPLACE(SUBSTRING(CONVERT(varchar, #startDate, 13), 4, 8), ' ', '')) + ', ' +
QUOTENAME(REPLACE(SUBSTRING(CONVERT(varchar, DATEADD(MONTH, 1, #startDate), 13), 4, 8), ' ', '')) + ', ' +
QUOTENAME(REPLACE(SUBSTRING(CONVERT(varchar, DATEADD(MONTH, 2, #startDate), 13), 4, 8), ' ', '')) + ', ' +
QUOTENAME(REPLACE(SUBSTRING(CONVERT(varchar, DATEADD(MONTH, 3, #startDate), 13), 4, 8), ' ', '')) + ', ' +
QUOTENAME(REPLACE(SUBSTRING(CONVERT(varchar, DATEADD(MONTH, 4, #startDate), 13), 4, 8), ' ', '')) + ', ' +
QUOTENAME(REPLACE(SUBSTRING(CONVERT(varchar, DATEADD(MONTH, 5, #startDate), 13), 4, 8), ' ', '')) + ')
) AS PivotTable'
execute(#sql)
I would set your query up slightly different because while it is dynamic in that the column names are changing, you have still hard-coded the number of columns.
First, I would use a recursive CTE to generate the list of months/years that you want to create.
DECLARE #startDate datetime
SET #startDate = '2013-01-01'
;with dates as
(
select #startdate datelist, 1 sp
union all
select dateadd(month, 1, datelist), sp+1
from dates
where sp+1 <= 5 -- change this number 5 to the number of months you need
)
select sp,
REPLACE(SUBSTRING(CONVERT(varchar(11), datelist, 13), 4, 8), ' ', '') MONTHANDYEAR
from dates
See SQL Fiddle with Demo. This is going to create your list of the 5 months with the year automatically. Then you are not hard-coding the 5 columns. Your current query is not as flexible as it could be. What will happen if you then want 12 months, you are going to have to change your code.
Once you generate the list of dates, I would insert it into a temp table so you can use it to get the columns.
The code to get the list of columns is:
select #cols = STUFF((SELECT ',' + QUOTENAME(monthandyear)
from #datesTemp
group by monthandyear, sp
order by sp
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select #colNames = STUFF((SELECT ', isnull(' + QUOTENAME(monthandyear)+', 0) as '+QUOTENAME(monthandyear)
from #datesTemp
group by monthandyear, sp
order by sp
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
See SQL Fiddle with Demo. You will see that there are two versions. The first one #cols gets the list of columns that will be used in the pivot. The second #colNames will be used in the final SELECT list to replace the null values with the zeros.
Then you put it all together and the code will be: (Note: I am using a version of my answer from your previous question)
DECLARE #cols AS NVARCHAR(MAX),
#colNames AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX),
#startDate datetime
SET #startDate = '2013-01-01'
;with dates as
(
select #startdate datelist, 1 sp
union all
select dateadd(month, 1, datelist), sp+1
from dates
where sp+1 <= 5 -- change this number 5 to the number of months you need
)
select sp,
REPLACE(SUBSTRING(CONVERT(varchar(11), datelist, 13), 4, 8), ' ', '') MONTHANDYEAR
into #datesTemp
from dates
select #cols = STUFF((SELECT ',' + QUOTENAME(monthandyear)
from #datesTemp
group by monthandyear, sp
order by sp
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select #colNames = STUFF((SELECT ', isnull(' + QUOTENAME(monthandyear)+', 0) as '+QUOTENAME(monthandyear)
from #datesTemp
group by monthandyear, sp
order by sp
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT resource, clientname,' + #colNames + '
from
(
select [CLIENTNAME], [RESOURCE], [FORECASTTOTAL],
REPLACE(SUBSTRING(CONVERT(varchar(11), SCHEDULEDDATE, 13), 4, 8), '' '', '''') monthandyear
from viewprojscheduling_group
) x
pivot
(
sum(FORECASTTOTAL)
for monthandyear in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo. This query will give you the result:
| RESOURCE | CLIENTNAME | JAN2013 | FEB2013 | MAR2013 | APR2013 | MAY2013 |
---------------------------------------------------------------------------
| res1 | abc | 1000 | 2000 | 0 | 0 | 0 |
| res1 | def | 0 | 0 | 2000 | 0 | 0 |
| res2 | def | 1500 | 0 | 0 | 0 | 0 |
| res3 | ghi | 0 | 0 | 2500 | 0 | 0 |