I am trying to reverse or somehow pivot appended rows from a table through an SQL Query. The following example illustrates the table structure I have
Timestamp
ID
Value
2023-01-18
A
10
2023-01-19
A
15
2023-01-20
A
20
2023-01-18
B
10
2023-01-19
B
15
2023-01-20
B
20
2023-01-18
C
10
2023-01-19
C
15
2023-01-20
C
20
And I am trying to modify the query to pivot or group the rows equivalent to the following:
Timestamp
A
B
C
2023-01-18
10
10
10
2023-01-19
15
15
15
2023-01-20
20
20
20
What would be a solution for this query?
I have tried pivoting the query like the following which according to my research should do what I am hoping for, but maybe I am missing something as it returns an error message below.
SELECT Facility,
Site,
SUBSTRING(Name,
CHARINDEX('_',Name)+1,
( ((LEN(Name)) - CHARINDEX('_', REVERSE(Name)))
- CHARINDEX('_',Name) )
) AS Panel,
dateadd(hh,-7,TimestampUTC) as TimeStamp,
ActualValue
FROM PSS_KPIHistory
WHERE Name LIKE '%PercentLoopsInNormal'
PIVOT(ActualValue for Panel in(select distinct Panel from PSS_KPIHistory))
The above query returns columns Facility, Site, an extracted string from the column "Name" stored as new Column "Panel", a Timestamp and the Value (ActualValue). I am returning everything from the table that contains "PercentLoopsInNormal" in the "Name" Column. This returns the following error:
Message=Incorrect syntax near the keyword 'PIVOT'.
Incorrect syntax near ')'.
your data doesn't fit your wanted result, changing it you can do
you would need a dynamic approach to the problem
CREATE TABLE table1
([Timestamp] DATE, [ID] varchar(1), [Value] int)
;
INSERT INTO table1
([Timestamp], [ID], [Value])
VALUES
('2023-01-18', 'A', 10),
('2023-01-19', 'A', 15),
('2023-01-20', 'A', 20),
('2023-01-18', 'B', 10),
('2023-01-19', 'B', 15),
('2023-01-20', 'B', 20),
('2023-01-18', 'C', 10),
('2023-01-19', 'C', 15),
('2023-01-20', 'C', 20)
;
9 rows affected
DECLARE
#columns NVARCHAR(MAX) = '',
#sql NVARCHAR(MAX) = '';
-- select the Distinct ID
SELECT
#columns+=QUOTENAME( [ID]) + ','
FROM
(SELECT DISTINCT [ID] FROM table1) t1
ORDER BY
[ID];
-- remove the last comma
SET #columns = LEFT(#columns, LEN(#columns) - 1);
-- construct dynamic SQL
SET #sql ='
SELECT * FROM
(
SELECT
[Timestamp], [ID], [Value]
FROM
table1
) t
PIVOT(
MAX([Value])
FOR [ID] IN ('+ #columns +')
) AS pivot_table;';
--SELECT #sql
-- execute the dynamic SQL
EXECUTE sp_executesql #sql;
Timestamp
A
B
C
2023-01-18
10
10
10
2023-01-19
15
15
15
2023-01-20
20
20
20
fiddle
Here's the traditional pivot approach:
Step 1: identifying values from a single column that should be split in different columns (your discriminatory values are 'A', 'B' and 'C')
Step 2: aggregating on the field for which specific value you want one record only in the output, in this case [Timestamp]
SELECT [Timestamp],
MAX(CASE WHEN [ID] = 'A' THEN [Value] END) AS A,
MAX(CASE WHEN [ID] = 'B' THEN [Value] END) AS B,
MAX(CASE WHEN [ID] = 'C' THEN [Value] END) AS C
FROM tab
GROUP BY [Timestamp]
Typically Step1 requires the use of a window function, but in this case your [ID] field is ready to be used by Step2.
Check the demo here.
Related
This question already has answers here:
SQL Server dynamic PIVOT query?
(9 answers)
Closed 1 year ago.
I tried to search before asking, but I didn't find something similar to the one I try to figure out. I use sql server to achieve that.
Current Situation
Target
Based on the Year, I want to pivot:
Col as the name of a new column
value should be the value of the column.
In that example, the first 36 rows should become one row. For every year there should be one row.
A B C D YEAR E F HiBioInsec HiChemInsec etc
76 1 191 4 2020 5000 2000
76 1 191 4 2021 5000 2000
I tried with pivot and max but I didn't got the expected output.
Any thoughts?
PIVOT seems like exactly what you need, actually. Does yours look like this at all? This worked for me.
CREATE TABLE dbo.YourTable
(
A int,
B int,
C int,
D int,
[Year] int,
E int,
F int,
col varchar(30),
[value] int
);
INSERT dbo.YourTable (A,B,C,D,[Year],col, [value])
VALUES (76, 1, 191, 4, 2020, 'HiBioInsec', 5000);
INSERT dbo.YourTable (A,B,C,D,[Year],col, [value])
VALUES (76, 1, 191, 4, 2020, 'HiChemInsec', 2000);
INSERT dbo.YourTable (A,B,C,D,[Year],col, [value])
VALUES (76, 1, 191, 4, 2021, 'HiBioInsec', 5000);
INSERT dbo.YourTable (A,B,C,D,[Year],col, [value])
VALUES (76, 1, 191, 4, 2021, 'HiChemInsec', 2000);
SELECT A,B,C,D,[Year],E,F
, pvt.HiBioInsec
, pvt.HiChemInsec
FROM
(
SELECT A,B,C,D,[Year],E,F,col, SUM([value]) AS SumValue
FROM dbo.YourTable [tbl]
GROUP BY A,B,C,D,[Year],E,F,col
) src
PIVOT (SUM(SumValue) FOR col IN ([HiBioInsec],[HiChemInsec])) pvt
Can you post your SQL?
You could also try something like this, but I don't see how you can get around using an aggregate.
SELECT A,B,C,D,[Year],E,F
, SUM(HiBioInsec) AS HiBioInsec
,SUM(HiChemInsec) AS HiChemInsec
FROM
(
SELECT A,B,C,D,[Year],E,F, [value] AS HibioInsec ,NULL AS HiChemInsec
FROM dbo.YourTable WHERE col = 'HiBioInsec'
UNION ALL
SELECT A,B,C,D,[Year],E,F, NULL AS HibioInsec , [value] AS HiChemInsec
FROM dbo.YourTable WHERE col = 'HiChemInsec'
) tbl
GROUP BY A,B,C,D,[Year],E,F
I'm trying to create result set with 3 columns. Each column coming from the summation of 1 Column of Table A but grouped by different ID's. Here's an overview of what I wanted to do..
Table A
ID Val.1
1 4
1 5
1 6
2 7
2 8
2 9
3 10
3 11
3 12
I wanted to create something like..
ROW SUM.VAL.1 SUM.VAL.2 SUM.VAL.3
1 15 21 33
I understand that I can not get this using UNION, I was thinking of using CTE but not quite sure with the logic.
You need conditional Aggregation
select 1 as Row,
sum(case when ID = 1 then Val.1 end),
sum(case when ID = 2 then Val.1 end),
sum(case when ID = 3 then Val.1 end)
From yourtable
You may need dynamic cross tab or pivot if number of ID's are not static
DECLARE #col_list VARCHAR(8000)= Stuff((SELECT ',sum(case when ID = '+ Cast(ID AS VARCHAR(20))+ ' then [Val.1] end) as [val.'+Cast(ID AS VARCHAR(20))+']'
FROM Yourtable
GROUP BY ID
FOR xml path('')), 1, 1, ''),
#sql VARCHAR(8000)
exec('select 1 as Row,'+#col_list +'from Yourtable')
Live Demo
I think pivoting the data table will yield the desired result.
IF OBJECT_ID('tempdb..#TableA') IS NOT NULL
DROP TABLE #TableA
CREATE TABLE #TableA
(
RowNumber INT,
ID INT,
Value INT
)
INSERT #TableA VALUES (1, 1, 4)
INSERT #TableA VALUES (1, 1, 5)
INSERT #TableA VALUES (1, 1, 6)
INSERT #TableA VALUES (1, 2, 7)
INSERT #TableA VALUES (1, 2, 8)
INSERT #TableA VALUES (1, 2, 9)
INSERT #TableA VALUES (1, 3, 10)
INSERT #TableA VALUES (1, 3, 11)
INSERT #TableA VALUES (1, 3, 12)
-- https://msdn.microsoft.com/en-us/library/ms177410.aspx
SELECT RowNumber, [1] AS Sum1, [2] AS Sum2, [3] AS Sum3
FROM
(
SELECT RowNumber, ID, Value
FROM #TableA
) a
PIVOT
(
SUM(Value)
FOR ID IN ([1], [2], [3])
) AS p
This technique works if the ids you are seeking are constant, otherwise I imagine some dyanmic-sql would work as well if changing ids are needed.
https://msdn.microsoft.com/en-us/library/ms177410.aspx
I need show sum col(item) under col with SQL code ? it's possible
Code item
---- ----
1 30
3 40
4 50
9 80
---- ----
Total 200
Use Rollup to get the summary row
SELECT CASE
WHEN Grouping(code) = 1 THEN 'Total'
ELSE Cast(code AS VARCHAR(50))
END,
Sum(item)
FROM Yourtable
GROUP BY code WITH rollup
DECLARE #Table1 TABLE
(Code int, item int)
;
INSERT INTO #Table1
(Code, item)
VALUES
(1, 30),
(3, 40),
(4, 50),
(9, 80)
;
Script :
select Code , sum(item)item
from #Table1
group by GROUPING SETS((Code) , ())
order by Code DESC
select * from (select * from #Table1
union
select null, sum(item) item from #Table1)a
order by item
Select
Code,
item
from
# table_name
Union All
select
Null,
sum(item)item
from
# table_name
As we are using union all so distinct and order by operation will be saved.
I have a stored procedure which works most of the time, but every now and again, I get an error message:
Msg 8152, Level 16, State 2, Line 98
String or binary data would be truncated.
The statement has been terminated.
How do I figure out which data string is causing this issue?
For this answer, which handles more complex select queries quite well, let's assume we have three tables defined as follows...
CREATE TABLE [dbo].[Authors](
[AuthorID] [int] NOT NULL,
[AuthorName] [varchar](20) NOT NULL
) ON [PRIMARY]
CREATE TABLE [dbo].[Books](
[BookID] [int] NOT NULL,
[AuthorID] [int] NOT NULL,
[BookName] [varchar](20) NOT NULL
) ON [PRIMARY]
CREATE TABLE [dbo].[Publications](
[BookID] [int] NOT NULL,
[PublicationName] [varchar](10) NOT NULL,
[AuthorID] [int] NOT NULL,
[WrittenBy] [varchar](10) NOT NULL
) ON [PRIMARY]
...and we create the following data...
INSERT INTO Authors ( AuthorID, AuthorName ) VALUES ( 1, 'BOB' )
INSERT INTO Authors ( AuthorID, AuthorName ) VALUES ( 2, 'JANE' )
INSERT INTO Authors ( AuthorID, AuthorName ) VALUES ( 3, 'SOREN LONGNAMESSON' )
INSERT INTO Books ( BookID, AuthorID, BookName ) VALUES ( 1, 1, 'My Life' )
INSERT INTO Books ( BookID, AuthorID, BookName ) VALUES ( 2, 2, 'Writing Long Titles For Dummies' )
INSERT INTO Books ( BookID, AuthorID, BookName ) VALUES ( 3, 3, 'Read Me' )
...and our complex query that is throwing the error is...
INSERT INTO Publications SELECT Books.BookID, Books.BookName, Authors.AuthorID, Authors.AuthorName FROM Books JOIN Authors ON Books.AuthorID = Authors.AuthorID
...then we can find the columns that are likely to be offending like this...
Step 1
Convert your INSERT statement into a SELECT INTO statement and write the results to a temporary table like this...
SELECT Books.BookID, Books.BookName, Authors.AuthorID, Authors.AuthorName INTO ##MyResults FROM Books JOIN Authors ON Books.AuthorID = Authors.AuthorID
Step 2
Now execute the following T-SQL to compare the column definitions of your destination table with the source columns of your complex query...
SELECT
SourceColumns.[name] AS SourceColumnName,
SourceColumns.[type] AS SourceColumnType,
SourceColumns.[length] AS SourceColumnLength,
DestinationColumns.[name] AS SourceColumnName,
DestinationColumns.[type] AS SourceColumnType,
DestinationColumns.[length] AS SourceColumnLength
FROM
tempdb.sys.syscolumns SourceColumns
JOIN tempdb.sys.sysobjects SourceTable ON SourceColumns.[id] = SourceTable.[id]
LEFT JOIN sys.syscolumns DestinationColumns ON SourceColumns.colorder = DestinationColumns.colorder
LEFT JOIN sys.sysobjects DestinationTable ON DestinationColumns.[id] = DestinationTable.[id]
WHERE
SourceTable.Name = '##MyResults'
AND DestinationTable.Name = 'Publications'
You can adapt this query to filter down to certain column types (you know the problem is with string or binary data) and also where the length of the source column is greater than the destination columns. Armed with this information you should be left with only a few columns that could possible cause truncation and can start your search from there.
TIP! Check your destination columns for ON INSERT TRIGGERS!!
The issue is clear that one of your column in the table is having a length more than the destination table.
To find the length of the column which might be creating the issue you can run this query
Select Max(Len(Column1)) --Take only varchar columns in this.
, Max(Len(Column2))
, Max(Len(Column3))
From YourTable
Now you can check the length of string with the column length of your destination table. Most probably you will find any one column is having a length more than the specified length your destination table column.
Lets say you get that the column2 has the issue after executing the above query ie the length of your varchar is more than the column length. Then to find the specific value you can run this query:
select * from yourtable
where len(column2)>20 --change 20 to the actual value of your column2
This will print your error message and store incorrect values in a global temp table. It's not ideal and will be applicable in all situations, but it works.
Our Tables
IF OBJECT_ID('dbo.yourTable') IS NOT NULL
DROP TABLE dbo.yourTable;
IF OBJECT_ID('tempdb..#valuesToBeInserted') IS NOT NULL
DROP TABLE #valuesToBeInserted;
CREATE TABLE yourTable
(
ID INT IDENTITY(1,1),
Col1 CHAR(2),
Col2 VARCHAR(5),
Col3 VARCHAR(10)
);
GO
SELECT * INTO #valuesToBeInserted
FROM
(
SELECT '12' col1,'12345' col2,'1234567890' col3 --good value
UNION ALL
SELECT '123','12345','1234567890' --bad value
UNION ALL
SELECT '12','123456','1234567890' --bad value
) A
Actual solution
BEGIN TRY
INSERT INTO yourTable(Col1,col2,col3)
SELECT *
FROM #valuesToBeInserted
END TRY
BEGIN CATCH
IF OBJECT_ID('tempdb..##TruncatedResults') IS NOT NULL
DROP TABLE ##TruncatedResults;
PRINT ERROR_MESSAGE() + CHAR(13) + 'Truncated values are in ##truncatedResults'
SELECT
CASE
WHEN DATALENGTH(Col1) > 2 THEN 1 ELSE 0
END AS isCol1Truncated,
CASE
WHEN DATALENGTH(Col2) > 5 THEN 1 ELSE 0
END AS isCol2Truncated,
CASE
WHEN DATALENGTH(Col3) > 10 THEN 1 ELSE 0
END AS isCol3Truncated,
* --lazy man's select
--col1,
--col2,
--col3
INTO ##truncatedResults --global temp table
FROM #valuesToBeInserted
WHERE DATALENGTH(Col1) > 2
OR DATALENGTH(Col2) > 5
OR DATALENGTH(Col3) > 10
END CATCH
If you wanted to create a dynamic SQL solution or just don't want to type it out, try this to create your CASE statements and where clause
DECLARE #caseStatement VARCHAR(MAX),
#whereClause VARCHAR(MAX);
SELECT #caseStatement = COALESCE(#caseStatement + ',','') + 'CASE WHEN ' + CONCAT('DATALENGTH(',COLUMN_NAME,') > ',CHARACTER_MAXIMUM_LENGTH) + ' THEN 1 ELSE 0 END AS Is' + COLUMN_NAME + 'Truncated',
#whereClause = COALESCE(#whereClause,'') + CONCAT('DATALENGTH(',COLUMN_NAME,') > ',CHARACTER_MAXIMUM_LENGTH,' OR ')
FROM INFORMATION_SCHEMA.COLUMNS
WHERE CHARACTER_MAXIMUM_LENGTH > 0
AND TABLE_NAME = 'yourTable'
SELECT #whereClause = 'WHERE ' + SUBSTRING(#whereClause,1,LEN(#whereClause) - 3)
SELECT #caseStatement
SELECT #whereClause
Results:
CASE WHEN DATALENGTH(Col1) > 2 THEN 1 ELSE 0 END AS IsCol1Truncated,CASE WHEN DATALENGTH(Col2) > 5 THEN 1 ELSE 0 END AS IsCol2Truncated,CASE WHEN DATALENGTH(Col3) > 10 THEN 1 ELSE 0 END AS
WHERE DATALENGTH(Col1) > 2 OR DATALENGTH(Col2) > 5 OR DATALENGTH(Col3) > 10
A 2016/2017 update will show you the bad value and column.
A new trace flag will swap the old error for a new 2628 error and will print out the column and offending value. Traceflag 460 is available in the latest cumulative update for 2016 and 2017:
More info:
https://stackoverflow.com/a/63474873/852208
This error is occurring due to less size for some column, but you are trying to insert more length of Text in to that column.
For Ex:
EMP_Name varchar(10)
and you are trying to insert/update
JOHN VOUGER
during the above case this expections occur. So, first check with the varchar columns and if possible increase the size of the column.
I'm trying to convert this table
ID TestID Elapsed ActionID
===================================
1 1 16 a1
2 1 17 a2
3 1 13 a3
4 1 14 a4
5 2 19 a1
6 2 21 a2
7 2 11 a3
8 2 22 a4
To this
TestID a1 a2 a3 a4
======================================
1 16 17 13 14
2 19 21 11 22
is this possible?
Yes, if there is only one action id for each testid
There is the pivot operator that Ajoe mentioned, but I think the traditional
syntax is easier to understand (if not immediately obvious).
You group rows by testid, so you will get one row of results
per each testid. What you select is the "max" in each group where the acitionid is a certain one. Or the min, or the average, or the sum - this is
predicated on there being only one item in each group.
SELECT testid,
MAX(CASE WHEN actionid = 'a1' THEN elapsed ELSE null END) AS a1,
MAX(CASE WHEN actionid = 'a2' THEN elapsed ELSE null END) AS a2,
MAX(CASE WHEN actionid = 'a3' THEN elapsed ELSE null END) AS a3,
MAX(CASE WHEN actionid = 'a4' THEN elapsed ELSE null END) AS a4
FROM results
GROUP BY testid
If you are using SQL Server 2005 (or above), here's the query, with proof of concept. Enjoy:
--Proof of concept structure and data creation
create table #t (ID int, TestID int, Elapsed int, ActionID varchar(10))
insert into #t (ID, TestID, Elapsed, ActionID) values
(1, 1, 16, 'a1'),
(2, 1, 17, 'a2'),
(3, 1, 13, 'a3'),
(4, 1, 14, 'a4'),
(5, 2, 19, 'a1'),
(6, 2, 21, 'a2'),
(7, 2, 11, 'a3'),
(8, 2, 22, 'a4');
--end of structure and data creating
--actual query starts here
DECLARE #cols VARCHAR(1000)
DECLARE #sqlquery VARCHAR(2000)
SELECT #cols = STUFF(( SELECT distinct ',' + QuoteName([ActionID])
FROM #t FOR XML PATH('') ), 1, 1, '')
SET #sqlquery = 'SELECT * FROM
(SELECT TestID, Elapsed, ActionID
FROM #t ) base
PIVOT (SUM(Elapsed) FOR [ActionID]
IN (' + #cols + ')) AS finalpivot'
--Depending on your approach, you might want to use MAX instead of SUM.
--That will depend on your business rules
EXECUTE ( #sqlquery )
--query ends here
--proof of concept cleanup
drop table #t;
This will work no matter how many different values you have in ActionID. It dynamically assembles a query with PIVOT. The only way you can do PIVOT with dynamic columns is by assembling the the query dynamically, which can be done in SQL Server.
Other examples:
SQL Server PIVOT perhaps?
Pivot data in T-SQL
How do I build a summary by joining to a single table with SQL Server?