Given the following simplified scenario.
DROP TABLE IF EXISTS SourceData;
CREATE TABLE SourceData
(
[Timestamp] DATE NOT NULL,
[Point] NVARCHAR(100) NOT NULL,
[Value] FLOAT NOT NULL
)
INSERT INTO SourceData
VALUES
('2020-01-01', 'A', 0.25),
('2020-01-01', 'B', 0.5),
('2020-01-01', 'C', 0.99),
('2020-01-02', 'A', 0.30),
('2020-01-02', 'B', 0.75),
('2020-01-02', 'C', 1.50),
('2020-01-03', 'A', 0.35),
('2020-01-03', 'B', 0.80),
('2020-01-03', 'C', 1.75)
SELECT
*
FROM
(
SELECT
[Timestamp],
[Point],
[Value]
FROM SourceData
) AS SourceTable
PIVOT
(
AVG([Value])
FOR [Point] IN (A, B, C)
) AS PivotTable;
Returns:
Timestamp A B C
2020-01-01 0.25 0.5 0.99
2020-01-02 0.3 0.75 1.5
2020-01-03 0.35 0.8 1.75
Fiddle Link
Is it possible to obtain the same results without hardcoding the column names inside the PIVOT clause? I do not know in advance which are the unique values in the Point column.
Or... is there a way to specify them based on a query?
Try this using a dynamic pivot query.
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(c.Point)
FROM SourceData c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT Timestamp, ' + #cols + ' from
(
select Timestamp
, Value
, Point
from SourceData
) x
pivot
(
max(Value)
for Point in (' + #cols + ')
) p '
execute(#query)
Live db<>fiddle link.
Related
My data looks like this
This is how I have attempted it so far
is giving the events columns duplicated as shown in the left side query above, not sure why
Output is required in 2 formats pivot by Event Date
and by Event Type
I tried to insert the SQL code but it didn't work with formatting, sorry
-- to create a table
Create Table TestStudentEvents (Pid int, StudentID int, EventName varchar(50), EventDate DateTime);
Insert into TestStudentEvents values
(1, 1, 'SA', '2021-05-10'), (2, 1, 'SA', '2021-05-12'), (3, 1, 'AA', '2021-05-11'),
(4, 2, 'SA', '2021-05-10'), (5, 2, 'SA', '2021-05-12'), (6, 2, 'AB', '2021-05-11')
--select * from #StudentEvents
DECLARE #cols AS NVARCHAR(MAX);
DECLARE #query AS NVARCHAR(MAX);
SELECT #cols = STUFF((SELECT distinct ',' +
QUOTENAME(EventName)
from TestStudentEvents AS t
where [StudentID] = 1 or [StudentID] = 2
group by EventName
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
, 1, 1, '');
-- select #cols
SELECT #query = 'SELECT * , '+ #cols + '
FROM
(
select distinct [EventName], COUNT(EventName) as NoOfEvents, [StudentID]
FROM TestStudentEvents
where [StudentID] =1 or [StudentID] = 2
group by [EventName], [StudentID]
) AS t
PIVOT
(
Max(NoOfEvents)
FOR [EventName] IN (' + #cols + ')' +
') p ';
execute(#query);
Drop table TestStudentEvents;
Just replace the * with actual columns in the dynamic SQL.
Please see the db<>fiddle here.
In my database I have a few products. Those products have an unknowningly amount of parameters/fields stored as a name and value in a separate table.
http://sqlfiddle.com/#!18/f3b3e
CREATE TABLE Products
([ProductId] varchar(50), [Name] varchar(50))
;
INSERT INTO Products
([ProductId], [Name])
VALUES
('PROD1', 'Product 1'),
('PROD2', 'Product 2'),
('PROD3', 'Product 3')
;
CREATE TABLE ProductFields
([ProductId] varchar(50), [Name] varchar(50), [Value] varchar(50))
;
INSERT INTO ProductFields
([ProductId], [Name], [Value])
VALUES
('PROD1', 'Color', 'Red'),
('PROD1', 'Size', '2'),
('PROD1', 'Weight', '50'),
('PROD2', 'Color', 'Blue'),
('PROD2', 'Size', '1'),
('PROD2', 'Weight', '15'),
('PROD3', 'Color', 'Yellow'),
('PROD3', 'Size', '3'),
('PROD3', 'Weight', '10')
;
If I have 3 products, I want my output to contain 3 rows that looks like this:
ProductId Name Color Size Weight
----------- ----------- --------- -------- ---------
PROD1 Product 1 Red 2 50
PROD2 Product 2 Blue 1 15
PROD3 Product 3 Yellow 3 10
How do I create a dynamic PIVOT that also has an INNER JOIN against that other table? All values are nice and simply VARCHARs, so that should be quite easy, however, I can't wrap my head around PIVOTs with dynamic values.
This is my go at it:
DECLARE
#cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(pf.Name)
FROM ProductFields pf
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SET #query = 'SELECT p.ProductId, p.Name, ' + #cols + ' from
(
SELECT p.ProductId, p.Name FROM Products p
INNER JOIN ProductFields pf
ON pf.ProductId = p.ProductId
) x
pivot
(
Value
for Name in (' + #cols + ')
) pi '
execute(#query)
Perhaps this will help. Notice the inclusion of ITEM and max(Value)
Example or dbFiddle
DECLARE
#cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(pf.Name)
FROM ProductFields pf
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SET #query = 'SELECT *
from (
SELECT p.ProductId
,p.Name
,Item=pf.Name
,pf.Value
FROM Products p
JOIN ProductFields pf
ON pf.ProductId = p.ProductId
) x
pivot
(
max(Value)
for Item in (' + #cols + ')
) pi '
execute(#query)
I have a table in SQL Server that looks something like this:
What I need is this:
I tried playing around with Pivot a little bit, but of course that won't work because Pivot isn't trying to flip multiple instances of the same field, it's aggregating over multiple instance of the same field.
Does anyone have any ideas of how I can do this in T-SQL?
You can do this easily using dynamic PIVOT. This is full working example:
IF OBJECT_ID('[dbo].[DataSource]') IS NOT NULL
BEGIN;
DROP TABLE [dbo].[DataSource];
END;
CREATE TABLE [dbo].[DataSource]
(
[ID] SMALLINT
,[Value] CHAR(1)
);
INSERT INTO [dbo].[DataSource] ([ID], [Value])
VALUES (1, 'A')
,(1, 'B')
,(1, 'C')
,(2, 'A')
,(2, 'B')
,(3, 'C')
,(4, 'A')
,(4, 'B')
,(4, 'C');
DECLARE #MaxValue INT;
WITH DataSource AS
(
SELECT ROW_NUMBER() OVER (PARTITION BY [ID] ORDER BY [Value]) AS [ValueID]
FROM [dbo].[DataSource]
)
SELECT #MaxValue = MAX([ValueID])
FROM DataSource;
DECLARE #DynamicSQLStatement NVARCHAR(MAX)
,#DynamicSQLPIVOTColumns NVARCHAR(MAX);
SELECT #DynamicSQLPIVOTColumns = STUFF
(
(
SELECT DISTINCT ',[Value' + CAST([number] AS VARCHAR(4)) + ']'
FROM master..[spt_values]
WHERE [number] BETWEEN 1 AND #MaxValue
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1
,1
,''
);
SET #DynamicSQLStatement = N'
SELECT [ID], ' + #DynamicSQLPIVOTColumns + '
FROM
(
SELECT *
,''Value'' + CAST(ROW_NUMBER() OVER (PARTITION BY [ID] ORDER BY [Value]) AS VARCHAR(12)) AS [ColumnName]
FROM [dbo].[DataSource]
) DS
PIVOT
(
MAX([value]) FOR [ColumnName] IN (' + #DynamicSQLPIVOTColumns + ')
) PVT;';
EXEC sp_executesql #DynamicSQLStatement;
and the result is:
Something wrong? The value C for the ID = 3 is in the first column, not in the last. That's because I am not aware how you are defining which value is in which column and I am using ROW_NUMBER to create such mapping. I guess in your real data you have a way of doing this.
So, let's say you have additional table like this to define this ordering:
IF OBJECT_ID('[dbo].[DataSourceOrdering]') IS NOT NULL
BEGIN;
DROP TABLE [dbo].[DataSourceOrdering];
END;
CREATE TABLE [dbo].[DataSourceOrdering]
(
[OrderID] SMALLINT
,[Value] CHAR(1)
);
INSERT INTO [dbo].[DataSourceOrdering] ([OrderID], [Value])
VALUES (1, 'A')
,(2, 'B')
,(3, 'C');
Then instead using ROW_NUMBER to defined the ordering we are going to use this table:
DECLARE #MaxValue INT;
WITH DataSource AS
(
SELECT DSO.[OrderID]
FROM [dbo].[DataSource] DS
INNER JOIN [dbo].[DataSourceOrdering] DSO
ON DS.[Value] = DSO.[Value]
)
SELECT #MaxValue = MAX([OrderID])
FROM DataSource;
DECLARE #DynamicSQLStatement NVARCHAR(MAX)
,#DynamicSQLPIVOTColumns NVARCHAR(MAX);
SELECT #DynamicSQLPIVOTColumns = STUFF
(
(
SELECT DISTINCT ',[Value' + CAST([number] AS VARCHAR(4)) + ']'
FROM master..[spt_values]
WHERE [number] BETWEEN 1 AND #MaxValue
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1
,1
,''
);
SET #DynamicSQLStatement = N'
SELECT [ID], ' + #DynamicSQLPIVOTColumns + '
FROM
(
SELECT DS.*
,''Value'' + CAST(DSO.[OrderID] AS VARCHAR(12)) AS [ColumnName]
FROM [dbo].[DataSource] DS
INNER JOIN [dbo].[DataSourceOrdering] DSO
ON DS.[Value] = DSO.[Value]
) DS
PIVOT
(
MAX([value]) FOR [ColumnName] IN (' + #DynamicSQLPIVOTColumns + ')
) PVT;';
EXEC sp_executesql #DynamicSQLStatement;
Something like this?
WITH
t AS (
SELECT 1 AS ID, 'A' AS Value
UNION ALL SELECT 1, 'B'
UNION ALL SELECT 1, 'C'
UNION ALL SELECT 2, 'A'
UNION ALL SELECT 2, 'B'
UNION ALL SELECT 3, 'C'
UNION ALL SELECT 4, 'A'
UNION ALL SELECT 4, 'B'
UNION ALL SELECT 4, 'C'
)
SELECT ID,
MAX(CASE WHEN Value = 'A' THEN Value ELSE NULL END) AS Value1,
MAX(CASE WHEN Value = 'B' THEN Value ELSE NULL END) AS Value2,
MAX(CASE WHEN Value = 'C' THEN Value ELSE NULL END) AS Value3
FROM t
GROUP BY ID
I have a table with 3 nvarchar columns, 1 time column, and 2 columns Pass and Fail. I need to display the data by time column. On each milestone, there will be the number of Pass and Fail. I use Pivot and its only output Pass result without Fail. I tried everything. Please help
This is the input data:
Col1 Col2 Col3 Time Pass Fail
------------------------------------
A B C 08:30 80 0
A B C 09:30 60 2
A B C 10:30 80 0
A B C 11:30 70 0
I'm using this code:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
SELECT
#cols = STUFF((SELECT ',' + QUOTENAME(Time)
FROM Your_Table
GROUP BY Time
ORDER BY Time
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '')
SET #query = 'SELECT Col1,Col2,Col3,' + #cols + ' from
(
SELECT Col1,Col2,Col3,Time,Pass,Fail
from TD_SanLuong_CN
) x
pivot
(
sum(Pass)
for Time in (' + #cols + ')
) p1
pivot
(
sum(Fail)
for Time in (' + #cols + ')
) p2'
execute(#query);
Can I not use pivot to Fail?
I need output result:
Col1 Col2 Col3 08:30_Pass 08:30_Fail 09:30_Pass 09:30_Fail ...
A B C 80 0 60 2
Please help. Thank you!
Hi have a look at this code, i think this what you exactly needed as you expected Output
IF OBJECT_ID('tempdb..#Temp') IS NOT NULL
Drop table #Temp
CREATE TABLE #Temp (
Col1 CHAR(1),
Col2 CHAR(1),
Col3 CHAR(1),
[Time] TIME(0),
Pass INT,
Fail INT
);
INSERT #Temp (Col1, Col2, Col3, [Time], Pass, Fail) VALUES
('A', 'B', 'C', '08:30', 80, 0),
('A', 'B', 'C', '09:30', 60, 2),
('A', 'B', 'C', '10:30', 80, 0),
('A', 'B', 'C', '11:30', 70, 0);
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX),
#cols2 AS NVARCHAR(MAX),
#cols3 AS NVARCHAR(MAX),
#Dyncols AS NVARCHAR(MAX)
SELECT
#cols = STUFF((SELECT ',' + QUOTENAME(Time)
FROM #Temp
GROUP BY [Time]
ORDER BY [Time]
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '')
SELECT
#cols2 = STUFF((SELECT ',' + 'MAX('+QUOTENAME(Time)+')' +' As '+ '['+CAST((Time) AS VARCHAR)+'_Pass'+']'
FROM #Temp
GROUP BY [Time]
ORDER BY [Time]
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '')
SELECT
#cols3 = STUFF((SELECT ',' + 'MAX('+QUOTENAME(Time)+')' +' As '+ '['+CAST((Time) AS VARCHAR)+'_Fail'+']'
FROM #Temp
GROUP BY [Time]
ORDER BY [Time]
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '')
SELECT
#Dyncols = STUFF((SELECT ',' + '['+CAST((Time) AS VARCHAR)+'_Pass'+']'+','+'['+CAST((Time) AS VARCHAR)+'_Fail'+']'
FROM #Temp
GROUP BY [Time]
ORDER BY [Time]
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '')
SET #query=';with cte
AS
(
SELECT Col1,Col2,Col3,'+#cols2+' From
(
SELECT Col1,Col2,Col3,Time,Pass,Fail
FROM #Temp
) AS X
PIVOT
(
SUM(Pass)
FOR [Time] IN ('+#cols+' )
) p1
Group by Col1,Col2,Col3
),Cte2
AS
(
SELECT '+#cols3+' From
(
SELECT Col1,Col2,Col3,Time,Pass,Fail
from #Temp
) x
PIVOT
(
SUM(Fail)
FOR Time IN ('+#cols+')
)p1
Group by Col1,Col2,Col3
)
SELECT Col1,Col2,Col3,'+#Dyncols+' FROM cte ,Cte2'
PRINT #query
EXEC(#query)
Result
Col1 Col2 Col3 08:30:00_Pass 08:30:00_Fail 09:30:00_Pass 09:30:00_Fail 10:30:00_Pass 10:30:00_Fail 11:30:00_Pass 11:30:00_Fail
A B C 80 0 60 2 80 0 70 0
I have the following table
The table after pivoting is as follows. How do I maintain the date order in pivoted columns?
Here is how you can do it dynamically.
CREATE TABLE TEST
(
NAME VARCHAR(10),
DATECOL DATE,
VALUECol INT
)
INSERT INTO TEST
VALUES
('A', '01/JAN/2014', 10),
('B', '01/JAN/2014', 20),
('A', '26/JAN/2014', 20),
('B', '26/JAN/2014', 30),
('A', '01/FEB/2014', 40),
('B', '01/FEB/2014', 50),
('A', '26/FEB/2014', 60),
('B', '26/FEB/2014', 70)
DECLARE #colsPivot AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #colsPivot = STUFF((SELECT distinct ',' + QUOTENAME(DATECOL)
from TEST
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query
= 'select *
from
(
select NAME,DATECOL,VALUECOL
from TEST
) x1
pivot
(
max(VALUECOL)
for DATECOL in ('+ #colspivot +')
) p'
exec(#query)
Check this on SQL Fiddle
I got my own way to resolve this problem
CREATE TABLE #TEMP (Name varchar(10), [DATE] datetime, Value int)
INSERT #TEMP VALUES
('A','01/JAN/2014',10),
('B','01/JAN/2014',20),
('A','26/JAN/2014',20),
('B','26/JAN/2014',30),
('A','01/FEB/2014',40),
('B','01/FEB/2014',50),
('A','26/FEB/2014',60),
('B','26/FEB/2014',70)
Now we assign date in the format DD/MMM/YYYY to the variable
(Note: The [DATE] column should be of the type datetime/date)
DECLARE #cols NVARCHAR (MAX)
SELECT #cols = COALESCE (#cols + ',[' + REPLACE(CONVERT(NVARCHAR, [DATE], 106), ' ', '/') + ']',
'[' + REPLACE(CONVERT(NVARCHAR, [DATE], 106), ' ', '/') + ']')
FROM (SELECT DISTINCT [DATE] FROM #TEMP) PV
ORDER BY [DATE]
After pivot the columns will be in perfect Date order with the format DD/MMM/YYYY
DECLARE #query NVARCHAR(MAX)
SET #query = 'SELECT * FROM
(
SELECT Name,REPLACE(CONVERT(NVARCHAR, [DATE], 106), '' '', ''/'') [DATE] , value FROM #TEMP
) x
PIVOT
(
SUM(value)
FOR [DATE] IN (' + #cols + ')
) p;'
EXEC SP_EXECUTESQL #query
Like this you can convert to any date formats and can maintain the order of date in pivoted columns
by editing the conversion type in SELECT #cols and inner SELECT of Pivot statement