Can this pivot be done more efficiently? - sql-server

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

Related

Merging multiple rows with same ID in SQL Server

What is the most efficient method to combine multiple rows of values with the same ID in SQL Server?
Original data table dbo.ProductCategory:
+-----------+----------+------+
| ProductID | CATID | AA |
+-----------+----------+------+
| 1 | 123 | A |
| 1 | 412 | B |
| 2 | 567 | C |
| 2 | 521 | A |
| 3 | 2 | D |
| 3 | 5 | A |
| 4 | 6 | C |
| 4 | 8 | E |
| 4 | 123 | A |
+----+------+----------+------+
And I'm trying to achieve the following result
+-----------+----------+------+
| ProductID | CATID | AA |
+-----------+----------+------+
| 1 | 123,412 | A,B |
| 2 | 567,521 | C,A |
| 3 | 2,5 | D,A |
| 4 | 6,8,123 | C,E,A|
+----+------+----------+------+
In SQL Server 2017+, you can use STRING_AGG
select ProductId, STRING_AGG(CATID, ',') as CATID, STRING_AGG(AA, ',') AA
from PC
GROUP BY ProductID
Sample Data
DECLARE #Temp AS TABLE (ProductID INT, CATID INT, AA CHAR(2))
INSERT INTO #Temp
SELECT 1 , 123 , 'A' UNION ALL
SELECT 1 , 412 , 'B' UNION ALL
SELECT 2 , 567 , 'C' UNION ALL
SELECT 2 , 521 , 'A' UNION ALL
SELECT 3 , 2 , 'D' UNION ALL
SELECT 3 , 5 , 'A' UNION ALL
SELECT 4 , 6 , 'C' UNION ALL
SELECT 4 , 8 , 'E' UNION ALL
SELECT 4 , 123 , 'A'
Using STUFF() In sql server
SELECT ProductID,STUFF((SELECT CONCAT(', ', CATID)
FROM #Temp i
WHERE i.ProductID = o.ProductID
FOR XML PATH ('')),1,1,'') AS CATID
,STUFF((SELECT CONCAT(', ', AA)
FROM #Temp i
WHERE i.ProductID = o.ProductID
FOR XML PATH ('')),1,1,'') AS AA
FROM #Temp o
GROUP BY ProductID
Result
ProductID CATID AA
------------------------------------
1 123, 412 A , B
2 567, 521 C , A
3 2, 5 D , A
4 6, 8, 123 C , E , A

SQL - Finding how long the account haven't been paid

I have the below table:
+--------+------+---------+------------+
| ID | TEAM | WEEK_NO | Money_paid |
+--------+------+---------+------------+
| 112233 | AA | 201801 | 100 |
| 112233 | BB | 201801 | 0 |
| 112233 | BB | 201802 | 0 |
| 112233 | BB | 201803 | 0 |
| 454545 | AA | 201801 | 100 |
| 658855 | BB | 201802 | 100 |
| 658855 | BB | 201802 | 100 |
| 112233 | BB | 201809 | 0 |
+--------+------+---------+------------+
and I want the result like the below table with this rule that
Count the consecutive week_no.s where the Money_paid is same. It has be in consecutive order:
+--------+------+---------+------------+-------+
| ID | TEAM | WEEK_NO | Money_paid | Count |
+--------+------+---------+------------+-------+
| 112233 | AA | 201801 | 100 | 1 |
| 112233 | BB | 201801 | 0 | 3 |
| 112233 | BB | 201802 | 0 | 3 |
| 112233 | BB | 201803 | 0 | 3 |
| 454545 | AA | 201801 | 100 | 1 |
| 658855 | BB | 201802 | 100 | 1 |
| 112233 | BB | 201809 | 0 | 1 |
+--------+------+---------+------------+-------+
So far I have tried many methods but none of them is any close to the desired result.
select top 10
Concat(TEAM, ID) AS Concatbu_ac, count(*)
from
(select
*,
(row_number() over (order by week_no) -
row_number() over (partition by Concat(team, ID) order by week_no)
) as group5
from
table5) t
group by
group5, Concat(team, ID);
If I understand correctly, you want to count the week based on amount paid by ID & Team. The idea is to count the record group by ID, Team, Money_Paid, then self join by unique column. Hope this answer your question.
DECLARE #dataset TABLE (
ID INT,
Team VARCHAR(10),
Week_No INT,
Money_Paid INT
)
INSERT INTO #dataset SELECT 112233, 'AA', 201801, 100
INSERT INTO #dataset SELECT 112233, 'BB', 201801, 0
INSERT INTO #dataset SELECT 112233, 'BB', 201802, 0
INSERT INTO #dataset SELECT 112233, 'BB', 201803, 0
INSERT INTO #dataset SELECT 454545, 'AA', 201801, 100
INSERT INTO #dataset SELECT 658855, 'BB', 201802, 100 --duplicate
INSERT INTO #dataset SELECT 658855, 'BB', 201802, 100 --duplicate
INSERT INTO #dataset SELECT 112233, 'BB', 201809, 0
--week count
SELECT
ds.*,
x.WeekCount
FROM #dataset ds
JOIN (
SELECT
ID,
Team,
COUNT(Week_No) AS WeekCount
FROM #dataset
GROUP BY ID, Team, Money_Paid
) x
ON ds.ID = x.ID
AND ds.Team = x.Team
ORDER BY ID, Team, Week_No
--week to date
SELECT
ds.*,
COUNT(*) OVER (PARTITION BY ID, Team, Money_Paid ORDER BY ID, Team, Week_No) AS WeekToDate
FROM #dataset ds

