How do I count unique combinations from a detail table? - sql

I need to write a query in SQL to count the number of unique combinations of record. I have a table of items with a child table listing options for each item. Each item may have 0 to x number of options. I want to count how many of each combinations there are. I thought I could take the child table and transpose it using pivot and unpivot, but I haven't figured it out. I then tried creating a list of the combinations, but I don't know how to count the occurrences. Can someone show me how to do this or point me in the right direction?
Here is the table I want to use:
Item | Option
----------------
1 | A
1 | B
2 | B
3 | B
4 | B
4 | C
5 | A
6 | A
6 | B
6 | C
7 | A
7 | B
7 | C
8 | A
8 | B
9 | A
10 | A
10 | B
The results I want are this:
Option 1 | Option 2 | Option 3 | Count
--------------------------------------------
A | B | | 3 * 1, 8, 10
B | | | 2 * 2, 3
B | C | | 1 * 4
A | | | 2 * 5, 9
A | B | C | 2 * 6, 7
This is saying that the combination A and B occurred twice, twice B was the only option picked, B and C were picked together 1 time. (The numbers after the asterisk aren't part of the result, they're just there to show which items are being counted.)
The closest I've come is the query below. It gives me the unique combinations, but doesn't tell me how many times that combination occurred:
SELECT ItemCombo, Count(*) AS ItemComboCount
FROM
(
SELECT
Item
,STUFF((SELECT ',' + CAST(Option AS varchar(MAX))
FROM itemDetail a
WHERE a.Item = b.Item
FOR XML PATH(''), TYPE).value('.', 'VARCHAR(MAX)'),1,1,''
) AS ItemCombo
FROM itemDetail b
) AS Combos
GROUP BY ItemCombo
ORDER BY Count(*) DESC

You should group by in the inner query and also order by option so the concatenated values can be correctly grouped.
SELECT ItemCombo, Count(*) AS ItemComboCount
FROM
(
SELECT
Item
,STUFF((SELECT ',' + CAST(Option AS varchar(MAX))
FROM itemDetail a
WHERE a.Item = b.Item
ORDER BY Option
FOR XML PATH(''), TYPE).value('.', 'VARCHAR(MAX)'),1,1,''
) AS ItemCombo
FROM itemDetail b
GROUP BY item
) AS Combos
GROUP BY ItemCombo
ORDER BY Count(*) DESC

