How do convert row column values in to a column value - sql-server

How do we convert row column values into column values.
for eg:-
I have 5 rows, each rows have 5 columns. I need to convert based on the ID.
My query is:
Select id,year,height,weight,date from user_det
---------------------------------------------------------
| Id | Year | height| weight| date |
---------------------------------------------------------
| 1 | 20082009 | 122 | 23 | 4/15/2009 |
---------------------------------------------------------
| 1 | 20092010 | 135 | 39 | 3/19/2010 |
---------------------------------------------------------
| 2 | 20082009 | 132 | 20 | 2/23/2009 |
---------------------------------------------------------
| 3 | 20142015 | 133 | 28 | 2/24/2015 |
---------------------------------------------------------
If I group by id maximum is 2. I need Result like below table
id | year1 | height1 |weight1 | date1 | year2 | height2|weight2|date2
-------------------------------------------------------------------------------
1 |20082009| 122 | 23 |4/15/2009| 20092010 | 135 | 39 |3/19/2010
--------------------------------------------------------------------------------
2 |20082009 | 135 | 20 |2/23/2009| | | |
--------------------------------------------------------------------------------
3 |20152015 | 133 | 28 |2/24/2015| | | |

You can do this with pivot or conditional aggregation. But, you need a column for the pivot:
select id,
max(case when seqnum = 1 then year end) as year_1,
max(case when seqnum = 1 then height end) as height_1,
max(case when seqnum = 1 then weight end) as weight_1,
max(case when seqnum = 1 then date end) as date_1,
max(case when seqnum = 2 then year end) as year_2,
max(case when seqnum = 2 then height end) as height_2,
max(case when seqnum = 2 then weight end) as weight_2,
max(case when seqnum = 2 then date end) as date_2
from (select t.*,
row_number() over (partition by id order by year) as seqnum
from user_det t
) t
group by id;

Related

Can this pivot be done more efficiently?

I managed to find a solution for formatting the shown driver table. The result is exactly what i need: One row for every QuoteID with the columns Birthdate and DriverType seperated by DriverIndex. My real drivertable has millions of rows.
Yet i am not convinced that this is the way to go. It just seems odd to me. But i am not a SQL expert. My Question: Can this be done in a more efficent way?
SQL Fiddle
MS SQL Server 2017 Schema Setup:
CREATE TABLE driver
([QuoteID] int, [DriverIndex] int,[Birthdate] date,[DriverType] int)
;
INSERT INTO driver
([QuoteID], [DriverIndex],[Birthdate], [DriverType])
VALUES
('72', '1','2022/01/01','11'),
('72', '2','2022/02/01','12'),
('73', '1','2022/03/01','13'),
('74', '1','2022/04/01','13'),
('73', '2','2022/05/01','10'),
('73', '3','2022/06/01','11');
Driver Table:
| QuoteID | DriverIndex | Birthdate | DriverType |
|---------|-------------|------------|------------|
| 72 | 1 | 2022-01-01 | 11 |
| 72 | 2 | 2022-02-01 | 12 |
| 73 | 1 | 2022-03-01 | 13 |
| 74 | 1 | 2022-04-01 | 13 |
| 73 | 2 | 2022-05-01 | 10 |
| 73 | 3 | 2022-06-01 | 11 |
Query:
with sq as(select QuoteID AS QuoteID_sq, [1] AS DriverIndex_1_DriverType , [2] AS DriverIndex_2_DriverType , [3] as DriverIndex_3_DriverType
from
( select [QuoteID], [DriverIndex],[Birthdate], [DriverType] from driver) src
pivot
( max([DriverType]) for DriverIndex in ([1], [2], [3]) ) piv),
sq2 as(select QuoteID as QuoteID_sq2, [1] AS DriverIndex_1_Birthdate , [2] AS DriverIndex_2_Birthdate , [3] as DriverIndex_3_Birthdate
from
( select [QuoteID], [DriverIndex],[Birthdate], [DriverType] from driver) src
pivot
( max([Birthdate]) for DriverIndex in ([1], [2], [3]) ) piv),
sq3 as(Select * from sq,sq2 Where sq.QuoteID_sq=sq2.QuoteID_sq2)
Select QuoteID_sq as QuoteID, max([DriverIndex_1_DriverType]) AS DriverIndex_1_DriverType,MAX([DriverIndex_2_DriverType]) AS DriverIndex_2_DriverType,Max([DriverIndex_3_DriverType]) AS DriverIndex_3_DriverType ,
max([DriverIndex_1_Birthdate]) AS DriverIndex_1_Birthdate , max([DriverIndex_2_Birthdate]) AS DriverIndex_2_Birthdate , max([DriverIndex_3_Birthdate]) as DriverIndex_3_Birthdate
from sq3
group by QuoteID_sq
Results:
| QuoteID | DriverIndex_1_DriverType | DriverIndex_2_DriverType | DriverIndex_3_DriverType | DriverIndex_1_Birthdate | DriverIndex_2_Birthdate | DriverIndex_3_Birthdate |
|---------|--------------------------|--------------------------|--------------------------|-------------------------|-------------------------|-------------------------|
| 72 | 11 | 12 | (null) | 2022-01-01 | 2022-02-01 | (null) |
| 73 | 13 | 10 | 11 | 2022-03-01 | 2022-05-01 | 2022-06-01 |
| 74 | 13 | (null) | (null) | 2022-04-01 | (null) | (null) |
You can simplify this by using only conditional aggregation.
SELECT QuoteID
, MAX(CASE WHEN DriverIndex = 1 THEN DriverType END) AS DriverIndex_1_DriverType
, MAX(CASE WHEN DriverIndex = 2 THEN DriverType END) AS DriverIndex_2_DriverType
, MAX(CASE WHEN DriverIndex = 3 THEN DriverType END) AS DriverIndex_3_DriverType
, MAX(CASE WHEN DriverIndex = 1 THEN Birthdate END) AS DriverIndex_1_Birthdate
, MAX(CASE WHEN DriverIndex = 2 THEN Birthdate END) AS DriverIndex_2_Birthdate
, MAX(CASE WHEN DriverIndex = 3 THEN Birthdate END) AS DriverIndex_3_Birthdate
FROM driver
GROUP BY QuoteID
ORDER BY QuoteID;
QuoteID
DriverIndex_1_DriverType
DriverIndex_2_DriverType
DriverIndex_3_DriverType
DriverIndex_1_Birthdate
DriverIndex_2_Birthdate
DriverIndex_3_Birthdate
72
11
12
null
2022-01-01
2022-02-01
null
73
13
10
11
2022-03-01
2022-05-01
2022-06-01
74
13
null
null
2022-04-01
null
null
Demo on db<>fiddle here

