Pivot Table and Concatenate Columns - sql

I have a database in the following format:
ID TYPE SUBTYPE COUNT MONTH
1 A Z 1 7/1/2008
1 A Z 3 7/1/2008
2 B C 2 7/2/2008
1 A Z 3 7/2/2008
Can I use SQL to convert it into this:
ID A_Z B_C MONTH
1 4 0 7/1/2008
2 0 2 7/2/2008
1 0 3 7/2/2008
So, the TYPE, SUBTYPE are concatenated into new columns and COUNT is summed where the ID and MONTH match.
Any tips would be appreciated. Is this possible in SQL or should I program it manually?
The database is SQL Server 2005.
Assume there are 100s of TYPES and SUBTYPES so and 'A' and 'Z' shouldn't be hard coded but generated dynamically.

SQL Server 2005 offers a very useful PIVOT and UNPIVOT operator which allow you to make this code maintenance-free using PIVOT and some code generation/dynamic SQL
/*
CREATE TABLE [dbo].[stackoverflow_159456](
[ID] [int] NOT NULL,
[TYPE] [char](1) NOT NULL,
[SUBTYPE] [char](1) NOT NULL,
[COUNT] [int] NOT NULL,
[MONTH] [datetime] NOT NULL
) ON [PRIMARY]
*/
DECLARE #sql AS varchar(max)
DECLARE #pivot_list AS varchar(max) -- Leave NULL for COALESCE technique
DECLARE #select_list AS varchar(max) -- Leave NULL for COALESCE technique
SELECT #pivot_list = COALESCE(#pivot_list + ', ', '') + '[' + PIVOT_CODE + ']'
,#select_list = COALESCE(#select_list + ', ', '') + 'ISNULL([' + PIVOT_CODE + '], 0) AS [' + PIVOT_CODE + ']'
FROM (
SELECT DISTINCT [TYPE] + '_' + SUBTYPE AS PIVOT_CODE
FROM stackoverflow_159456
) AS PIVOT_CODES
SET #sql = '
;WITH p AS (
SELECT ID, [MONTH], [TYPE] + ''_'' + SUBTYPE AS PIVOT_CODE, SUM([COUNT]) AS [COUNT]
FROM stackoverflow_159456
GROUP BY ID, [MONTH], [TYPE] + ''_'' + SUBTYPE
)
SELECT ID, [MONTH], ' + #select_list + '
FROM p
PIVOT (
SUM([COUNT])
FOR PIVOT_CODE IN (
' + #pivot_list + '
)
) AS pvt
'
EXEC (#sql)

select id,
sum(case when type = 'A' and subtype = 'Z' then [count] else 0 end) as A_Z,
sum(case when type = 'B' and subtype = 'C' then [count] else 0 end) as B_C,
month
from tbl_why_would_u_do_this
group by id, month
You change requirements more than our marketing team! If you want it to be dynamic you'll need to fall back on a sproc.

Related

Error during PIVOT in SQL Server 2014

I am trying to create pivot where row is Brand and SKU, sum is qty for column YYMM, but I'm not sure why it is throwing an error. Please help.
Code:
DECLARE #TABLE TABLE
(
SKU VARCHAR(10),
YYMM VARCHAR(50),
BRAND VARCHAR(50),
QTY INT
)
INSERT INTO #TABLE
SELECT '104591168', '2015-January', 'abott', 2 UNION ALL
SELECT '104580709', '2016-January', 'GSK', 2 UNION ALL
SELECT '104720038', '2017-January', 'RANBAXCY', 2 UNION ALL
SELECT '10467011A', '2018-January', 'abott', 2 UNION ALL
SELECT '104590691', '2019-January', 'abott', 10
Pivot code:
select *
from
(select
BRAND, sku, QTY, YYMM
from #TABLE) src
pivot
(sum(QTY)
for [Year Month]
You forgot to finish your query.
Please see the bottom of MSDN article for PIVOT examples.
I would guess that you wanted your query to look something like this:
SELECT *
FROM(
SELECT BRAND, sku, QTY, YYMM
FROM #TABLE
) AS src
PIVOT(
sum(QTY)
for [YYMM] IN( [2015-January], [2016-January], [2017-January] /* add other moneths here */ )
) AS Pivoted
We can make it as Dynamic sql
DECLARE #Column NVARCHAR(max),
#Column2 NVARCHAR(max),
#Sql NVARCHAR(max)
SELECT #Column = Stuff((SELECT DISTINCT ', '
+ Quotename(Cast(yymm AS VARCHAR(20)))
FROM #table
FOR xml path ('')), 1, 1, '')
SELECT #Column2 = Stuff((SELECT DISTINCT ', ' + 'ISNULL('
+ Quotename(Cast(yymm AS VARCHAR(20)))
+ ','
+ '''0''' + ') AS '
+ Quotename(Cast(yymm AS VARCHAR(20)))
FROM #table
FOR xml path ('')), 1, 1, '')
SET #Sql='
SELECT BRAND ,sku,'+#Column2+'
FROM(
SELECT *
FROM #TABLE
) AS src
PIVOT(
sum(QTY)
for [YYMM] IN('+#Column+')
) AS Pivoted
'
PRINT #Sql
EXEC(#Sql)
Result
BRAND sku 2015-January 2016-January 2017-January 2018-January 2019-January
--------------------------------------------------------------------------------------------------------
abott 104590691 0 0 0 0 10
abott 104591168 2 0 0 0 0
abott 10467011A 0 0 0 2 0
GSK 104580709 0 2 0 0 0
RANBAXCY 104720038 0 0 2 0 0

form a query as per column values

I am using sql server 2012 and I have a table like this:
FieldName FieldValue
DivisionId 1
DivisionId 2
DivisionId 3
CompanyId 2
CompanyId 3
LocationId 1
What i want is concatenate columns and form a where clause query like this
(DivisionId=1 OR DivisionId=2 OR DivisionId=3) AND
(CompanyId=2 OR CompanyId=3) AND
(LocationId = 1)
What I was able to figure out is, I need to concatenate columns values like this
DECLARE #Query VARCHAR(MAX)
SELECT #Query =
ISNULL(#Query,'') + IIF(#Query IS NOT NULL, ' AND ', '') + CONCAT(DF.FieldName,'=',DA.FieldValue)
FROM TABLE
SELECT #Query;
But this code will not handle OR condition.
Try following solution:
DECLARE #eav TABLE (
FieldName NVARCHAR(128) NOT NULL,
FieldValue VARCHAR(50) NOT NULL
)
INSERT #eav (FieldName, FieldValue)
SELECT 'DivisionId', 1 UNION ALL
SELECT 'DivisionId', 2 UNION ALL
SELECT 'DivisionId', 3 UNION ALL
SELECT 'CompanyId ', 2 UNION ALL
SELECT 'CompanyId ', 3 UNION ALL
SELECT 'LocationId', 1
DECLARE #Predicate NVARCHAR(MAX) = N''
SELECT #Predicate = #Predicate
+ CASE WHEN rn_asc = 1 THEN ' AND ' + FieldName + ' IN (' + LTRIM(FieldValue) ELSE '' END
+ CASE WHEN rn_asc > 1 THEN ', ' + LTRIM(FieldValue) ELSE '' END
+ CASE WHEN rn_desc = 1 THEN ') ' ELSE '' END
FROM (
SELECT *,
rn_asc = ROW_NUMBER() OVER(PARTITION BY x.FieldName ORDER BY x.FieldValue),
rn_desc= ROW_NUMBER() OVER(PARTITION BY x.FieldName ORDER BY x.FieldValue DESC)
FROM #eav x
) y
ORDER BY FieldName, FieldValue
SELECT #Predicate = STUFF(#Predicate, 1, 5, '')
SELECT #Predicate
-- Results: CompanyId IN (2, 3) AND DivisionId IN (1, 2, 3) AND LocationId IN (1)
Then you could use #Predicate to create a dynamic SQL SELECT statement (for example)
DECLARE #SqlStatement NVARCHAR(MAX)
SET #SqlStatement = 'SELECT ... FROM dbo.Table1 WHERE ' + #Predicate
EXEC sp_executesql #SqlStatement

Convert Rows to Column In SQL Server 2008

I want to use Pivot to convert rows to columns.
I have three tables:
Student table with columns StudentID and StudentName
Subject table with columns SubjectID and SubjectName
Student Subject table with columns StudentSubjectID, StudentID, SubjectID and Date
Now I wrote a query to get data from the above tables
StudentID StudentName SubjectID SubjectName DateTime
-----------------------------------------------------------
1 Yasser 1 Math 1/1/2017
1 Yasser 1 English 1/1/2017
1 Yasser 1 Math 3/1/2017
1 Mark 1 Math 1/1/2017
1 John 1 Math 6/1/2017
Now I will make a monthly report to display Student Subject per month and output should be
Student/Days 1/1/2017 2/1/2017 3/1/2017 4/1/2017 ......................................... 30/1/2017 (All days for month)
Yasser Math - Math - -
English - - - -
Mark Math - - - -
How can I do this?
Thank you
I guess the script will require too complex dynamic coding,
I had just created a schema but will take too much effort to convert for a complete dynamic SQL script
;with cte as (
select distinct StudentName, SubjectName, [DateTime] from studentCTE
), cte2 as (
select
StudentName,
case when [DateTime] = '2017-01-01' then SubjectName else null end as '2017-01-01',
case when [DateTime] = '2017-03-01' then SubjectName else null end as '2017-03-01',
case when [DateTime] = '2017-06-01' then SubjectName else null end as '2017-06-01'
from cte
)
SELECT distinct
StudentName,
STUFF(
(
SELECT
',' + [2017-01-01]
FROM cte2 C
where c.StudentName = cte2.StudentName
FOR XML PATH('')
), 1, 1, ''
) As '2017-01-01',
STUFF(
(
SELECT
',' + [2017-03-01]
FROM cte2 C
where c.StudentName = cte2.StudentName
FOR XML PATH('')
), 1, 1, ''
) As '2017-03-01',
STUFF(
(
SELECT
',' + [2017-06-01]
FROM cte2 C
where c.StudentName = cte2.StudentName
FOR XML PATH('')
), 1, 1, ''
) As '2017-06-01'
from cte2
I hope it helps somehow,
But I guess after SQL concatenation of subjectname fields, the below query can be solved by a dynamic sql pivot
Please check following dynamic SQL pivot query
DECLARE #PivotColumnHeaders VARCHAR(MAX)
SELECT #PivotColumnHeaders =
COALESCE(
#PivotColumnHeaders + ',[' + convert(nvarchar(20),date,23) + ']',
'[' + convert(nvarchar(20),date,23) + ']'
)
FROM dbo.GetFullMonth(getdate())
DECLARE #PivotTableSQL NVARCHAR(MAX)
SET #PivotTableSQL = N'
SELECT *
FROM (
select distinct StudentName,
STUFF(
(
SELECT
'','' + SubjectName
FROM studentCTE C
where c.StudentName = studentCTE.StudentName
and c.DateTime = studentCTE.DateTime
FOR XML PATH('''')
), 1, 1, ''''
) As Subjects
, [DateTime]
from studentCTE
) AS PivotData
PIVOT (
MAX(Subjects)
FOR [DateTime] IN (
' + #PivotColumnHeaders + '
)
) AS PivotTable
'
EXECUTE(#PivotTableSQL)
Here is my output
To summarize the query, first of all I need the current month's dates as column names. I used the calendar function module GetFullMonth at given reference. I just changed the return "datetime" column to "date" data type.
As I noted in my previous code sample, I used SQL concatenation with FOR XML PATH method.
One last reference I'ld like to share with you. I used date to string conversion in the PivotColumnHeaders section of the dynamic query where I define the additional pivot columns. I used the conversion parameter as 23.
You can check for a full list of datetime format parameter at given reference.
I hope these helps you for the solution,
If you want to date column contains only what has your table, then you have to use this query.
DECLARE #DYNQRY AS VARCHAR(MAX)
,#COL AS VARCHAR(MAX)
SELECT #COL= ISNULL(#COL + ',','')
+ QUOTENAME(DATE)
FROM (SELECT DISTINCT DATE FROM STUDENTSUBJECT) AS DATE
SET #DYNQRY ='SELECT STUDENTNAME, ' + #COL + '
FROM (SELECT A.STUDENTID,STUDENTNAME,SUBJECTNAME,DATE
FROM STUDENT A,SUBJECT B,STUDENTSUBJECT C
WHERE A.STUDENTID=C.STUDENTID
AND B.SUBJECTID=C.SUBJECTID
)A
PIVOT(
MAX(SUBJECTNAME)
FOR DATE IN (' + #COL + ')
) AS PVTTABLE'
EXEC (#DYNQRY)
CREATE TABLE #tt(StudentID INT,StudentName VARCHAR(200),SubjectID INT,SubjectName VARCHAR(200),[DateTime]DATETIME)
INSERT INTO #tt
SELECT 1,'Yasser',1,'Math','01/01/2017' UNION
SELECT 1,'Yasser',1,'English','01/01/2017' UNION
SELECT 1,'Yasser',1,'Math','01/03/2017' UNION
SELECT 1,'Mark',1,'Math','01/01/2017' UNION
SELECT 1,'John',1,'Math','01/06/2017'
DECLARE #col1 VARCHAR(max),#col2 VARCHAR(max),#sql VARCHAR(max)
SELECT #col1=ISNULL(#col1+',','')+ t.d
,#col2=ISNULL(#col2+',','')+'ISNULL('+ t.d +',''-'') AS '+t.d
FROM #tt
INNER JOIN master.dbo.spt_values AS sv ON sv.type='P' AND sv.number BETWEEN 0 AND 30
CROSS APPLY(VALUES('['+CONVERT(VARCHAR,DATEADD(dd,sv.number,DATEADD(MONTH, MONTH([DateTime])-1,DATEADD(YEAR,YEAR([DateTime])-1900,0))),110)+']')) t(d)
WHERE MONTH(DATEADD(dd,sv.number,DATEADD(MONTH, MONTH([DateTime])-1,DATEADD(YEAR,YEAR([DateTime])-1900,0))))= MONTH([DateTime])
GROUP BY MONTH([DateTime]),YEAR([DateTime]),sv.number,t.d
PRINT #col2
SET #sql='
SELECT StudentID,StudentName,'+#col2+' FROM #tt
PIVOT(max(SubjectName) FOR [DateTime] IN ('+#col1+')) p'
EXEC (#sql)
--partial column--
StudentID StudentName 01-01-2017 01-02-2017 01-03-2017 01-04-2017 01-05-2017 01-06-2017 01-07-2017 01-08-2017 01-09-2017 01-10-2017 01-11-2017
1 John - - - - - Math - - - - -
1 Mark Math - - - - - - - - - -
1 Yasser Math - Math - - - - - - - -

Dynamic pivot data with multiple datatypes

I have a trick problem with a pivot table to make:
I have a table which looks like:
id table object name type nvarchar date int bit
1 1 2 name 1 tables NULL NULL NULL
2 1 2 name 1 columns NULL NULL NULL
3 1 2 name 1 datatypes NULL NULL NULL
4 1 2 name 1 _users NULL NULL NULL
1 1 3 active 3 NULL NULL NULL 1
2 1 3 active 3 NULL NULL NULL 1
3 1 3 active 3 NULL NULL NULL 1
4 1 3 active 3 NULL NULL NULL 1
the output should look like:
id name active
1 tables 1
2 columns 1
3 datatypes 1
4 _users 1
Based upon the "type" I should put the correct data from the column in it, these columns are formated in nvarchar, bit, datetime, int, ect.
The "id" is the row id, the "name, active" comes from the name column and the values from nvarchar, date, int and bit columns.
UPDATE: the columns like nvarchar, date, int and bit (and most other SQL formats) are actually contain this type of data. The column "type" gives which column contains the data to being used, so if "type" is "1", than I want to use the "nvarchar" if "type" is "3" than I want to use the "bit" which contains really a bit and not a nvarchar. In the Pivot I want to have the bit under "active" column, if I have in the example a 3th column (name) for example "activation_date" I want to see a third column with the value (type = 2) from the date column.
I am lost in this, please help
Assuming there's only one not null column for each row:
with cte as (
select
id,
name,
coalesce(
[nvarchar],
convert(nvarchar(max), [date], 120),
cast([int] as nvarchar(max)),
cast([bit] as nvarchar(max))
) as value
from Table1 as t
)
select
id,
max(case when [name] = 'name' then value end) as [name],
max(case when [name] = 'active' then value end) as [active]
from cte
group by id
sql fiddle demo
But I must warn you, this types of database schema is not best way to use SQL.
If you want to do this dynamically without hardcoding columns:
declare #stmt nvarchar(max)
select #stmt =
isnull(#stmt + ', ', '') +
'max(case when [name] = ''' + name + ''' then value end) as ' + quotename([name])
from (select distinct [name] from Table1) as t
select #stmt = '
with cte as (
select
id,
name,
coalesce(
[nvarchar],
convert(nvarchar(max), [date], 120),
cast([int] as nvarchar(max)),
cast([bit] as nvarchar(max))
) as value
from Table1 as t
)
select
id, ' + #stmt + '
from cte
group by id
'
exec sp_executesql
#stmt = #stmt
sql fiddle demo
If you have some Mapping table like this:
name value
--------------------
name nvarchar
active bit
you can use this query:
declare #stmt nvarchar(max)
select #stmt =
isnull(#stmt + ', ', '') +
'max(case when [name] = ''' + name + ''' then [' + value + '] end) as ' + quotename([name])
from Mapping
select #stmt = '
select
id, ' + #stmt + '
from Table1
group by id
'
exec sp_executesql
#stmt = #stmt
sql fiddle demo

Pivot a fixed multiple column table in sql server

I have a table which I need to pivot for reporting services:
DateCreated Rands Units Average Price Success % Unique Users
-------------------------------------------------------------------------
2013-08-26 0 0 0 0 0
2013-08-27 0 0 0 0 0
2013-08-28 10 2 5 100 1
2013-08-29 12 1 12 100 1
2013-08-30 71 9 8 100 1
2013-08-31 0 0 0 0 0
2013-09-01 0 0 0 0 0
In other words I need to have Rands, Units, Average Price etc at rows and the dates as columns.
I have read various examples but I just can't seem to get it right.
Any help would be much appreciated!
This one will do what you want, but you have to specify all the dates
select
c.Name,
max(case when t.DateCreated = '2013-08-26' then c.Value end) as [2013-08-26],
max(case when t.DateCreated = '2013-08-27' then c.Value end) as [2013-08-27],
max(case when t.DateCreated = '2013-08-28' then c.Value end) as [2013-08-28],
max(case when t.DateCreated = '2013-08-29' then c.Value end) as [2013-08-29],
max(case when t.DateCreated = '2013-08-30' then c.Value end) as [2013-08-30],
max(case when t.DateCreated = '2013-08-31' then c.Value end) as [2013-08-31],
max(case when t.DateCreated = '2013-09-01' then c.Value end) as [2013-09-01]
from test as t
outer apply (
select 'Rands', Rands union all
select 'Units', Units union all
select 'Average Price', [Average Price] union all
select 'Success %', [Success %] union all
select 'Unique Users', [Unique Users]
) as C(Name, Value)
group by c.Name
You can create a dynamic SQL for this, something like this:
declare #stmt nvarchar(max)
select #stmt = isnull(#stmt + ',', '') +
'max(case when t.DateCreated = ''' + convert(nvarchar(8), t.DateCreated, 112) + ''' then c.Value end) as [' + convert(nvarchar(8), t.DateCreated, 112) + ']'
from test as t
select #stmt = '
select
c.Name, ' + #stmt + ' from test as t
outer apply (
select ''Rands'', Rands union all
select ''Units'', Units union all
select ''Average Price'', [Average Price] union all
select ''Success %'', [Success %] union all
select ''Unique Users'', [Unique Users]
) as C(Name, Value)
group by c.Name'
exec sp_executesql #stmt = #stmt
I solved this in the end using dynamic sql, very similar to the marked answer. I wasn't able to find a way of doing this without dynamic sql. The dates had to be in order and the last 7 days, they also had to use the day of the week names (which I didn't specify in the question).
The biggest change I needed to make was changing the table variable into a temporary table.
This is because dynamic sql statements execute in a different context and don't know about any variables you have created.
In the end I was completely off track trying to use PIVOT and APPLY should be used in situations where there are more than one "type" of value.
I have included my solution below since it could help someone who has a similar problem:
CREATE TABLE #SummaryTable
(
[DateCreated] DATE UNIQUE,
[Rands] DECIMAL,
[Units] INT,
[Average Price] DECIMAL,
[Success %] INT,
[Unique Users] INT
);
--Code to fill table
declare #stmt nvarchar(max)
select #stmt = isnull(#stmt + ',', '') +
'max(case when t.DateCreated = ''' + convert(nvarchar(16), t.DateCreated, 126)
+ ''' then c.Value end) as [' + left(datename(dw, t.DateCreated),3) + ']'
from #SummaryTable as t
select #stmt = '
select
c.Name, ' + #stmt + ' from #SummaryTable as t
outer apply (
select ''Rands'', Rands union all
select ''Units'', Units union all
select ''Average Price'', [Average Price] union all
select ''Success'', [Success %] union all
select ''Unique Users'', [Unique Users]
) as C(Name, Value)
group by c.Name'
exec(#stmt)