Include rows with 0s or NULLs in the SQL query output

I am querying data (multiple columns) for different item types through a UNION of 2 different queries. If there are no values in any of those columns for a particular item type, that record does not show up. But, I need all rows (including empty ones) pertaining to each item type. The empty rows can show 0.
My data is:
create table sales_table ([yr] int, [qtr] varchar(40), [item_type] varchar(40), [sale_price] int);
create table profit_table ([yr] int, [qtr] varchar(40), [item_type] varchar(40), [profit] int);
create table item_table ([item_type] varchar(40));
insert into sales_table values
(2010,'Q1','abc',31),(2010,'Q1','def',23),(2010,'Q1','mno',12),(2010,'Q1','xyz',7),(2010,'Q2','abc',54),(2010,'Q2','def',67),(2010,'Q2','mno',92),(2010,'Q2','xyz',8);
insert into profit_table values
(2010,'Q1','abc',10),(2010,'Q1','def',6),(2010,'Q1','mno',23),(2010,'Q1','xyz',7),(2010,'Q2','abc',21),(2010,'Q2','def',13),(2010,'Q2','mno',15),(2010,'Q2','xyz',2);
insert into item_table values
('abc'),('def'),('ghi'),('jkl'),('mno'),('xyz');
My Query is:
SELECT a.yr, a.qtr, b.item_type, MAX(a.sales), MAX(a.avg_price), MAX(a.profit)
FROM
(SELECT [yr], [qtr],
CASE WHEN item_type IN ('mno', 'xyz') THEN 'Other' else upper(item_type) END AS [item_type],
COUNT(sale_price) OVER (PARTITION BY yr, qtr, item_type) [sales],
AVG(sale_price) OVER (PARTITION BY yr, qtr, item_type) [avg_price],
NULL [profit]
FROM sales_table
WHERE yr >=2010
UNION ALL
SELECT yr, qtr,
CASE WHEN item_type IN ('mno', 'xyz') THEN 'Other' else upper(item_type) END AS [item_type],
NULL [sales],
NULL [avg_price],
SUM(profit) OVER (PARTITION BY yr, qtr, item_type) [profit]
FROM profit_table
WHERE yr >=2010
) a
FULL OUTER JOIN
(SELECT
CASE WHEN item_type IN ('mno', 'xyz') THEN 'Other' else upper(item_type) END AS [item_type]
FROM item_table
WHERE item_type in ('abc','def','ghi','jkl','mno','xyz')
) b
ON a.item_type = b.item_type
GROUP BY a.yr, a.qtr, b.item_type
ORDER BY a.yr, a.qtr, b.item_type;
The current output is like this:
yr qtr item_type sales avg_price profit
(null) (null) GHI (null) (null) (null)
(null) (null) JKL (null) (null) (null)
2010 Q1 ABC 1 31 10
2010 Q1 DEF 1 23 6
2010 Q1 Other 1 12 23
2010 Q2 ABC 1 54 21
2010 Q2 DEF 1 67 13
2010 Q2 Other 1 92 15
What I want is like as shown below.
yr qtr item_type sales avg_price profit
2010 Q1 ABC 1 31 10
2010 Q1 DEF 1 23 6
2010 Q1 GHI 0 0 0
2010 Q1 JKL 0 0 0
2010 Q1 Other 2 9.5 30
2010 Q2 ABC 1 54 21
2010 Q2 DEF 1 67 13
2010 Q2 GHI 0 0 0
2010 Q2 JKL 0 0 0
2010 Q2 Other 2 50 17
Please advise.
Here you go. Just the way it's described in the comments.
I made a mini calendar table, but you'll want to spend some time making a real one. Once you have it, you'll use it all the time.
if OBJECT_ID('tempdb..#sales_table', 'U') is not null
drop table #sales_table
if OBJECT_ID('tempdb..#profit_table', 'U') is not null
drop table #profit_table
if OBJECT_ID('tempdb..#item_table', 'U') is not null
drop table #item_table
if OBJECT_ID('tempdb..#date_table', 'U') is not null
drop table #date_table
create table #sales_table
(
[yr] int
, [qtr] varchar(40)
, [item_type] varchar(40)
, [sale_price] int
);
create table #profit_table
(
[yr] int
, [qtr] varchar(40)
, [item_type] varchar(40)
, [profit] int
);
create table #item_table
(
[item_type] varchar(40)
);
create table #date_table
(
[yr] int
, [qtr] varchar(2)
);
insert into #sales_table values
(2010,'Q1','abc',31)
,(2010,'Q1','def',23)
,(2010,'Q1','mno',12)
,(2010,'Q1','xyz',7)
,(2010,'Q2','abc',54)
,(2010,'Q2','def',67)
,(2010,'Q2','mno',92)
,(2010,'Q2','xyz',8);
insert into #profit_table values
(2010,'Q1','abc',10)
,(2010,'Q1','def',6)
,(2010,'Q1','mno',23)
,(2010,'Q1','xyz',7)
,(2010,'Q2','abc',21)
,(2010,'Q2','def',13)
,(2010,'Q2','mno',15)
,(2010,'Q2','xyz',2);
insert into #item_table values
('abc'),('def'),('ghi'),('jkl'),('mno'),('xyz');
insert into #date_table values
(2010,'Q1'),(2010,'Q2'), (2010,'Q3'),(2010,'Q4');
SELECT
b.yr
, b.qtr
, b.item_type
, COALESCE(MAX(a.sales),0) AS sales
, COALESCE(MAX(a.avg_price),0) AS avg_price
, COALESCE(MAX(a.profit),0) AS profit
FROM
(
SELECT
dt.[yr]
,dt.[qtr]
,CASE
WHEN it.[item_type] IN ('mno', 'xyz') THEN 'Other'
ELSE UPPER(it.[item_type])
END AS [item_type]
FROM
#date_table AS dt
CROSS JOIN
#item_table AS it
WHERE
dt.[yr] >=2010
GROUP BY
dt.[yr]
,dt.[qtr]
,CASE
WHEN it.[item_type] IN ('mno', 'xyz') THEN 'Other'
ELSE UPPER(it.[item_type])
END
) AS b
LEFT JOIN
(SELECT [yr], [qtr],
CASE
WHEN item_type IN ('mno', 'xyz') THEN 'Other'
ELSE UPPER([item_type])
END AS [item_type],
COUNT(sale_price) OVER (PARTITION BY yr, qtr, item_type) [sales],
AVG(sale_price) OVER (PARTITION BY yr, qtr, item_type) [avg_price],
NULL [profit]
FROM #sales_table
WHERE yr >=2010
UNION ALL
SELECT yr, qtr,
CASE
WHEN item_type IN ('mno', 'xyz') THEN 'Other'
ELSE UPPER([item_type])
END AS [item_type],
NULL [sales],
NULL [avg_price],
SUM(profit) OVER (PARTITION BY yr, qtr, item_type) [profit]
FROM #profit_table
WHERE yr >=2010
) a
ON
a.[yr] = b.[yr]
AND
a.[qtr] = b.[qtr]
AND
a.[item_type] = b.[item_type]
GROUP BY
b.yr, b.qtr, b.item_type
ORDER BY b.yr, b.qtr, b.item_type;
Results:
+------+-----+-----------+-------+-----------+--------+
| yr | qtr | item_type | sales | avg_price | profit |
+------+-----+-----------+-------+-----------+--------+
| 2010 | Q1 | ABC | 1 | 31 | 10 |
| 2010 | Q1 | DEF | 1 | 23 | 6 |
| 2010 | Q1 | GHI | 0 | 0 | 0 |
| 2010 | Q1 | JKL | 0 | 0 | 0 |
| 2010 | Q1 | Other | 1 | 12 | 23 |
| 2010 | Q2 | ABC | 1 | 54 | 21 |
| 2010 | Q2 | DEF | 1 | 67 | 13 |
| 2010 | Q2 | GHI | 0 | 0 | 0 |
| 2010 | Q2 | JKL | 0 | 0 | 0 |
| 2010 | Q2 | Other | 1 | 92 | 15 |
| 2010 | Q3 | ABC | 0 | 0 | 0 |
| 2010 | Q3 | DEF | 0 | 0 | 0 |
| 2010 | Q3 | GHI | 0 | 0 | 0 |
| 2010 | Q3 | JKL | 0 | 0 | 0 |
| 2010 | Q3 | Other | 0 | 0 | 0 |
| 2010 | Q4 | ABC | 0 | 0 | 0 |
| 2010 | Q4 | DEF | 0 | 0 | 0 |
| 2010 | Q4 | GHI | 0 | 0 | 0 |
| 2010 | Q4 | JKL | 0 | 0 | 0 |
| 2010 | Q4 | Other | 0 | 0 | 0 |
+------+-----+-----------+-------+-----------+--------+

How do convert row column values in to a column value

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;

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