Dynamic Pivot of Email Addresses - sql

I have tried to research this, and I am unable to find something quite like it. I have a table that may have entries added many times over as well as deleted. I have no idea how many columns I will need, therefore I need a Dynamic Pivot. All the examples I see use a windows function, but I am pivoting email addresses.
The Table would look something like this:
Number | Email
--------------
1 | email1#email.com
1 | email2#email.com
1 | email3#email.com
2 | email4#email.com
2 | email5#email.com
3 | email6#email.com
4 | email7#email.com
4 | email8#email.com
I want the table to look like this(when all are included):
Number | Email1 | Email2 | Email3
---------------------------------------------------------------
1 | email1#email.com | email2#email.com | email3#email.com
2 | email4#email.com | email5#email.com |
3 | email6#email.com | |
4 | email7#email.com | email8#email.com |
If Number 1 wasn't included it would look like:
Number | Email1 | Email2
--------------------------------------------
2 | email4#email.com | email5#email.com
3 | email6#email.com |
4 | email7#email.com | email8#email.com
Thanks for the help!
Here is code to create a mock table:
CREATE TABLE #table
(number INT, email VARCHAR(30))
INSERT INTO #table (number, email)
VALUES (1,'email1#email.com')
,(1,'email2#email.com')
,(1,'email3#email.com')
,(2,'email4#email.com')
,(2,'email5#email.com')
,(3,'email6#email.com')
,(4,'email7#email.com')
,(4,'email8#email.com')
This is similar to what I have tried, I used count to try and just make it work, but was unable.
DECLARE #columns NVARCHAR(MAX), #sql NVARCHAR(MAX);
SET #columns = N'';
SELECT #columns += N', p.' + QUOTENAME(Number)
FROM (SELECT p.Number FROM #table AS p
GROUP BY p.Name) AS x;
SELECT #columns
SET #sql = N'
SELECT ' + STUFF(#columns, 1, 2, '') + '
FROM
(
SELECT p.number, p.email
FROM #test p
) AS j
PIVOT
(
Count(email) FOR Name IN ('
+ STUFF(REPLACE(#columns, ', p.[', ',['), 1, 1, '')
+ ')
) AS p;';
PRINT #sql;
EXEC sp_executesql #sql;

You first need to create a RNO by partition over Number.
The following query should do what you want:
CREATE TABLE #table (Number INT, Email VARCHAR(30))
INSERT INTO #table (Number, Email)
VALUES (1,'email1#email.com')
,(1,'email2#email.com')
,(1,'email3#email.com')
,(2,'email4#email.com')
,(2,'email5#email.com')
,(3,'email6#email.com')
,(4,'email7#email.com')
,(4,'email8#email.com')
SELECT *, ROW_NUMBER() OVER (PARTITION BY Number ORDER BY Number, Email) AS [RNO] INTO #temp FROM #table
DECLARE #DynamicCols NVARCHAR(MAX) = '';
DECLARE #pvt NVARCHAR(MAX) = '';
SET #pvt = STUFF(
(SELECT DISTINCT N', ' + QUOTENAME([RNO]) FROM #temp FOR XML PATH('')),1,2,N'')
SET #DynamicCols = STUFF(
(SELECT DISTINCT N', ' + QUOTENAME([RNO]) + ' AS '+ QUOTENAME('Email' + CAST([RNO] AS VARCHAR(MAX))) FROM #temp FOR XML PATH('')),1,2,N'')
EXEC (N'SELECT [Number],'+ #DynamicCols+'
FROM #temp tmp
PIVOT (MAX([Email]) FOR RNO IN('+#pvt+')) AS PIV');

Related

How do I include an additional non-aggregated column for each of my in my PIVOT values?

I have the following code fragment which gives me the current results below. I'm attempting to add an additional column for each of my pivoted values in order to include the lastview data for each of my siteuserid / tagname combo (see expected results). Since this column isn't an aggregation, I don't believe an additional pivot would help. I've tried multiple ways of adding lastview, but it always results in additional rows rather than the desired output.
create table #taghits (userid int, email varchar(20), tagname varchar(20), hits int, lastview date)
insert into #taghits select 1, 'email1#here.com', 'tag1', 3, '2020-03-24';
insert into #taghits select 2, 'email2#here.com', 'tag1', 1, '2020-03-17';
insert into #taghits select 2, 'email2#here.com', 'tag2', 1, '2020-03-18';
insert into #taghits select 3, 'email3#here.com', 'tag1', 2, '2020-03-25';
insert into #taghits select 3, 'email3#here.com', 'tag2', 5, '2020-03-28';
select * from #taghits;
DECLARE #Columns3 as NVARCHAR(MAX)
SELECT #Columns3 = ISNULL(#Columns3 + ', ','') + QUOTENAME(TagName)
FROM (
select distinct TagName
from #taghits
) AS TagNames
ORDER BY TagNames.TagName
DECLARE #scolumns as NVARCHAR(MAX)
SELECT #scolumns = ISNULL(#Scolumns + ', ','')+ 'ISNULL(' + QUOTENAME(TagName) + ', 0) AS '+ QUOTENAME(TagName)
FROM (select distinct TagName
from #taghits) AS TagNames
ORDER BY TagNames.TagName
DECLARE #SQL as NVARCHAR(MAX)
SET #SQL = '
select userid, email, ' + #scolumns + '
from
(
select userid, email, tagname, hits
from #taghits
) as TagHits
PIVOT (
SUM(hits)
FOR TagName IN (' + #Columns3 + ')
) AS PivotTable
order by userId
'
exec sp_executesql #SQL;
Current Result
| userid | email | tag1 | tag2 |
|--------|-----------------|------|------|
| 1 | email1#here.com | 3 | 0 |
| 2 | email2#here.com | 1 | 1 |
| 3 | email3#here.com | 2 | 5 |
Desired Result
| userid | email | tag1_hits | tag1_lastview | tag2_hits | tag2_lastview |
|--------|-----------------|-----------|---------------|-----------|---------------|
| 1 | email1#here.com | 3 | 2020-03-24 | 0 | null |
| 2 | email2#here.com | 1 | 2020-03-17 | 1 | 2020-03-18 |
| 3 | email3#here.com | 2 | 2020-03-25 | 5 | 2020-03-28 |
try the following:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((select distinct ',
SUM(CASE WHEN tagname=''' + CAST(tagname as varchar(10)) + ''' THEN [hits] ELSE 0 END) AS [' + CAST(tagname as varchar(10)) + '_hits],
MAX(CASE WHEN tagname=''' + CAST(tagname as varchar(10)) + ''' THEN [lastview] ELSE NULL END) AS [' + CAST(tagname as varchar(10)) + '_lastview]'
/*---------------You can add other columns here similar to above--------------*/
FROM #taghits
FOR XML PATH(''),type).value('.','varchar(max)'),1,2,'')
SET #query = 'SELECT userid, email, ' + #Cols + ' FROM #taghits group by userid, email'
print (#query)
exec(#query)
Please see db<>fiddle here.

rows to columns based on first column

I have a student table like below
name | subject | scode
sam | science | 20
sam | computer | 30
sam | language | 50
sam | history | 20
joe | PET | 30
joe | computer | 50
dan | lab | 40
i am looking for out put like below
name | 20 | 30 | 40 | 50
sam | science | computer | null | language
sam | history | null | null | null
joe | null | PET | null | Computer
dan | null | null | lab | null
there are chances a student can add one more subject in future and code is dynamic for that particular student
i tried using for xml however able to get in the format of xml but not able to transpose it. any help in pivoting this as per the output is possible?
I think pivoting in combination with dynamix SQL qould do the trick. I created an approach for this, but this will require some more modification: currently it would group away the second line with scode 20 for student sam. Give it a try - when you get stuck, I will try to modify it a little more:
IF OBJECT_ID('dbo.tbl_test') IS NOT NULL
DROP TABLE tbl_test
GO
CREATE TABLE tbl_test (
sName varchar(25)
,sSubject varchar(25)
,sCode int
)
GO
INSERT INTO tbl_test VALUES
('sam', 'science', 20)
,('sam', 'computer', 30)
,('sam', 'language', 50)
,('sam', 'history', 20)
,('joe', 'PET', 30)
,('joe', 'computer', 50)
,('dan', 'lab', 40)
DECLARE #Cols NVARCHAR(MAX);
DECLARE #Qry NVARCHAR(MAX);
SELECT #Cols = STUFF((SELECT DISTINCT ', [' + CAST(scode AS VARCHAR(5)) + ']'
FROM tbl_test
ORDER BY 1
FOR XML PATH ('')), 1, 1, '')
SET #Qry = 'WITH cte AS(
SELECT sName_GRP, sName, ' + #Cols + '
FROM (
SELECT sName, sCode, sSubject, sName + ' + CHAR(39) + '_' + CHAR(39) + ' + RIGHT(' + char(39) + '0000' + CHAR(39) +' + CAST(ROW_NUMBER() OVER (PARTITION BY sName, sCode ORDER BY sName, sCode) AS VARCHAR(5)), 5) sName_GRP
FROM tbl_test
) AS j
PIVOT
(
MAX(sSubject) FOR sCode in (' + #Cols + ')
) AS p
)
SELECT sName, ' + #Cols + '
FROM cte'
EXEC sp_executesql #Qry

Count by two columns in SQL Server?

Here is an example table:
CREATE TABLE Example
(
LastName varchar(255),
FirstName varchar(255),
HomeAddress varchar(255),
City varchar(255)
);
INSERT INTO Example VALUES ('Murphy', 'James','123 Easy St', 'New York');
INSERT INTO Example VALUES ('Black', 'John','123 Easy St', 'Boston');
INSERT INTO Example VALUES ('Black', 'Amy','123 Easy St', 'Chicago');
INSERT INTO Example VALUES ('Simpson', 'Bill','123 Easy St', 'New York');
INSERT INTO Example VALUES ('Jones', 'James','123 Easy St', 'Chicago');
INSERT INTO Example VALUES ('Black', 'John','123 Easy St', 'Boston');
INSERT INTO Example VALUES ('Murhpy', 'James','123 Easy St', 'New York');
I want to be able to count by two columns, 'LastName' and 'City'. That is - I want this:
Name New York Boston Chicago
-------------------------------------
Jones 0 0 1
Black 0 2 1
Simpson 1 0 0
Murphy 2 0 0
My question is similar to this question. I created the following, which produces very close results:
DECLARE #DynamicPivotQuery AS NVARCHAR(MAX),
#PivotColumnNames AS NVARCHAR(MAX),
#PivotSelectColumnNames AS NVARCHAR(MAX)
--Get distinct values of the PIVOT Column
SELECT #PivotColumnNames = ISNULL(#PivotColumnNames + ',','') + QUOTENAME(City)
FROM (SELECT DISTINCT City FROM Example) AS cat
--Get distinct values of the PIVOT Column with isnull
SELECT #PivotSelectColumnNames
= ISNULL(#PivotSelectColumnNames + ',','')
+ 'ISNULL(' + QUOTENAME(City) + ', 0) AS '
+ QUOTENAME(City)
FROM (SELECT DISTINCT City FROM Example) AS cat
--Prepare the PIVOT query using the dynamic
SET #DynamicPivotQuery =
N'SELECT LastName, ' + #PivotSelectColumnNames + '
FROM Example
pivot(count(City) for City in (' + #PivotColumnNames + ')) as pvt';
--Execute the Dynamic Pivot Query
EXEC sp_executesql #DynamicPivotQuery
BUT, this is not quite what I am after. It produces this:
I tried to add 'GROUP BY LastName' to the end of the query, but it does not work. I get an error:
Column 'pvt.Boston' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
You are so close!
Two things:
Use a subquery/derived table to select only the columns you need for your pivot
Don't misspell Murphy
--Prepare the PIVOT query using the dynamic
SET #DynamicPivotQuery =
N'SELECT LastName, ' + #PivotSelectColumnNames + '
FROM (select LastName, City from Example) e
pivot(count(City) for City in (' + #PivotColumnNames + ')) as pvt';
rextester demo: http://rextester.com/ETJ57985
returns:
+----------+--------+---------+----------+
| LastName | Boston | Chicago | New York |
+----------+--------+---------+----------+
| Black | 2 | 1 | 0 |
| Jones | 0 | 1 | 0 |
| Murphy | 0 | 0 | 2 |
| Simpson | 0 | 0 | 1 |
+----------+--------+---------+----------+
I think simple pivot will give your query results
Select * from (
Select LastName, City from Example ) a
pivot (count(city) for city in ([New York],[Boston],[Chicago])) p
If you change your query as below you will get appropriate results:
--Prepare the PIVOT query using the dynamic
SET #DynamicPivotQuery =
N'Select LastName, ' + #PivotSelectColumnNames + ' from ( SELECT LastName, City
FROM Example ) a
pivot(count(City) for City in (' + #PivotColumnNames + ')) as pvt';
Output as below:
+----------+----------+--------+---------+
| LastName | New York | Boston | Chicago |
+----------+----------+--------+---------+
| Black | 0 | 2 | 1 |
| Jones | 0 | 0 | 1 |
| Murhpy | 1 | 0 | 0 |
| Murphy | 1 | 0 | 0 |
| Simpson | 1 | 0 | 0 |
+----------+----------+--------+---------+
Try this -
DECLARE #DynamicPivotQuery AS NVARCHAR(MAX),
#PivotColumnNames AS NVARCHAR(MAX),
#PivotSelectColumnNames AS NVARCHAR(MAX)
--Get distinct values of the PIVOT Column
SELECT #PivotColumnNames = ISNULL(#PivotColumnNames + ',','') + QUOTENAME(City)
FROM (SELECT DISTINCT City FROM #example) AS cat
--Get distinct values of the PIVOT Column with isnull
SELECT #PivotSelectColumnNames
= ISNULL(#PivotSelectColumnNames + ',','')
+ 'ISNULL(' + QUOTENAME(City) + ', 0) AS '
+ QUOTENAME(City)
FROM (SELECT DISTINCT City FROM #example) AS cat
--Prepare the PIVOT query using the dynamic
SET #DynamicPivotQuery =
N'SELECT LastName, ' + #PivotSelectColumnNames + '
FROM #example
pivot(count(City) for City in (' + #PivotColumnNames + ')) as pvt group by LastName, ' + #PivotColumnNames;
--Execute the Dynamic Pivot Query
EXEC sp_executesql #DynamicPivotQuery
Alternative approach not using PIVOT:
DECLARE #sql NVARCHAR(max);
SET #sql = ''; -- not necessary for CONCAT, but necessary if converting this answer to + style string concatenation
SELECT
#sql = CONCAT(#sql, ', COUNT(CASE WHEN city = ''', City, ''' THEN 1 END) as ', QUOTENAME(City))
FROM
Example
GROUP BY City
SET #sql = CONCAT('SELECT Name', #sql, ' FROM example GROUP BY name')
EXEC sp_executesql #sql

Convert Decimal to String on Pivot

example Code
Declare
#table1 VARCHAR(MAX)
Set #table1 = 'Select * from #tempTbl'
Declare
#List VARCHAR(MAX),
#Pivot VARCHAR(MAX)
Select #List = ISNULL(#List + ',', '') + TrxCd From TransacMaster Where Module = 'CB'
Set #Pivot = '
SELECT ROW_NUMBER() OVER (ORDER BY DebtorCd ASC) As RowIndex, *
FROM (
Select Distinct
c.UserId,
d.Name,
b.TrxCd
SUM(b.Amount) As Amount
From tbl_InvH a
Inner Join tbl_InvD b on a.subCd = b.subCd and a.InvNo = b.InvNo
Left Join tbl_User c On a.UserId = c.UserId
Left Join tbl_Personnel d on c.PersonnelId = d.PersonnelId
Group By c.UserId, d.Name, b.TrxCd
) as s
PIVOT
(
SUM(Amount)
FOR TrxCd IN (' + #List + ')
)AS pvt'
Declare #Result nVarchar(MAX)
Set #Result = #table1 + '
Union All
' + #Pivot
Exec sp_executesql #Result
I want to Convert Field Amount from decimal to String, because after this I want to UNION with another tables, but field amount and field from another table is different type.
I have tried CAST(SUM(Amount) as Varchar) But Error :
'CAST' is not a recognized aggregate function.
I can't Convert on Main Select because Field of TrxCd is Dynamic
Result Of Pivot
RowIndex | UserId | Name | IT01 | IT02 | IT03 | IT04
--------------------------------------------------------------------------------
1 | John | John Ivy | 2,000 | 2,000 | 1,000 | 5,000
2 | Merry | Merry Ish | 1,000 | 1,000 | 1,000 | 6,000
other Table
RowIndex | UserId | Name | Transac1 | Transac2 | Transac3 | Transac4
-------------------------------------------------------------------------------------------------
1 | John | John Ivy | Trx Bank A | Trx Bank B | Trx Bank C | Trx Bank D
What should I do to Convert Field Amount from Pivot.
Because of the dynamic nature, you could take the same #List expression Select #List = ISNULL(#List + ',', '') + TrxCd From TransacMaster Where Module = 'CB' and create a second for the dynamic casting in the main select;
Select #List2 = ISNULL(#List2 + ',', '') + 'cast(' + TrxCd + 'as varchar)' From TransacMaster Where Module = 'CB'
Then use this and additional columns required to replace the asterix in the main select;
'SELECT ROW_NUMBER() OVER (ORDER BY DebtorCd ASC) As RowIndex, UserId, Name,' + #List2 +'....'

SQL Server Pivot is inserting NULL values

Please find the table (OututTable) that needs to be transposed. Here the QuestionID is formed by concatenating two values -[Question:AnswerID]
refID | SessionID | QuestionID | AnswerValue
9000 | 205545715 | [4907] | Good morning
12251 | 205543469 | [10576:16307] | 3
12255 | 205543469 | [10907:17001] | 4
13157 | 205543703 | [10576:16307] | 3
14387 | 205543493 | [10907:17001] | 2
14389 | 205543493 | [10911:17007] | 3
The expected output should have one row per SessionID and the number of columns are dynamic
SessionID | [4097] | [10576:16307] | [10907:17001] | [10911:17007]
205545715 |Good morning | | |
205543469 | | 3 | 4 |
205543703 | | 3 | |
205543493 | | | 2 | 3
I have the output in the above format but there are only NULL values inserted instead of Answer values
I am thinking there might a mismatch in column names. Any help would be great! please let me know.
Code:
set #Questions = (STUFF((SELECT distinct ',[' + cast(i.SessionID as varchar(20)) + ']'
FROM OutputTable i
FOR XML PATH(''), TYPE).value('.','VARCHAR(max)'), 1, 1, ''))
print #Questions
set #SQLQuery = 'select QuestionID,'+ #Questions +' from '+'('+ 'select SessionID,QuestionID,AnswerValue from OutputTable '+ ') p '+ 'PIVOT'+ '('+'max(Answervalue)'+'FOR p.SessionID IN ('+ #Questions +')' +') as pvt'
Great Question! The problem is with the brackets in the QuestionID. While these are necessary for the Pivot Column Aliases, these don't work as string filters.
The code sample also switches QuestionID and SessionID for the expected output.
This code will return the expected output, sorted slightly differently. A temp table is created here to simulate the OutputTable object. This will need to be switched out with the DB Table.
declare
#Questions varchar(max),
#SQLQuery varchar(max)
create table #OutputTable
(
refID int,
SessionID int,
QuestionID varchar(50),
AnswerValue varchar(50)
)
insert into #OutputTable
values
(9000,205545715,'[4907]','Good morning'),
(12251,205543469,'[10576:16307]','3'),
(12255,205543469,'[10907:17001]','4'),
(13157,205543703,'[10576:16307]','3'),
(14387,205543493,'[10907:17001]','2'),
(14389,205543493,'[10911:17007]','3')
set #Questions = (STUFF((SELECT distinct ',' + cast(i.QuestionID as varchar(20))
FROM #OutputTable i
FOR XML PATH(''), TYPE).value('.','VARCHAR(max)'), 1, 1, ''))
set #SQLQuery = '
select SessionID,'+ #Questions +'
from (
select
SessionID,
replace(replace(QuestionID,''['',''''),'']'','''') QuestionID,
AnswerValue
from #OutputTable
) p
PIVOT (
max(Answervalue)
FOR p.QuestionID IN ('+ #Questions +')
) as pvt
order by SessionID desc'
exec(#SQLQuery)