T-SQL : can this data be displayed using PIVOT without an aggregate - sql-server

I have two tables that I've been asked to create a PIVOT table with however there is no aggregate and after messing around with this I'm unsure this is possible.
Table A looks like:
ID CustomerID ItemCode CustomerItemCode CustomerItemDescription
-------------------------------------------------------------------
1 1 123 321 product x
2 2 123 456 product x
3 1 987 789 product y
4 2 987 567 product y
Table B:
CustomerID CustomerName
------------------------
1 Customer ABC
2 Customer XYZ
What the result should look like is:
ItemCode CustomerItemDescription Customer ABC Customer XYZ
-------------------------------------------------------------
123 product x 321 456
987 product y 789 567
Because customers could always be added I'm trying to make this as dynamic as possible so I've gotten as far as setting up the customer columns and creating a temp table with the data but without an aggregate I'm unsure how to make this display properly.

That is an interesting one and yes it is possible.
Here two possibilities to achieve this.
Option 1 (the simpler to read and understand):
You can use this if you know the names of the Customers and can generate parts of the required query outside of SQL and execute the full in the end.
SELECT
ItemCode,
CustomerItemDescription,
max(case when (CustomerName='Customer ABC') then CustomerItemCode else NULL end) as 'Customer ABC',
max(case when (CustomerName='Customer XYZ') then CustomerItemCode else NULL end) as 'Customer XYZ'
FROM Table_A
JOIN Table_B ON Table_A.CustomerID = Table_B.CustomerID
GROUP BY ItemCode, CustomerItemDescription
ORDER BY ItemCode;
SQL-Fiddle for Option 1: https://www.db-fiddle.com/f/eEEDSao6Qy9v6um8N4zjqn/0
Option 2 (dynamic but hard to understand):
Here you generates dynamic the query inside of the SQL using variabel and execute this in the end.
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'max(case when (CustomerName = ''',
CustomerName,
''') then CustomerItemCode else NULL end) as ''',
CustomerName,''''
)
) INTO #sql
FROM Table_A
JOIN Table_B ON Table_A.CustomerID = Table_B.CustomerID;
SET #sql = CONCAT('SELECT ItemCode, CustomerItemDescription, ', #sql, '
FROM Table_A
JOIN Table_B ON Table_A.CustomerID = Table_B.CustomerID
GROUP BY ItemCode, CustomerItemDescription
ORDER BY ItemCode');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SQL-Fiddle for Option 2: https://www.db-fiddle.com/f/xkAaZM9Z7Wszh89PMYNYWP/0
Check also this article for additional infos: https://ubiq.co/database-blog/display-row-values-columns-mysql/
SQL-Fiddle for the Article: https://www.db-fiddle.com/f/qZedfre2FtowmsxRB5TWt8/0

Concept is right - use dynamic sql. If you are specifically working with T-SQL and SQL Server 2012+, try the following:
declare #fldlist varchar(max) = stuff((
select ', '+ concat('max(case when customername = ''', CustomerName , ''' then CustomerItemCode else NULL end) [', CustomerName , ']')
from Table_B
JOIN Table_B ON Table_A.CustomerID = Table_B.CustomerID for xml path('')),1,1,'')
/*
need to use a global temp table because dynamic sql
*/
declare #tbname varchar(255) = '##UnlikelyToBeUsedGlobalTempTblName';
/*
there is an 8000 char limit for dynamic sql in sql server. If there are too many customers with long names, you will need to split tables.
*/
declare #sql varchar(8000) = CONCAT('
if object_id(''tempdb..'''+#tbname+''') is not null drop table ',#tbname,'
SELECT ItemCode, CustomerItemDescription, ', #fldlist, ' into ' , #tbname , '
FROM Table_A
JOIN Table_B ON Table_A.CustomerID = Table_B.CustomerID
GROUP BY ItemCode, CustomerItemDescription
ORDER BY ItemCode');
exec(#sql)
select * from ##UnlikelyToBeUsedGlobalTempTblName

Related

Using Dynamic SQL to concatenate two columns into a table header with their respective values

I have a two tables, GuestList and CustomerList. I joined them and used dynamic SQL pivot table to convert the 'city' column from GuestList table into rows or table headers and the average population would be displayed under each city. So after executing the query at the bottom, my table header looks like this and the average population is displayed under each city.
Time| Atlanta| Los Angeles | New York | Denver| Minneapolis
But I want my table header to look like this. Basically 'Id' has four values, 1, 2,3,4 and each city has all these four ID's. I could not add all the cities but rest of the cities will also be like this.
Time| Atlanta_1|| Atlanta_2|| Atlanta_3|| Atlanta_4|
Could someone help me on this by writing the rest of the query on how to concatenate the two columns in the GuestList table and put their respective population underneath it.
declare #ColumnNames nvarchar(max) = ''
declare #SQL nvarchar(max) = ''
select #ColumnNames += QUOTENAME(a.address) + ','
from GuestList as a
inner join CustomerList as b
on a.Id = b.Id
group by a.address
order by a.address
set #ColumnNames = left(#ColumnNames, LEN(#ColumnNames)-1 )
set #SQL= N'
select Time,' + #ColumnNames + '
from
(
select a.Time, a.city, a.population, b.Gender
from GuestList as a
inner join CustomerList as b
on a.Id = b.Id
inner join Split(#city, '','') as c
on a.city = c.Data
where a.city = c.Data
) as SourceTable
pivot
(avg(population)
for city
in ('
+ #ColumnNames +
')) as PivotTable
order by Time'
execute sp_executesql #SQL,
N'#city nvarchar(max)'
,#city = 'Atlanta,Los Angeles,New York'
"FOR XML PATH" to the rescue. Here's the basic idea...
CREATE TABLE #ids(id NVARCHAR(20));
INSERT #ids VALUES ('1'), ('2'), ('3'), ('4');
CREATE TABLE #cities(city NVARCHAR(20));
INSERT #cities VALUES ('Atlanta'), ('Los Angeles'), ('New York'), ('Denver'), ('Minneapolis');
SELECT 'Time' + (
SELECT '|' + city + '_' + id
FROM #cities
CROSS JOIN #ids
ORDER BY city, id
FOR XML PATH('')
) ;
... yields the result ...
Time|Atlanta_1|Atlanta_2|Atlanta_3|Atlanta_4|Denver_1|Denver_2|Denver_3|Denver_4|Los Angeles_1|Los Angeles_2|Los Angeles_3|Los Angeles_4|Minneapolis_1|Minneapolis_2|Minneapolis_3|Minneapolis_4|New York_1|New York_2|New York_3|New York_4

Exporting dynamic column pivot result (Dynamic SQL resultset with varying number of columns) into excel file using SSIS

I have a dynamic pivot sql script (the pivoted columns are dynamic). I wanted to export the result set into excel file and email it with send mail task in ssis. Does anyone know how to do that? Below is my Dynamic column pivot sql script
Declare #SQL varchar(max)
Select #SQL = Stuff((Select Distinct ',' + QuoteName([Response Code]) From YourTable Order by 1 For XML Path('')),1,1,'')
Select #SQL = 'Select [Employee],' + #SQL + '
From (
Select [Employee],[Response Code],Cnt=1,Lvl=0 from YourTable
Union All
Select [Employee],[Response Code],Cnt=0,Lvl=0 from (Select Distinct [Employee] from YourTable) A Join (Select Distinct [Response Code] from YourTable) B on 1=1
Union All
Select ''Total'',[Response Code],count(*),1 From YourTable Group By [Response Code]
) A
Pivot (sum(Cnt) For [Response Code] in (' + #SQL + ') ) p'
Exec(#SQL);
The above script will return the table like this
Employee ptb ulm vml wrn
Emp A 0 0 2 1
Emp B 0 2 0 1
Emp C 1 0 1 0
Total 1 2 3 2
I need to export the above result table in to excel file. I know how to do if the column is static using SSIS ;but I am struggling with the dynamic column pivot. Could anyone please help me. Thank you very much for your time and help

What is SQL Query (T-SQL) which counts transaction status for particular Name against all categorical statuses available?

I am trying to get a table that looks like:
Columns : [District] [Name] [Status1] [Status2] [Status3]
Data: DistrictA MrChan 1 1 1
Data: DistrictB MrFoo 1 0 2
Data: DistrictB MsLucy 0 1 0
(sorry the table turns out unexpected after posting)
select StatusID, StatusCode from
BookingStatus retrieves all categorical statuses Status1, Status2, Status3
select userid, DistrictA, StatusID from
UnitBooking retrieve multiple rows which represent booking transactions.
In example above Ms Lucy has done 1 booking she would have 1 row in UnitBooking. Mr Foo have 3 rows and Mr Chan also have 3 rows.
Example Data:
select [userid], [username], [District], [StatusID] from UnitBooking
[1],[MrChan],[DistrictA],[1]
[1],[MrChan],[DistrictA],[2]
[1],[MrChan],[DistrictA],[3]
[2],[MrFoo],[DistrictB],[1]
[2],[MrFoo],[DistrictB],[3]
[2],[MrFoo],[DistrictB],[3]
[3],[MsLucy],[DistrictB],[2]
select [StatusID], [StatusCode] from BookingStatus
[1],[Status1]
[2],[Status2]
[3],[Status3]
What is the T-SQL that produces the result set?
Thanks a lot
select
district,
username,
sum(case when bs.statusid=1 then 1 else 0 end) 'status1',
sum(case when bs.statusid=2 then 1 else 0 end) 'status2',
sum(case when bs.statusid=3 then 1 else 0 end) 'status13'
from
unitbooking ub
join
BookingStatus Bs
on bs.statusid=ub.statusid
group by district,username
I have come up with an SQL that works for my report but I am not good at what the STUFF function and XML PATH part do, except it works. Here goes:
DECLARE #cols AS NVARCHAR(MAX),#query AS NVARCHAR(MAX)
SELECT #cols = STUFF(( SELECT ', SUM(CASE WHEN ub.BookingStatus='
+ Cast(ItemID as nvarchar(128))
+ ' THEN 1 ELSE 0 END) '
+ QUOTENAME(StatusCode)
FROM customtable_BookingStatus
--WHERE ItemID Not In (1)
GROUP BY StatusCode
,ItemID
ORDER BY ItemID
FOR XML PATH('')
,TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 1, '')
SET #query = 'SELECT usr.FullName, usi.District, '
+'cnt.[OPEN] as [DUALLANGUAGE], cnt.BOOKED, cnt.CONFIRMED, cnt.SOLD from customtable__User usr
left join customtable_Public_User_Info usi on usi.UserID = usr.UserID
inner join '
+'(SELECT ub.UserID,' + #cols
+'from customtable_UnitBooking ub
join customtable_BookingStatus bs on bs.ItemID = ub.BookingStatus
group by ub.UserId, ub.BookingStatus) '
+'cnt on cnt.UserID = usr.UserID'
--Print #query
EXEC sp_executesql #query;
Here's some value added conveniences:
-Support new entries in BookingStatus table dynamically. Still need to explicitly state the name of new column in final #query.
-Technically the names of new column could also be dynamic inside #query by adding an additional STUFF function with similar signature that retrieves plain comma separated columns. Here's the snippet:
DECLARE #colsForQuery AS NVARCHAR(MAX)
SELECT #colsForQuery = STUFF(( SELECT ',cnt.' + QUOTENAME(StatusCode)
FROM customtable_BookingStatus
--WHERE ItemID Not In (1)
GROUP BY StatusCode
,ItemID
ORDER BY ItemID
FOR XML PATH('')
,TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 1, '')
produces this:
cnt.[OPEN],cnt.[BOOKED],cnt.[SOLD],cnt.[CONFIRMED]
-My report has dual language but not easy if all columns are entirely dynamic
-Omit columns using 'WHERE ItemID Not In (1)' or remove it in #query
-Watch out for columns that clashes with T-SQL keyword, example I must encase [OPEN] because its keyword
Overall I think TheGameiswar is correct answer. I am just extending his solution based on the URL suggested inside comments.

Display multiple values from two tables in one row in SQL Server

I have the following two tables
TableA Table B
id bid bname btitle
---- ------------------------------
1 1 john titlejohn
2 1 william titlewilliam
3 1 george titlegeorge
2 bill titlebill
3 kyle titlekyle
3 seb titleseb
I need a query in SQL Server which displays the following output:
id name title
1 john,william,george titlejohn,titlewilliam,titlegeorgw
2 bill titlebill
3 kyle,seb titlekyle,titleseb
Please help.
select id, name = stuff(n.name, 1, 1, ''), title = stuff(t.title, 1, 1, '')
from TableA a
outer apply
(
select ',' + bname
from TableB x
where x.bid = a.id
for xml path('')
) n (name)
outer apply
(
select ',' + btitle
from TableB x
where x.bid = a.id
for xml path('')
) t (title)
Here's one solution. It only handles bname but you can extend it to handle btitle. Concatenating column values for a given key is not a natural thing in SQL so you need a trick to loop through the table extracting each row with same key. The trick is to create a memory table with an identity column (n say) which autoincrements on each insert. You can loop through then, picking n=1, then n=2, etc to build up the string.
create function tbl_join_name( #id int)
returns varchar(max)
as
begin
declare #tbl table (n int identity(1,1), name varchar(max), title varchar(max))
insert #tbl( name, title )
select bname, btitle from TableB where bid = #id
declare #n int = 1, #name varchar(max) = '', #count int = (select count(*) from #tbl)
while #n <= #count begin
set #name = #name + (case #name when '' then '' else ',' end) + (select name from #tbl where n = #n)
set #n = #n + 1
end
return #name
end
go
select id, tbl_join_name(id) as bname --, tbl_join_title(id) as btitle
from TableA
It's not very efficient, though. Tested with Sql Server 2008 R2.
Another way:
SELECT A.id,
STUFF((SELECT ','+bname
FROM TableB B
WHERE B.bid = A.id
FOR XML PATH('')),1,1,'') as name,
STUFF((SELECT ','+btitle
FROM TableB B
WHERE B.bid = A.id
FOR XML PATH('')),1,1,'') as title
FROM TableA A
Output:
id name title
1 john,william,george titlejohn,titlewilliam,titlegeorge
2 bill titlebill
3 kyle,seb titlekyle,titleseb

Convert a column values to fields and retain other values in sql server

Can someone help me in converting the below mentioned original table to table required? I think I have done it before, it's just I am unable to do it now. Thanks for the help.
Original Table
year school program count
2014 A XYZ 3
2014 A DEF 1
2014 B XYZ 2
2014 B DEF 4
2014 B GHI 5
2014 C XYZ 3
Table Required
YEAR SCHOOL XYZ DEF GHI
2014 A 3 1 0
2014 B 2 4 5
2014 C 3 0 0
Try Dynamic Pivot,
CREATE TABLE #Your_Table
(
YEAR INT,
SCHOOL CHAR(1),
PROGRAM VARCHAR(10),
COUNT INT
)
INSERT INTO #Your_Table
VALUES (2014,'A','XYZ',3),
(2014,'A','DEF',1),
(2014,'B','XYZ',2),
(2014,'B','DEF',4),
(2014,'B','GHI',5),
(2014,'C','XYZ',3)
DECLARE #DynamicPivotQuery AS NVARCHAR(MAX)
DECLARE #ColumnName AS NVARCHAR(MAX)
DECLARE #TempColumnname AS NVARCHAR(MAX)
--Get distinct values of the PIVOT Column
SELECT #ColumnName = ISNULL(#ColumnName + ',', '')
+ Quotename(PROGRAM)
FROM (SELECT DISTINCT PROGRAM
FROM #Your_Table) AS Courses
SELECT #TempColumnname = ISNULL(#TempColumnname + ',', '')
+ 'ISNULL(' + Quotename(PROGRAM) + ',0) AS '+Quotename(PROGRAM)
FROM (SELECT DISTINCT PROGRAM
FROM #Your_Table) AS Courses
--PRINT #TempColumnname
--Prepare the PIVOT query using the dynamic
SET #DynamicPivotQuery = N'SELECT Year, School, ' + #TempColumnname
+ 'FROM #Your_Table PIVOT(SUM(Count)
FOR PROGRAM IN (' + #ColumnName + ')) AS PVTTable'
--Execute the Dynamic Pivot Query
EXEC SP_EXECUTESQL
#DynamicPivotQuery
You can try using this query. It first computes a temporary table containing the XYZ, DEF, and GHI count for each record. Then the outer query aggregates these counts for each year/school combination.
SELECT t.year, t.school,
SUM(t.XYZ) AS XYZ, SUM(t.DEF) AS DEF, SUM(t.GHI) AS GHI
FROM
(
SELECT year, school,
CASE WHEN program = 'XYZ' THEN count ELSE 0 END AS XYZ
CASE WHEN program = 'DEF' THEN count ELSE 0 END AS DEF
CASE WHEN program = 'GHI' THEN count ELSE 0 END AS GHI
FROM table
) t
GROUP BY t.year, t.school
Use PIVOT:
SELECT
YEAR,
SCHOOL,
COALESCE([XYZ], 0) [XYZ],
COALESCE([DEF], 0) [DEF],
COALESCE([GHI], 0) [GHI]
INTO
Table_Required
FROM
Original_table
PIVOT
(SUM([count])
FOR program
in([XYZ],[DEF],[GHI])
)AS p
ORDER BY YEAR, SCHOOL

Resources