Working Out The Time Difference Between Two Rows On Multiple Occurances

I'm working on what will be a new function but I'm trying to get a foundation of how to work out the time difference between two rows every time a value appears in two other columns appear (prev_value, current_value). The difference will then be wrapped in a sum for a final value against each ID.
The example below is a small portion of the table, so if we use ID 194422 hopefully I can get my point across.
The Datediff I'm trying to work out would be between every record that has a Current_Value 12026 and Prev_Value 12026. So between the top 2 rows, I would expect 3 minute difference and between rows 3 and 4 , I would expect a 4 minute difference.
If the row contains a current_value and prev_value of 12026 then I would also include this in the time so on row 13,14,15 I would expect a 9 min difference between row 13 and 15.
I would then group and sum these results by the ID.
ROWID| ID | Columnname | prev_value | current_value | OperationTime
1 |197684 | STATUSID | 1 | 12026 | 2020-02-11 13:37:00.0010
2 |197684 | STATUSID | 12026 | 1 | 2020-02-11 13:40:00.000
3 |197684 | STATUSID | 1 | 12026 | 2020-02-11 13:44:00.000
4 |197684 | STATUSID | 12026 | 1 | 2020-02-11 13:48:00.000
5 |198662 | STATUSID | 1 | 12026 | 2020-02-24 15:10:00.000
6 |198662 | STATUSID | 12026 | 1 | 2020-02-24 15:20:00.000
7 |198662 | STATUSID | 1 | 12026 | 2020-02-24 15:23:00.000
8 |198662 | STATUSID | 12026 | 1 | 2020-02-24 15:41:00.000
9 |198662 | STATUSID | 1 | 12026 | 2020-02-24 16:24:00.000
10 |198662 | STATUSID | 12026 | 1 | 2020-02-24 17:05:00.000
11 |194422 | STATUSID | 1 | 12026 | 2020-02-25 09:04:00.000
12 |194422 | STATUSID | 12026 | 8 | 2020-02-25 09:07:00.000
13 |198662 | STATUSID | 1 | 12026 | 2020-02-26 15:32:00.000
14 |198662 | STATUSID | 12026 | 12026 | 2020-02-26 15:40:00.000
15 |198662 | STATUSID | 12026 | 1 | 2020-02-26 15:41:00.000
16 |194422 | STATUSID | 1 | 12026 | 2020-03-02 16:06:00.000
17 |194422 | STATUSID | 12026 | 8 | 2020-03-02 16:15:00.000
At the end result would then be:
RowID | TimeSpent(Mins)
194422 | 9
197684 | 7
198662 | 78
I've now tried a few different methods but records are either missing or incorrect times are being retrieved.
I've look at a CTE with an INNER and LEFT JOIN and OUTER JOIN back on the CTE, I've tried two CTE's, I've looked at LAG, RANK and ROW_NUMBER() in all examples.
The script below sort of works but it doesn't pull in all my results and in this example it misses ID 194422.
DECLARE #STATUSID INT
SET #STATUSID = 12026
;WITH CTE
AS (SELECT prev_value,
current_value,
OPERATIONTIME,
ROWID,
ID,
DENSE_RANK () OVER (PARTITION BY prev_value ORDER BY operationtime) AS ROWNUMBER
FROM AUDITREVIEW
WHERE columnname = 'STATUSID'
AND ( current_value = #STATUSID
OR prev_value = #STATUSID )
)
SELECT *,
DATEDIFF(MINUTE, CTE2.cte2OPERATIONTIME, CTE1.operationtime) as t
INTO #Temp
FROM
CTE AS CTE1
OUTER APPLY
(SELECT top 1
prev_value as cte2prev_value,
current_value cte2current_value,
OPERATIONTIME cte2OPERATIONTIME,
ROWID cte2rowID,
ID cte2ID,
DENSE_RANK () OVER ( ORDER BY operationtime) AS cte2ROWNUMBER
FROM CTE
WHERE CTE.ID = CTE1.ID
AND CTE.PREV_VALUE = CTE1.CURRENT_VALUE
AND CTE.ROWNUMBER < CTE1.ROWNUMBER
ORDER BY CTE.OPERATIONTIME DESC
) CTE2
--WHERE CTE1.WORKORDERID = 194422
SELECT SUM(t), ID
FROM #Temp
WHERE cte2prev_value <> #STATUSID
GROUP BY ID
DROP TABLE #Temp
try the following:
SELECT a1.ID,
SUM(DATEDIFF(MINUTE, a1.OperationTime, a2.OperationTime)) Time_Spent_in_Minutes
FROM AUDITREVIEW a1
JOIN AUDITREVIEW a2 ON a2.ID = a1.ID
AND ((a1.ROWID = a2.ROWID)
OR (a1.ROWID + 1 = a2.ROWID))
WHERE(a1.current_value = 12026 AND a2.prev_value = 12026)
AND ((a1.prev_value = 1 AND a2.current_value = 1)
OR (a1.current_value = 12026 AND a1.prev_value = 12026)
OR (a2.current_value = 12026 AND a2.prev_value = 12026))
GROUP BY a1.ID
ORDER BY 1;
db<>fiddle demo

Multiple pivots

I have a table that looks like the following:
+---------+----------+---------+-------+-----------+-----------+-------------+
| ValueId | ObjectId | Field | Value | Estimated | OrigValue | FromDefault |
+---------+----------+---------+-------+-----------+-----------+-------------+
| 1 | 1 | 'Stat1' | 35 | true | (null) | (null) |
| 2 | 1 | 'Stat2' | 2 | false | 0 | true |
| 3 | 1 | 'Stat3' | 0.213 | true | 0.212 | false |
| 4 | 2 | 'Stat1' | 513 | true | 122 | true |
| 5 | 2 | 'Stat2' | 31 | true | (null) | true |
| 6 | 2 | 'Stat3' | 2.411 | true | (null) | false |
+---------+----------+---------+-------+-----------+-----------+-------------+
Fiddle: http://www.sqlfiddle.com/#!9/445271/2/0
And I want the pivot(s) to look like this:
+----------+-------+-------+-------+-----------------+-----------------+-------------------+-----------------+-----------------+-------------------+-----------------+-----------------+-------------------+
| ObjectId | Stat1 | Stat2 | Stat3 | Stat1_Estimated | Stat1_OrigValue | Stat1_FromDefault | Stat2_Estimated | Stat2_OrigValue | Stat2_FromDefault | Stat3_Estimated | Stat3_OrigValue | Stat3_FromDefault |
+----------+-------+-------+-------+-----------------+-----------------+-------------------+-----------------+-----------------+-------------------+-----------------+-----------------+-------------------+
| 1 | 35 | 2 | 0.213 | true | (null) | (null) | false | false | true | true | 0.212 | false |
| 2 | 513 | 31 | 2.411 | true | 122 | true | true | (null) | true | true | (null) | false |
+----------+-------+-------+-------+-----------------+-----------------+-------------------+-----------------+-----------------+-------------------+-----------------+-----------------+-------------------+
Fiddle: http://www.sqlfiddle.com/#!9/6e84ff2/2/0
I understand how to do the pivot to get the "value" as the field but not how I can include multiple pivots and get them to be named as as Field + _ + OriginalColumn
Edit: The number of distinct values for Field are well known as can be hard-coded into the answer.
SQL DEMO
Because the fields are know you can use conditional aggregation functions
SELECT ObjectId,
MAX(CASE WHEN Field = '''Stat1''' THEN Value END) as Stat1,
MAX(CASE WHEN Field = '''Stat2''' THEN Value END) as Stat2,
MAX(CASE WHEN Field = '''Stat3''' THEN Value END) as Stat3,
MAX(CASE WHEN Field = '''Stat1''' THEN Estimated END) as Stat_Estimated1,
MAX(CASE WHEN Field = '''Stat1''' THEN OrigValue END) as Stat_OrigValue1,
MAX(CASE WHEN Field = '''Stat1''' THEN FromDefault END) as Stat_FromDefault1,
MAX(CASE WHEN Field = '''Stat2''' THEN Estimated END) as Stat_Estimated2,
MAX(CASE WHEN Field = '''Stat2''' THEN OrigValue END) as Stat_OrigValue2,
MAX(CASE WHEN Field = '''Stat2''' THEN FromDefault END) as Stat_FromDefault2,
MAX(CASE WHEN Field = '''Stat3''' THEN Estimated END) as Stat_Estimated3,
MAX(CASE WHEN Field = '''Stat3''' THEN OrigValue END) as Stat_OrigValue3,
MAX(CASE WHEN Field = '''Stat3''' THEN FromDefault END) as Stat_FromDefault3
FROM mytable
GROUP BY ObjectId
;
You can also arrange the data and use the PIVOT function. Be aware value column can only contain one data type so I convert boolean to 0/1
SQL DEMO
SELECT ObjectId, Field, Value
FROM myTable
UNION ALL
SELECT ObjectId, CONCAT(Field, '_Estimated'), CASE WHEN Estimated IS NULL THEN NULL
WHEN Estimated THEN 1
ELSE 0
END
FROM myTable
UNION ALL
SELECT ObjectId, CONCAT(Field, '_OrigValue'), OrigValue
FROM myTable
UNION ALL
SELECT ObjectId, CONCAT(Field, '_FromDefault'), CASE WHEN FromDefault IS NULL THEN NULL
WHEN FromDefault THEN 1
ELSE 0
END
FROM myTable
;

SQL Server GROUP BY multiple columns

Let's assume I have in SQL Server the following table with only seven days available (SUN - SAT):
Orders
| Day | ProductType | Price |
| SUN | 1 | 10 |
| MON | 1 | 15 |
| MON | 2 | 20 |
| MON | 3 | 10 |
| TUE | 1 | 5 |
| TUE | 3 | 5 |
...
I need to group the data in a way so that to see the Total sum of Prices by each distinct Day and two groups of ProductType (= 1 and > 1):
| Day | FirstProductTypeTotal | RestProductsTypesTotal | GrandTotal |
| SUN | 10 | 0 | 10 |
| MON | 15 | 30 | 45 |
| TUE | 5 | 5 | 10 |
...
where FirstProductTypeTotal is ProductType = 1 and RestProductTypesTotal is ProductType > 1.
Is it possible to select this in one select instead of writing two different selects:
Select Day, SUM(Price) as FirstTotal from Orders where ProductType = 1 group by Day
and
Select Day, SUM(Price) as SecondTotal from Orders where ProductType > 1 group by Day
And then add FirstTotal and SecondTotal manually in the code to get the Grand total for each day of the week?
Use CASE Expression
Select Day, SUM(CASE WHEN ProductType = 1 THE Price ELSE 0 END) AS FirstTotal,
SUM(CASE WHEN ProductType > 1 THE Price ELSE 0 END) AS SecondTotal,
SUM(Price) AS GrandTotal
FROM Orders
group by Day
Try conditional aggregation;
Sample data;
CREATE TABLE #Orders ([Day] varchar(10), ProductType int, Price int)
INSERT INTO #Orders ([Day],ProductType, Price)
VALUES
('SUN',1,10)
,('MON',1,15)
,('MON',2,20)
,('MON',3,10)
,('TUE',1,5)
,('TUE',3,5)
Query;
SELECT
o.[Day]
,SUM(CASE WHEN o.ProductType = 1 THEN o.Price ELSE 0 END) FirstTotal
,SUM(CASE WHEN o.ProductType > 1 THEN o.Price ELSE 0 END) SecondTotal
,SUM(o.Price) GrandTotal
FROM #Orders o
GROUP BY o.[Day]
Result
Day FirstTotal SecondTotal GrandTotal
MON 15 30 45
SUN 10 0 10
TUE 5 5 10
You'd just need to sort out the ordering of the days because SQL Server by definition doesn't store the data in any particular order.

SQL Server 2012 Rows into Columns with varchar

I am looking for a solution for the following problem, which affects two tables. I already tried to search for the solution, but couldn't find the way to go.
single_value
| docId | siteNo | siteName | siteAccount | comment | docDate | extNo
---+------------+--------+---------------+-------------+---------+-------------------------+-------
1 | T000000095 | 201060 | Main Location | 92400 | NULL | 2014-10-31 00:00:00.000 | NULL
multi_value
| docId | field_no | row_no | value_char | value_date | value_num
---+------------+----------+--------+------------+------------+-----------
1 | T000000095 | 60 | 1 | NULL | NULL | 250.00
2 | T000000095 | 60 | 2 | NULL | NULL | -1.24
3 | T000000095 | 61 | 1 | Positive | NULL | NULL
4 | T000000095 | 61 | 2 | Negative | NULL | NULL
5 | T000000095 | 62 | 1 | NULL | NULL | 90000.00
6 | T000000095 | 62 | 2 | NULL | NULL | 688000.00
What I need is now an SQL statement which gives me an output like the following one for each row_no of the table multi_value for a specific docId:
| docId | siteNo | siteName | siteAccount | comment | docDate | extNo | amount | addInfo | costUnit
---+------------+--------+---------------+-------------+---------+-------------------------+-------|--------+----------+----------
1 | T000000095 | 201060 | Main Location | 92400 | NULL | 2014-10-31 00:00:00.000 | NULL | 250.00 | Positive | 90000.00
2 | T000000095 | 201060 | Main Location | 92400 | NULL | 2014-10-31 00:00:00.000 | NULL | -1.24 | Negative | 688000.00
It has to list all Information of the table 'single_value' and kind of transpose the values of the table 'multi_value'. The connection between both tables can be achieved via the docId.
The table 'multi_value' is designed in that way, that each field no only allows one specific column to be filled:
60 = value_num (amount)
61 = value_char (addInfo)
62 = value_num (costUnit)
What is the easiest way to achieve that? The table layout cannot be changed. For the transpose of the multi_value I already tried the following, but it doesn't work with the varchar datatype within the column value_char.
SELECT row_no
SUM(case when field_no = 60 then value_num else 0 end) as amount,
--(case when field_no = 61 then value_char else 0 end) as addInfo,
SUM(case when field_no = 62 then value_num else 0 end) as costUnit
FROM multi_value
WHERE docId = 'T000000095'
GROUP By
row_no
Many thanks in advance.
Thomas
Not very elegant, but works fine :)
SELECT row_no,
SUM(case when field_no = 60 then value_num else 0 end) as amount,
case SUM(case
when field_no = 61 then (
case value_char
when 'Positive' then 1
else 2 end
) else 0 end
) when 1 then 'Positive' else 'Negative' end as addInfo,
SUM(case when field_no = 62 then value_num else 0 end) as costUnit
FROM multi_value
GROUP By
row_no
UPDATE
WITH dist as
(
SELECT DISTINCT value_char
FROM multi_value
WHERE value_char is not null
)
, with_no as
(
select value_char, row_number() over(order by value_char) [no]
from dist
)
, [raw] as
(
SELECT m.row_no,
SUM(case when m.field_no = 60 then m.value_num else 0 end) as amount,
SUM(case when m.field_no = 61 then d.[no] else 0 end) as addInfo,
SUM(case when m.field_no = 62 then m.value_num else 0 end) as costUnit
FROM multi_value m
LEFT JOIN with_no as d on d.value_char = m.value_char
GROUP By
m.row_no
)
SELECT row_no
, amount
, d.value_char as addInfo
, costUnit
FROM [raw] r
LEFT JOIN with_no as d on d.[no] = r.addInfo

Resources