convert rows to columns order by another column in sql server - sql

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?

Related

SQL - Revert appended rows queried from Database table

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.

Pivot two columns based on a value without aggregations [duplicate]

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

T-SQL Summation

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

Show sum Column SQL code

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.

How do I join an unknown number of rows to another row?

I have this scenario:
Table A:
---------------
ID| SOME_VALUE|
---------------
1 | 123223 |
2 | 1232ff |
---------------
Table B:
------------------
ID | KEY | VALUE |
------------------
23 | 1 | 435 |
24 | 1 | 436 |
------------------
KEY is a reference to to Table A's ID. Can I somehow join these tables so that I get the following result:
Table C
-------------------------
ID| SOME_VALUE| | |
-------------------------
1 | 123223 |435 |436 |
2 | 1232ff | | |
-------------------------
Table C should be able to have any given number of columns depending on how many matching values that are found in Table B.
I hope this enough to explain what I'm after here.
Thanks.
You need to use a Dynamic PIVOT clause in order to do this.
EDIT:
Ok so I've done some playing around and based on the following sample data:
Create Table TableA
(
IDCol int,
SomeValue varchar(50)
)
Create Table TableB
(
IDCol int,
KEYCol int,
Value varchar(50)
)
Insert into TableA
Values (1, '123223')
Insert Into TableA
Values (2,'1232ff')
Insert into TableA
Values (3, '222222')
Insert Into TableB
Values( 23, 1, 435)
Insert Into TableB
Values( 24, 1, 436)
Insert Into TableB
Values( 25, 3, 45)
Insert Into TableB
Values( 26, 3, 46)
Insert Into TableB
Values( 27, 3, 435)
Insert Into TableB
Values( 28, 3, 437)
You can execute the following Dynamic SQL.
declare #sql varchar(max)
declare #pivot_list varchar(max)
declare #pivot_select varchar(max)
Select
#pivot_list = Coalesce(#Pivot_List + ', ','') + '[' + Value +']',
#Pivot_select = Coalesce(#pivot_Select, ', ','') +'IsNull([' + Value +'],'''') as [' + Value + '],'
From
(
Select distinct Value From dbo.TableB
)PivotCodes
Set #Sql = '
;With p as (
Select a.IdCol,
a.SomeValue,
b.Value
From dbo.TableA a
Left Join dbo.TableB b on a.IdCol = b.KeyCol
)
Select IdCol, SomeValue ' + Left(#pivot_select, Len(#Pivot_Select)-1) + '
From p
Pivot ( Max(Value) for Value in (' + #pivot_list + '
)
)as pvt
'
exec (#sql)
This gives you the following output:
Although this works at the moment it would be a nightmare to maintain. I'd recommend trying to achieve these results somewhere else. i.e not in SQL!
Good luck!
As Barry has amply illustrated, it's possible to get multiple columns using a dynamic pivot.
I've got a solution that might get you what you need, except that it puts all of the values into a single VARCHAR column. If you can split those results, then you can get what you need.
This method is a trick in SQL Server 2005 that you can use to form a string out of a column of values.
CREATE TABLE #TableA (
ID INT,
SomeValue VARCHAR(50)
);
CREATE TABLE #TableB (
ID INT,
TableAKEY INT,
BValue VARCHAR(50)
);
INSERT INTO #TableA VALUES (1, '123223');
INSERT INTO #TableA VALUES (2, '1232ff');
INSERT INTO #TableA VALUES (3, '222222');
INSERT INTO #TableB VALUES (23, 1, 435);
INSERT INTO #TableB VALUES (24, 1, 436);
INSERT INTO #TableB VALUES (25, 3, 45);
INSERT INTO #TableB VALUES (26, 3, 46);
INSERT INTO #TableB VALUES (27, 3, 435);
INSERT INTO #TableB VALUES (28, 3, 437);
SELECT
a.ID
,a.SomeValue
,RTRIM(bvals.BValues) AS ValueList
FROM #TableA AS a
OUTER APPLY (
-- This has the effect of concatenating all of
-- the BValues for the given value of a.ID.
SELECT b.BValue + ' ' AS [text()]
FROM #TableB AS b
WHERE a.ID = b.TableAKEY
ORDER BY b.ID
FOR XML PATH('')
) AS bvals (BValues)
ORDER BY a.ID
;
You'll get this as a result:
ID SomeValue ValueList
--- ---------- --------------
1 123223 435 436
2 1232ff NULL
3 222222 45 46 435 437
This looks like something a database shouldn't do. Firstly; a table cannot have arbitrary number of columns depending on whatever you'll store. So you will have to put up a maximum number of values anyway. You can get around this by using comma seperated values as value for that cell (or a similar pivot-like solution).
However; if you do have table A and B; i recommend keeping to those two tables; as they seem to be pretty normalised. Should you need a list of b.value given an input a.some_value, the following sql query gives that list.
select b.value from a,b where b.key=a.id a.some_value='INPUT_VALUE';