To address the additional requirement you mentioned in the comments I would add a CTE, some more XML processing and dynamic TSQL to Vamsi Prabhala's excellent answer (+1 from my side):
--create test table
create table tmp (Item int, [Option] char(1))
--populate test table
insert into tmp values ( 1, 'A') ,( 1, 'B') ,( 2, 'B') ,( 3, 'B') ,( 4, 'B') ,( 4, 'C') ,( 5, 'A') ,( 6, 'A') ,( 6, 'B') ,( 6, 'C') ,( 7, 'A') ,( 7, 'B') ,( 7, 'C') ,( 8, 'A') ,( 8, 'B') ,( 9, 'A') ,(10, 'A') ,(10, 'B')
declare #count int
declare #loop int = 1
declare #dynamicColums nvarchar(max) = ''
declare #sql nvarchar(max) = ''
--count possible values
select #count = max(c.options_count) from (
select count(*) as options_count from tmp group by item
) c
--build dynamic headers for all combinations
while #loop <= #count
begin
set #dynamicColums = #dynamicColums + ' Parts.value(N''/x['+ cast(#loop as nvarchar(max)) +']'', ''char(1)'') AS [Option ' + cast(#loop as nvarchar(max)) + '],'
set #loop = #loop + 1
end
--build dynamic TSQL statement
set #sql = #sql + ';WITH Splitted'
set #sql = #sql + ' AS ('
set #sql = #sql + ' SELECT ItemComboCount'
set #sql = #sql + ' ,ItemCombo'
set #sql = #sql + ' ,CAST(''<x>'' + REPLACE(ItemCombo, '','', ''</x><x>'') + ''</x>'' AS XML) AS Parts'
set #sql = #sql + ' FROM '
set #sql = #sql + ' ('
set #sql = #sql + ' SELECT ItemCombo, Count(*) AS ItemComboCount'
set #sql = #sql + ' FROM'
set #sql = #sql + ' ('
set #sql = #sql + ' SELECT'
set #sql = #sql + ' Item '
set #sql = #sql + ' ,STUFF((SELECT '','' + CAST([Option] AS varchar(MAX))'
set #sql = #sql + ' FROM tmp a '
set #sql = #sql + ' WHERE a.Item = b.Item'
set #sql = #sql + ' ORDER BY [Option]'
set #sql = #sql + ' FOR XML PATH(''''), TYPE).value(''.'', ''VARCHAR(MAX)''),1,1,'''''
set #sql = #sql + ' ) AS ItemCombo'
set #sql = #sql + ' FROM tmp b'
set #sql = #sql + ' GROUP BY item'
set #sql = #sql + ' ) AS Combos'
set #sql = #sql + ' GROUP BY ItemCombo'
set #sql = #sql + ' ) t'
set #sql = #sql + ' )'
set #sql = #sql + ' SELECT '
set #sql = #sql + #dynamicColums
set #sql = #sql + ' ItemComboCount as [Count]'
set #sql = #sql + ' FROM Splitted'
--execute dynamic TSQL statement
exec(#sql)
Results:
Now if you add another value (for example 'D') with a couple of insert statements:
insert into tmp values ( 1, 'D')
insert into tmp values ( 7, 'D')
you'll see that new columns are dinamically generated:

Related

Pivot Table with Dynamic

My quest might be answered in somewhere here but I couldn't find. So, sorry if I asked in vain.
I have a Table that's populate automatically with precisely date/time in SQL Server and looks like this:
Cod_Analise| Dat_analise | Nom_usuario| Resultado
-----------+-----------------+------------+-----------
1 | 02/20/2019 14:30| John | 4.5
2 | 02/20/2019 14:31| Carl | 60
3 | 02/21/2019 17:25| Carl | 17
2 | 02/19/2019 06:00| Marcus | 58
1 | 02/20/2019 15:40| Jorge | 5.2
2 | 02/21/2019 22:00| John | 58
and I need something like this:
Dat_Analise | 1 | 2 | 3 | Nom_usuario
------------+---+---+---+------------
02/19/2019 | 0 |58 | 0 | Marcus
02/20/2019 |4.9|60 | 0 | (First or Last one)
I need to do a pivot table based in this table where the Columns are Dat_Analise(date), Nom_operador(who did) and "Cod_Analise"(what did). And the rows are "Resultados"(results).
My problem is, i need to group by time period taking avg results for dynamic number of Cod_analises. But I even did the pivot with dynamic columns but I cannot fit the Group By part inside the pivot table.
I try to use a model that i found here and my procedure is like this:
SELECT
A.[RESULTADO],
A.[DAT_ANALISE],
A.[NOM_USUARIO],
B.[NOM_ANALISE]
into #temporaria
FROM
[BSDB_Processo].[dbo].[RESULTADOS_ANALISES] A,
[BSDB_Processo].[dbo].[ANALISES] B
WHERE
A.COD_PROCESSO = #PROCESSO
AND
A.COD_ANALISE = B.COD_ANALISE
AND
NUM_LOTE =#LOTE
Then:
declare #columnsSrc nvarchar(max) = N''
,#columnsDst nvarchar(max) = N''
,#sql nvarchar(max)
,#KeyColumns nvarchar(max) = N'DAT_ANALISE'
,#compatibility int = (
select top 1 compatibility_level from sys.databases
where name = db_name()
order by Name
);
declare #GroupBy nvarchar(max) =
--case when #compatibility <= 90
-- then case when len(#KeyColumns)=0 then '' else 'group by ' + #KeyColumns +
-- ' with rollup' end
-- else case when len(#KeyColumns)=0 then '' else 'group by rollup ('
-- + #KeyColumns + ')' end
-- end
case when len(#KeyColumns)=0 then '' else 'group by ' + #KeyColumns end;
select
#columnsSrc += nchar(10) + N',' + quotename([NOM_ANALISE])
,#columnsDst += nchar(10) + N',sum(isnull(' + quotename([NOM_ANALISE]) + N',0)) as '
+ quotename([NOM_ANALISE])
from (
select [NOM_ANALISE]
from #temporaria
group by [NOM_ANALISE]
) as x
order by x.[NOM_ANALISE]
And:
set #sql = N'
select ' +
case when len(#KeyColumns)=0 then '' else #KeyColumns + ',' end +
STUFF(#columnsDst, 1, 2, '') + '
INTO ##tabelaAnalises
from (
select' + nchar(10) +
case when len(#KeyColumns)=0 then '' else #KeyColumns + ',' end +
' [NOM_ANALISE],[RESULTADO]
from #temporaria
) as j
pivot (
sum([RESULTADO]) for [NOM_ANALISE] in ('
+ stuff(replace(#columnsSrc, ',p.[', ',['), 2, 1, '')
+ ')
) as p' + nchar(10) +
#GroupBy +
';'
>;
--print #sql;
exec sp_executesql #sql;
select * from ##tabelaAnalises
commit
End
Hope you can help me guys and ,again, sorry if i did something wrong with this post. First time using this
Try to see the below query. Please, see UPDATE section of a pivot with column Nom_usuario.
Sample data:
IF OBJECT_ID('tempdb..#SomeTable') IS NOT NULL DROP TABLE #SomeTable
GO
CREATE TABLE #SomeTable
(
Cod_Analise int,
Dat_analise datetime,
Nom_usuario varchar(50),
Resultado numeric(18,1)
)
INSERT INTO #SomeTable
(
Cod_Analise,
Dat_analise,
Nom_usuario,
Resultado
)
VALUES
( 2, '20190219 06:00', 'Marcus', 58)
, ( 1, '20190220 14:30', 'John', 4.5)
, ( 2, '20190220 14:31', 'Carl', 60)
, ( 1, '20190220 15:40', 'Jorge', 5.2)
, ( 3, '20190221 17:25', 'Carl', 17)
, ( 2, '20190221 22:00', 'John', 58)
A query:
SELECT
pvt.Dat_analise
, pvt.[1]
, pvt.[2]
, pvt.[3]
FROM
(SELECT
CONVERT(date, (t.Dat_analise)) Dat_analise
, t.Cod_Analise
, t.Resultado
FROM #SomeTable t) AS t
PIVOT
(
AVG(T.Resultado)
FOR t.Cod_Analise IN ([1], [2], [3])
) pvt
And dynamic version:
declare #cols nvarchar(max);
declare #sql nvarchar(max);
select #cols = stuff((
select distinct
' , ' + CONCAT('[', CONVERT(varchar(10), t.Cod_Analise), ']')
from #SomeTable t
order by 1
for xml path (''), type).value('.','nvarchar(max)')
,1,2,'')
select #sql = '
select p.Dat_Analise,' + #cols + '
from (
SELECT
CONVERT(date, (t.Dat_analise)) Dat_analise
, t.Cod_Analise
, t.Resultado
FROM #SomeTable t
) as t
pivot (AVG(T.Resultado)
FOR t.Cod_Analise in (' + #cols + ') ) p'
exec(#sql);
OUTPUT:
Dat_analise 1 2 3
2019-02-19 NULL 58.000000 NULL
2019-02-20 4.850000 60.000000 NULL
2019-02-21 NULL 58.000000 17.000000
UPDATE:
Use the following code snippet to show a Nom_usuario:
declare #cols nvarchar(max);
declare #sql nvarchar(max);
select #cols = stuff((
select distinct
' , ' + CONCAT('[', CONVERT(varchar(10), t.Cod_Analise), ']')
from SomeTable t
order by 1
for xml path (''), type).value('.','nvarchar(max)')
,1,2,'')
select #sql = '
select *
from
(
SELECT
CONVERT(date, (t.Dat_analise)) Dat_analise
, t.Cod_Analise
, t.Resultado
, MAX(t.Nom_usuario) OVER (PARTITION BY CONVERT(DATE, (t.Dat_analise))) Nom_usuario
FROM SomeTable t
) as t
pivot (AVG(T.Resultado)
FOR t.Cod_Analise in (' + #cols + ') ) p'
exec(#sql);
OUTPUT:
Dat_analise Nom_usuario 1 2 3
2019-02-21 John NULL 58.000000 17.000000
2019-02-20 Jorge 4.850000 60.000000 NULL
2019-02-19 Marcus NULL 58.000000 NULL

Turn Columns into Rows partitioned

I have a table like this:
CFL52_ID CFL52_PRICE CFL51_MILEAGE
------------ --------------- -----------------
1 100000.00 10000
1 200000.00 20000
2 800000.00 10000
2 900000.00 20000
I want to pivot the columns into rows. Mileage was the column title and Price the value. Turning into something like this:
CFL52_ID [10000] [20000]
------------ --------------- -----------------
1 100000.00 200000.00
2 800000.00 900000.00
Notice that id was grouping, prices are pivoted and mileage turning into columns title, Mileage are dynamic -could increment- .
I've tried with no success with this:
SELECT [10000],[20000]
FROM ( SELECT
CFL52_PRICE as indicatorvalue,
CFL51_MILEAGE as indicatorname
FROM [TFL52_PRICES_M] p
INNER JOIN [TFL51_PARAM_MILEAGE] k ON CFL52_CFL51_CODIGO = CFL51_CODIGO
WHERE CFL52_DATES = '2018-07-01 00:00:00.000' AND CFL52_DATEEN= '2018-07-02 00:00:00.000') as a
pivot
(
max(indicatorvalue) for indicatorname in ([10000],[20000])
) p
just use conditional aggregation instead. You also should use aliases to indicate which table a column belongs to.
select CFL52_ID
, [10000] = MAX(case when CFL51_MILEAGE = 10000 then CFL52_PRICE end)
, [20000] = MAX(case when CFL51_MILEAGE = 20000 then CFL52_PRICE end)
FROM [TFL52_PRICES_M] p
INNER JOIN [TFL51_PARAM_MILEAGE] k ON CFL52_CFL51_CODIGO = CFL51_CODIGO
WHERE CFL52_DATES = '2018-07-01 00:00:00.000'
AND CFL52_DATEEN= '2018-07-02 00:00:00.000'
group by CFL52_ID
To manage dynamic titles you need dynamic TSQL:
if OBJECT_ID('dbo.test') is null
create table dbo.test(CFL52_ID varchar(50), CFL52_PRICE decimal(18,2), CFL51_MILEAGE int)
--populate test table
insert into dbo.test values
(1, 100000.00, 10000),
(1, 200000.00, 20000),
(2, 800000.00, 10000),
(2, 900000.00, 20000)
declare #columns nvarchar(max)='' --holds all the numbers that will become column names
declare #sql nvarchar(max)='' --contains the TSQL dinamically generated
--generate dynamic column names
select #columns = #columns + ', [' + cast(CFL51_MILEAGE as varchar(max))+ ']'
from dbo.test
group by CFL51_MILEAGE
--remove first (unnecessary) comma
set #columns = RIGHT(#columns, len(#columns)-2)
--build dynamic TSQL query
set #sql = #sql + ' select piv.[CFL52_ID], ' + #columns
set #sql = #sql + ' from ( '
set #sql = #sql + ' select [CFL52_ID], [CFL52_PRICE], [CFL51_MILEAGE] '
set #sql = #sql + ' from dbo.test '
set #sql = #sql + ' ) src '
set #sql = #sql + ' pivot ( '
set #sql = #sql + ' max([CFL52_PRICE]) '
set #sql = #sql + ' for [CFL51_MILEAGE] in ('+#columns+') '
set #sql = #sql + ' ) piv '
--execute dynamic TSQL query
exec(#sql)
output:
If you add more rows to your input table with CFL51_MILEAGE values of 10000 and 20000, then input and output tables will be:
If you add more rows to your input table introducing new CFL51_MILEAGE values (other than 10000 or 20000), then input and output tables will be:

Transposing a SQL query result

I have a query that produces a table that looks like this:
+----------------+-----------+------------+----------+
| CustomerNumber | FirstName | MiddleName | LastName |
+----------------+-----------+------------+----------+
| 123456 | Test | Test1 | Test2 |
+----------------+-----------+------------+----------+
What I am needing is for the table to look like this:
+----------------+--------+
| CustomerNumber | 123456 |
+----------------+--------+
| FirstName | Test |
| MiddleName | Test1 |
| LastName | Test2 |
+----------------+--------+
This is my current sql query:
SELECT
CustomerNumber,
FirstName,
MiddleName,
LastName
FROM Customers
WHERE CustermerNumber = 123456
Is there a way to complete this? I have been looking at transposing it via unpivot but have not been able to understand how.
You can use apply :
select tt.*
from table t cross apply (
values ('CustomerNumber', CustomerNumber), ('FirstName', FirstName),
('MiddleName', MiddleName), ('LastName', LastName)
) tt (name, v);
With dynamic TSQL pivoting you can also manage multiple rows input:
if OBJECT_ID('Test') is not null
drop table [dbo].[Test]
CREATE TABLE [dbo].[Test](CustomerNumber varchar(10), FirstName varchar(10),
MiddleName varchar(10), LastName varchar(10))
--populate test table
insert into [dbo].[Test] values
(123456, 'Test','Test1','Test2')
, (234567, 'Test_2','Test_21','Test_22')
, (345678, 'Test_3','Test_31','Test_32')
--this variable holds all the customer numbers that will become column names
declare #columns nvarchar(max)=''
--this variable contains the dinamically generated TSQL code
declare #sql nvarchar(max)=''
select #columns = #columns + ', [' + [CustomerNumber] + ']' from [dbo].[Test]
set #columns = RIGHT(#columns, len(#columns)-2)
set #sql = #sql + 'select piv.COL as CustomerNumber, ' + #columns
set #sql = #sql + ' from '
set #sql = #sql + ' ( '
set #sql = #sql + ' select [CustomerNumber], col, val, ord '
set #sql = #sql + ' from [dbo].[Test] '
set #sql = #sql + ' CROSS APPLY ('
set #sql = #sql + ' VALUES (''FirstName'' ,FirstName , 1), '
set #sql = #sql + ' (''MiddleName'',MiddleName, 2), '
set #sql = #sql + ' (''LastName'' ,LastName, 3) '
set #sql = #sql + ' )CS (COL,VAL,ORD) '
set #sql = #sql + ' ) src '
set #sql = #sql + ' pivot ( max(val) for [CustomerNumber] in ('+#columns+') ) piv'
set #sql = #sql + ' order by ord'
exec(#sql)
Sample input and output with one row:
Sample input and output with three rows:

Dynamic pivot a 3 column table

I'm trying to use dynamic pivot to have a column containing dates to be the column names.
I want this table:
App Date Count
Excel 2018-05-01 1
Excel 2018-05-02 1
Excel 2018-05-03 2
Word 2018-05-02 3
Word 2018-05-07 5
Word 2018-05-12 2
Paint 2018-05-07 6
to look like this:
2018-05-01 2018-05-02 2018-05-03 2018-05-07 2018-05-12
Excel 1 1 2 0 0
Word 0 3 0 5 2
Paint 0 0 0 6 0
I can't use a normal pivot as I don't know how many or what the dates will actually be. Each app can have a different number of rows. This table isn't just a SELECT * FROM TABLE either, it's made up of subqueries and CTEs so is a little complicated to work with.
Any help is appreciated. Let me know if you need more information.
Using dynamic TSQL:
if OBJECT_ID('dbo.test') is null
create table dbo.test(App varchar(50), [Date] varchar(50), [Count] int)
truncate table dbo.test
insert into dbo.test values
('Excel', '2018-05-01', 1),
('Excel', '2018-05-02', 1),
('Excel', '2018-05-03', 2),
('Word ', '2018-05-02', 3),
('Word ', '2018-05-07', 5),
('Word ', '2018-05-12', 2),
('Paint', '2018-05-07', 6)
declare #dates nvarchar(max)='' --holds all the dates that will become column names
declare #dates_aliases nvarchar(max)='' --holds the headers without NULL values
declare #sql nvarchar(max)='' --contains the TSQL dinamically generated
select #dates = #dates + ', [' + CONVERT(char(10), [date],126)+ ']' from dbo.test
group by [date]
select #dates_aliases = #dates_aliases + ', isnull(['
+ CONVERT(char(10), [date],126)+ '], 0) as ['
+ CONVERT(char(10), [date],126)+ ']'
from dbo.test group by [date]
set #dates = RIGHT(#dates, len(#dates)-2)
set #dates_aliases = RIGHT(#dates_aliases, len(#dates_aliases)-2)
set #sql = #sql + ' select piv.[App], ' + #dates_aliases
set #sql = #sql + ' from '
set #sql = #sql + ' ( '
set #sql = #sql + ' select [App], [Date], [Count] '
set #sql = #sql + ' from dbo.test '
set #sql = #sql + ' ) src '
set #sql = #sql + ' pivot '
set #sql = #sql + ' ( '
set #sql = #sql + ' max([Count]) '
set #sql = #sql + ' for [Date] in ('+#dates+') '
set #sql = #sql + ' ) piv '
exec(#sql)
Results:
Try this:
SELECT A.*
INTO #TEMP
FROM
(
SELECT 'Excel' as app,'2018-05-01' as 'Date',1 as 'Count'
UNION ALL
SELECT 'Excel' as app,'2018-05-02' as 'Date',1 as 'Count'
UNION ALL
SELECT 'Excel' as app,'2018-05-03' as 'Date',2 as 'Count'
UNION ALL
SELECT 'Word' as app,'2018-05-02' as 'Date', 3 as 'Count'
UNION ALL
SELECT 'Word' as app,'2018-05-07' as 'Date', 5 as 'Count'
UNION ALL
SELECT 'Word' as app,'2018-05-12' as 'Date', 2 as 'Count'
UNION ALL
SELECT 'Paint' as app,'2018-05-07' as 'Date', 6 as 'Count'
) as A
ANSWER:
DECLARE #SQL VARCHAR(MAX)
DECLARE #Columns VARCHAR(MAX) = ''
DECLARE #Columns2 VARCHAR(MAX) = ''
SELECT #Columns = #Columns + '[' + a.[Column] + '], '
FROM
(SELECT DISTINCT [date] as [Column]
FROM #TEMP) as a
SELECT #Columns2 = #Columns2 + 'ISNULL([' + a.[Column] + '],0) as [' + a.[column] +'], '
FROM
(
SELECT DISTINCT [date] as [Column]
FROM #TEMP
) as a
SET #Columns2 = Left(#Columns2, Len(#Columns2) - 1)
SET #Columns = Left(#Columns, Len(#Columns) - 1)
SET #SQL = 'SELECT app, ' + #Columns2
+ ' FROM #TEMP PIVOT (Avg (Count) FOR Date IN ('
+ #Columns
+ ')) AS pt '
--PRINT #Columns
EXEC( #SQL )

How can I dynamically create columns in SQL select statement

I have 3 tables. Team, Option, OptionTeam.
The Team holds a TeamId and Name
Option holds OptionId, OptionGroup
OptionTeam holds TeamId, OptionId, OptionGroup
select a.TeamId, a.Name
(select count(*) from OptionTeam ot where ot.TeamId=a.TeamId and ot.OptionGroup=4) as Option1,
(select count(*) from OptionTeam ot where ot.TeamId=a.TeamId and ot.OptionGroup=5) as Option2,
(select count(*) from OptionTeam ot where ot.TeamId=a.TeamId and ot.OptionGroup=6) as Option3,
(select count(*) from OptionTeam ot where ot.TeamId=a.TeamId and ot.OptionGroup=11) as Option4
from Team a
I want to get a list of Teams, and extra columns indicating how many options of each group are connected to each Team. This is done by the above query, but I want to replace the 4,5,6,11 with values of OptionGroup from a table Option.
It has to be dynamic, because there might be a new OptionGroup in the future, and I want the stored procedure to be able to handle it.
Sample data:
Team
TeamId
1
2
3
Option
OptionId | OptionGroup
11 | 4
12 | 5
13 | 4
14 | 4
15 | 5
OptionTeam
TeamId | OptionId | OptionGroup
1 | 11 | 4
1 | 13 | 4
2 | 12 | 5
2 | 14 | 4
3 | 15 | 5
And the list I want to get is
TeamId | Group4 (OptionGroup=4) | Group5 (OptionGroup=5)
1 | 2 | 0
2 | 1 | 1
3 | 0 | 1
You'll need a dynamic pivot to do this. Here's the stored procedure:
CREATE PROC [dbo].[pivotsp]
#query AS NVARCHAR(MAX), -- The query, can also be the name of a table/view.
#on_rows AS NVARCHAR(MAX), -- The columns that will be regular rows.
#on_cols AS NVARCHAR(MAX), -- The columns that are to be pivoted.
#agg_func AS NVARCHAR(257) = N'SUM', -- Aggregate function.
#agg_col AS NVARCHAR(MAX), -- Column to aggregate.
#output AS NVARCHAR(257) = N'', -- Table for results
#debug AS bit = 0 -- 1 for debugging
AS
-- Example usage:
-- exec pivotsp
-- 'select * from vsaleshistory',
-- 'market,marketid,family,familyid,Forecaster,Forecasterid,product,productid',
-- 'month',
-- 'sum',
-- 'ku',
-- '##sales'
-- Input validation
IF #query IS NULL OR #on_rows IS NULL OR #on_cols IS NULL
OR #agg_func IS NULL OR #agg_col IS NULL
BEGIN
RAISERROR('Invalid input parameters.', 16, 1);
RETURN;
END
-- Additional input validation goes here (SQL Injection attempts, etc.)
BEGIN TRY
DECLARE
#sql AS NVARCHAR(MAX),
#cols AS NVARCHAR(MAX),
#newline AS NVARCHAR(2);
SET #newline = NCHAR(13) + NCHAR(10);
-- If input is a valid table or view
-- construct a SELECT statement against it
IF COALESCE(OBJECT_ID(#query, N'U'),
OBJECT_ID(#query, N'V')) IS NOT NULL
SET #query = N'SELECT * FROM ' + #query;
-- Make the query a derived table
SET #query = N'(' + #query + N') AS Query';
-- Handle * input in #agg_col
IF #agg_col = N'*'
SET #agg_col = N'1';
-- Construct column list
SET #sql =
N'SET #result = ' + #newline +
N' STUFF(' + #newline +
N' (SELECT N'','' + quotename( '
+ 'CAST(pivot_col AS sysname)' +
+ ') AS [text()]' + #newline +
N' FROM (SELECT DISTINCT('
+ #on_cols + N') AS pivot_col' + #newline +
N' FROM' + #query + N') AS DistinctCols' + #newline +
N' ORDER BY pivot_col' + #newline +
N' FOR XML PATH(''''))' + #newline +
N' ,1, 1, N'''');'
IF #debug = 1
PRINT #sql
EXEC sp_executesql
#stmt = #sql,
#params = N'#result AS NVARCHAR(MAX) OUTPUT',
#result = #cols OUTPUT;
IF #debug = 1
PRINT #cols
-- Create the PIVOT query
IF #output = N''
begin
SET #sql =
N'SELECT *' + #newline +
N'FROM (SELECT '
+ #on_rows
+ N', ' + #on_cols + N' AS pivot_col'
+ N', ' + #agg_col + N' AS agg_col' + #newline +
N' FROM ' + #query + N')' +
+ N' AS PivotInput' + #newline +
N' PIVOT(' + #agg_func + N'(agg_col)' + #newline +
N' FOR pivot_col IN(' + #cols + N')) AS PivotOutput;'
end
ELSE
begin
set #sql = 'IF EXISTS (SELECT * FROM tempdb.sys.objects WHERE ' +
'name = ''' + #output + ''' AND type = N''U'') DROP TABLE tempdb.' + #output
EXEC sp_executesql #sql;
SET #sql =
N'SELECT * INTO ' + #output + #newline +
N'FROM (SELECT '
+ #on_rows
+ N', ' + #on_cols + N' AS pivot_col'
+ N', ' + #agg_col + N' AS agg_col' + #newline +
N' FROM ' + #query + N')' +
+ N' AS PivotInput' + #newline +
N' PIVOT(' + #agg_func + N'(agg_col)' + #newline +
N' FOR pivot_col IN(' + #cols + N')) AS PivotOutput;'
end
IF #debug = 1
PRINT #sql
EXEC sp_executesql #sql;
END TRY
BEGIN CATCH
DECLARE
#error_message AS NVARCHAR(2047),
#error_severity AS INT,
#error_state AS INT;
SET #error_message = ERROR_MESSAGE();
SET #error_severity = ERROR_SEVERITY();
SET #error_state = ERROR_STATE();
RAISERROR(#error_message, #error_severity, #error_state);
RETURN;
END CATCH
With that, it's easy to pivot on a variable number of columns:
EXEC pivotsp
'SELECT TeamID, OptionGroup, OptionID AS Options FROM OptionTeam',
'Teamid', -- Row headers
'optiongroup', -- item to aggregate
'count', -- aggregation function
'optiongroup', -- Column header
'##temp' -- output table name
SELECT * FROM ##temp
Results:
Teamid 4 5
1 2 0
2 1 1
3 0 1
SELECT a.*, o.optionGroup, COUNT(*)
FROM team a
CROSS JOIN
option o
JOIN OptionTeam ot
ON ot.teamId = a.teamId
AND ot.optionGroup = o.optionGroup
WHERE o.OptionId = #id
GROUP BY
a.teamId, o.optionGroup
select teamID,
sum(case when optionGroup = 4 then 1 else 0 end) as optionGroup4,
sum(case when optionGroup = 5 then 1 else 0 end) as optionGroup5,
from optionteam
group by teamID
to add more optiongroups without changing the code, try grouping by that field:
select teamID,optionGroup,count(optionID) as optionCount
from optionteam
group by teamID,optionGroup