SQL Pivot and Distinct from new Columns - sql

I have a table called ADMIN that originally looks like this
KEY VALUE
Version1 2019_RQK#2019
Version2 2019_RQK#2020
Version2 2019_RQK#2021
Version2 2019_RQK#2022
Version2 2020_TKA#2020
Version2 2020_TKA#2021
Version2 2020_TKA#2022
Version2 2020_TKA#2023
And I am try to change it to look like this
VERSION YEAR1 YEAR2 YEAR3 YEAR4
2019_RQK 2019 2020 2021 2022
2020_TKA 2020 2021 2022 2023
I wrote some SQL in order to get the left and right versions of the [VALUE] columns but I dont know how to condense it so that it only shows a DISTINCT as for the left side of the [VALUE] column. I tried to use distinct but it still brings up the same repeated entries, this is what I've written so far, I dont know if PIVOT function would work here I tried a few things it didn't end up correct.
SELECT DISTINCT LEFT([VALUE], 7) AS VERSION, RIGHT([VALUE], 4) AS YEAR
FROM ADMIN
WHERE [KEY] LIKE '%VERSION%'
Just gives me, not sure how to change it in the same query
VERSION YEAR
2019_RQK 2019
2019_RQK 2020
2019_RQK 2021
2019_RQK 2022
2020_TKA 2020
2020_TKA 2021
2020_TKA 2022
2020_TKA 2023

So, yes, you need a PIVOT table to do that. You can learn all about them here, which has a pretty straightforward (and quick!) walkthrough to understand why it works magic.
To PIVOT this table, we need to add a column for YEAR1, YEAR2, etc.. so they can be our headers/new columns. I'll do that with a basic ROW_NUMBER function. I know this example has 4 maximum new columns per entry, so I'm hardcoding them in, but the link above explains how you can dynamically generate the IN statement if you have an unknown number of maximum columns.
Please note, my test table was created with col1 and col2 because I am lazy. You should swap those for the actual column names.
SELECT * FROM (
-- we start with your basic table, as you provided
SELECT
LEFT(col2, 7) AS VERSION,
RIGHT(col2, 4) AS YEAR,
ROW_NUMBER() OVER (partition by LEFT(col2, 7) order by RIGHT(col2, 4)) as YearNum /* sort these by the proper year, so we don't get of order */
FROM ADMIN
WHERE col1 LIKE '%VERSION%'
) versionResults
PIVOT (
max([YEAR]) -- grab the year
for [YearNum] -- this column holds our new column headers
in ( /* these are the possible YearNum values, now our new column headers */
[1],
[2],
[3],
[4]
)
) as pivotResults
Demo here.

You need to also extract the first 4 chars as the "base year", then subtract the "Year" from the "Base Year" (and add 1) to get an integer value (1-4) and use those as the PIVOT list.
Example Fiddle
The reason this is "difficult" is that you have 3 key values stored in 1 column. At least it's fixed width so easy enough to break apart consistently.
If the VALUE column contains differently formatted data, this won't work.
CREATE TABLE Admin
( Key1 char(8)
, Val char(13)
);
INSERT INTO Admin (Key1, Val)
VALUES
('Version1','2019_RQK#2019')
, ('Version2','2019_RQK#2020')
, ('Version2','2019_RQK#2021')
, ('Version2','2019_RQK#2022')
, ('Version2','2020_TKA#2020')
, ('Version2','2020_TKA#2021')
, ('Version2','2020_TKA#2022')
, ('Version2','2020_TKA#2023');
WITH Src AS (
SELECT
Version = SUBSTRING(Val,1,8)
, Year = CAST(SUBSTRING(Val,10,4) as int)
, YearCt = CAST(SUBSTRING(Val,10,4) as int) - CAST(SUBSTRING(Val,1,4) as int) + 1
FROM Admin
)
SELECT
pvt.Version
, Year1 = pvt.[1]
, Year2 = pvt.[2]
, Year3 = pvt.[3]
, Year4 = pvt.[4]
FROM Src
PIVOT (MAX(Year) FOR YearCt IN ([1],[2],[3],[4])) pvt;

Using dynamical pivoting such as below one would be a better option in terms of picking the currently inserted years with no applying any manipulation to the query while the data change
DECLARE #cols AS NVARCHAR(MAX), #query AS NVARCHAR(MAX)
SET #cols = ( SELECT STRING_AGG(CONCAT('[year',[n],']'),',')
FROM (SELECT DISTINCT
ROW_NUMBER() OVER
(PARTITION BY LEFT([value], 7) ORDER BY [value]) AS [n]
FROM [admin] ) q );
SET #query =
N'SELECT *
FROM
(
SELECT DISTINCT LEFT([value], 7) AS version, RIGHT([value], 4) AS year,
CONCAT(''year'',ROW_NUMBER() OVER
(PARTITION BY LEFT([value], 7)
ORDER BY RIGHT([value], 4))) AS [n]
FROM [admin]
WHERE [key] LIKE ''%VERSION%'' ) q
PIVOT (
MAX([year]) FOR [n] IN (' + #cols + N')
) p
ORDER BY [version]';
EXEC sp_executesql #query;
Demo
Btw, you might also split the value by # sign SUBSTRING([value],1,CHARINDEX('#',[value])-1) in order to extract version, and SUBSTRING([value],CHARINDEX('#',[value])+1,LEN([value])) in order to extract year columns without specifying the length values as arguments within the functions as an alternative.

Related

How to recursively calculate yearly rollover in SQL?

I need to calculate yearly rollover for a system that keeps track of when people have used days off.
The rollover calculation itself is simple: [TOTALDAYSALLOWED] - [USED]
Provided that number is not higher than [MAXROLLOVER] (and > 0)
Where this gets complicated is the [TOTALDAYSALLOWED] column, which is [NUMDAYSALLOWED] combined with the previous year's rollover to get the total number of days that can be used in a current year.
I've tried several different ways of getting this calculation, but all of them have failed to account for the previous year's rollover being a part of the current year's allowed days.
Creating columns for the LAG of days used, joining the data to itself but shifted back a year, etc. I'm not including examples of code I've tried because the approach was wrong in all of the attempts. That would just make this long post even longer.
Here's the data I'm working with:
Here's how it should look after the calculation:
This is a per-person calculation, so there's no need to consider any personal ID here. DAYTYPE only has one value currently, but I want to include it in the calculation in case another is added. The [HOW] column is only for clarity in this post.
Here's some code to generate the sample data (SQL Server or Azure SQL):
IF OBJECT_ID('tempdb..#COUNTS') IS NOT NULL DROP TABLE #COUNTS
CREATE TABLE #COUNTS (USED INT, DAYTYPE VARCHAR(20), THEYEAR INT)
INSERT INTO #COUNTS (USED, DAYTYPE, THEYEAR)
SELECT 1, 'X', 2019
UNION
SELECT 3, 'X', 2020
UNION
SELECT 0, 'X', 2021
IF OBJECT_ID('tempdb..#ALLOWANCES') IS NOT NULL DROP TABLE #ALLOWANCES
CREATE TABLE #ALLOWANCES (THEYEAR INT, DAYTYPE VARCHAR(20), NUMDAYSALLOWED INT, MAXROLLOVER INT)
INSERT INTO #ALLOWANCES (THEYEAR, DAYTYPE, NUMDAYSALLOWED, MAXROLLOVER)
SELECT 2019, 'X', 3, 3
UNION
SELECT 2020, 'X', 3, 3
UNION
SELECT 2021, 'X', 3, 3
SELECT C.*, A.NUMDAYSALLOWED, A.MAXROLLOVER
FROM #COUNTS C
JOIN #ALLOWANCES A ON C.DAYTYPE = A.DAYTYPE AND C.THEYEAR = A.THEYEAR
The tricky part is to limit the rollover amount. This is maybe possible with window functions, but I think this is easier to do with a recursive query:
with
data as (
select c.*, a.numdaysallowed, a.maxrollover,
row_number() over(partition by c.daytype order by c.theyear) rn
from #counts c
inner join #allowances a on a.theyear = c.theyear and a.daytype = c.daytype
),
cte as (
select d.*,
numdaysallowed as totaldaysallowed,
numdaysallowed - used as actualrollover
from data d
where rn = 1
union all
select d.*,
d.numdaysallowed + c.actualrollover,
case when d.numdaysallowed + c.actualrollover - d.used > d.maxrollover
then 3
else d.numdaysallowed + c.actualrollover - d.used
end
from cte c
inner join data d on d.rn = c.rn + 1 and d.daytype = c.daytype
)
select * from cte order by theyear
Demo on DB Fiddle

T-sql :get SUM of Columns

I have a table that looks something like the following :
W1 W2 w3
Gold 10 2 3
Silver 3 1 1
but i need a result :
W1 W2 w3
Gold 10 12 15
Silver 3 4 5
Is there any way i can get that result?
My sql query :
SELECT
week1=[1],week2=[2],week3=[3]
FROM
(
SELECT
[week]=DATEPART(ISO_WEEK,ta.enddate),ta.id
FROM
table1 ta where ta.enddate BETWEEN '2016/01/01' AND '2016/12/31'
) src
PIVOT
(
SUM(id) FOR week IN (
[1],[2],[3])
) piv
Does this do what you want?
select t.??, t.w1, (t.w1 + t.w2) as w2, (t.w1 + t.w2 + t.w3) as w3
from table1 t;
I don't know what the name of the first column is, so I just used ??.
One thought, since you tagged this question with Reporting Services. If, in the end, you are displaying the info using Reporting Services I would highly consider using the Matrix tool to do the pivoting and summation of the data because that is exactly what it does.
To further explain as it seems you are going to use SSRS. Your matrix would have a dataset that would be similar to this:
SELECT
[week]=DATEPART(ISO_WEEK,ta.enddate),
ta.id,
ta.MetalType as GoldorSilver
FROM table1 ta
where ta.enddate BETWEEN '2016/01/01' AND '2016/12/31'
The matrix would have a header and footer and column group would be [Week] with a Column Group total to do the sum across the week. The row group footer would do the sum across all weeks.
Calculate the running total before pivoting the data
SELECT element,
week1=[1],week2=[2],week3=[3]
FROM
(
SELECT [week] = DATEPART(ISO_WEEK,ta.enddate),
price = sum(ta.price)Over(Partition by element Order by enddate),
element
FROM table1 ta
where ta.enddate BETWEEN '2016/01/01' AND '2016/12/31'
) src
PIVOT
(
SUM(price) FOR week IN ( [1],[2],[3])
) piv
for older versions
SELECT element,
week1=[1],week2=[2],week3=[3]
FROM
(
SELECT [week] = DATEPART(ISO_WEEK,ta.enddate),
cs.price,
element
FROM table1 ta
cross apply(select sum(price) from table1 tb
where ta.element = tb.element and ta.enddate >= tb.enddate ) cs (price)
where ta.enddate BETWEEN '2016/01/01' AND '2016/12/31'
) src
PIVOT
(
SUM(price) FOR week IN ( [1],[2],[3])
) piv

Multi row to a row sql

I have a table as bellow:
I want query to print output as bellow:
Note: Please, do not downvote. I know the rules of posting answers, but for such of questions there's no chance to post short answer. I posted it only to provide help for those who want to find out how to achieve that, but does not expect ready-to-use solution.
I'd suggest to read these articles:
PIVOT on two or more fields in SQL Server
Pivoting on multiple columns - SQL Server
Pivot two or more columns in SQL Server 2005
At first UNPIVOT then PIVOT. If number of rows for each Pod_ID is not always equal 3 then you need to use dynamic SQL. The basic sample:
SELECT *
FROM (
SELECT Pod_ID,
Purs + CASE WHEN RN-1 = 0 THEN '' ELSE CAST(RN-1 as nvarchar(10)) END as Purs,
[Values]
FROM (
SELECT Pod_ID,
Pur_Qty, --All columns that will be UNPIVOTed must be same datatype
Pur_Price,
CAST(ETD_Date as int) ETD_Date, -- that is why I cast date to int
ROW_NUMBER() OVER (ORDER BY (SELECT 1)) as RN
FROM YourTable
) as p1
UNPIVOT (
[Values] FOR [Purs] IN(Pur_Qty, Pur_Price, ETD_Date)
) as unpvt
) as p2
PIVOT (
MAX([Values]) FOR Purs IN (Pur_Qty,Pur_Price,ETD_Date,Pur_Qty1,Pur_Price1,ETD_Date1,Pur_Qty2,Pur_Price2,ETD_Date2)
) as pvt
Will bring you:
Pod_ID Pur_Qty Pur_Price ETD_Date Pur_Qty1 Pur_Price1 ETD_Date1 Pur_Qty2 Pur_Price2 ETD_Date2
F8E2F614-75BC-4E46-B7F8-18C7FC4E5397 24 22 20160820 400 33 20160905 50 44 20160830

T-SQL Pivot/Unpivot(Transpose) Column Headers Needed as Data Rows

I'm working on a T-SQL issue where I needed to Transponse Rows into Columns and using UNPIVOT and PIVOT together per a post at Simple way to transpose columns and rows in Sql?
No problem. It does Exactly what I want with the row/column manipulation. However what I would REALLY like to do is to get the values used for the column headers to become yet another row in the results.
My abbreviated code is:
SELECT *
FROM (SELECT fiscalyear,
Sum(totalrecords) AS TotalRecords
FROM dbo.tbleirstatisticsoverviewsummary
WHERE fiscalquarter = 'ALL'
AND branchcode = 'ALL'
GROUP BY fiscalyear,
fiscalquarter,
branchcode) AS p
UNPIVOT (value
FOR colname IN ( totalrecords )) AS unpvt
PIVOT (Max(value) For FiscalYear IN ([2012],[2013],[ALL])) as p
What it renders is:
colname 2012 2013 ALL
TotalRecords 421 227 648
Where the first line is column headers.
Any thoughts on how I could get the column headers to be data rows?
Adding some sample Raw Data
fiscalyear TotalRecords
2012 421
2013 227
ALL 648
There are a few confusing things that you are doing.
First, typically you will unpivot multiple columns. Right now, you are are unpivoting one column and it seems like you are doing it just to rename the column?
Second, you are aggregating the data twice, the PIVOT should be able to handle the aggregation using SUM().
Third, it is not exactly clear on why you need the column headers as a row, what will you want the column headers to be called?
Based on your sample data you should be able to just apply the PIVOT function:
select 'TotalRecords' TotalRecords,
[2012],
[2013],
[All]
from tbleirstatisticsoverviewsummary
pivot
(
sum(totalrecords)
for FiscalYear IN ([2012],[2013],[ALL])
) p;
See SQL Fiddle with Demo. Then if you want a row with the columns headers, then you can use a UNION ALL:
select 'colname' col1,
2012 col2,
2013 col3,
'All' col4
union all
select 'TotalRecords' TotalRecords,
[2012],
[2013],
[All] = cast([all] as varchar(10))
from tbleirstatisticsoverviewsummary
pivot
(
sum(totalrecords)
for FiscalYear IN ([2012],[2013],[ALL])
) p;
See SQL Fiddle with Demo

Inserting and transforming data from SQL table

I have a question which has been bugging me for a couple of days now. I have a table with:
Date
ID
Status_ID
Start_Time
End_Time
Status_Time(seconds) (How ling they were in a certain status, in seconds)
I want to put this data in another table, that has the Status_ID grouped up as columns. This table has columns like this:
Date
ID
Lunch (in seconds)
Break(in seconds)
Vacation, (in seconds) etc.
So, Status_ID 2 and 3 might be grouped under vacation, Status_ID 1 lunch, etc.
I have thought of doing a Case nested in a while loop, to go through every row to insert into my other table. However, I cannot wrap my head around inserting this data from Status_ID in rows, to columns that they are now grouped by.
There's no need for a WHILE loop.
SELECT
date,
id,
SUM(CASE WHEN status_id = 1 THEN status_time ELSE 0 END) AS lunch,
SUM(CASE WHEN status_id = 2 THEN status_time ELSE 0 END) AS break,
SUM(CASE WHEN status_id = 3 THEN status_time ELSE 0 END) AS vacation
FROM
My_Table
GROUP BY
date,
id
Also, keeping the status_time in the table is a mistake (unless it's a non-persistent, calculated column). You are effectively storing the same data in two places in the database, which is going to end up resulting in inconsistencies. The same goes for pushing this data into another table with times broken out by status type. Don't create a new table to hold the data, use the query to get the data when you need it.
This type of query (that transpose values from rows into columns) is named pivot query (SQL Server) or crosstab (Access).
There is two types of pivot queries (generally speaking):
With a fixed number of columns.
With a dynamic number of columns.
SQL Server support both types but:
Database Engine (query language: T-SQL) support directly only pivot
queries with a fixed number of columns(1) and indirectly (2)
Analysis Services (query language: MDX) support directly both types (1 & 2).
Also, you can query(MDX) Analysis Service data sources from T-SQL using OPENQUERY/OPENROWSET functions or using a linked server with four-part names.
T-SQL (only) solutions:
For the first type (1), starting with SQL Server 2005 you can use the PIVOT operator:
SELECT pvt.*
FROM
(
SELECT Date, Id, Status_ID, Status_Time
FROM Table
) src
PIVOT ( SUM(src.Status_Time) FOR src.Status_ID IN ([1], [2], [3]) ) pvt
or
SELECT pvt.Date, pvt.Id, pvt.[1] AS Lunch, pvt.[2] AS [Break], pvt.[3] Vacation
FROM
(
SELECT Date, Id, Status_ID, Status_Time
FROM Table
) src
PIVOT ( SUM(src.Status_Time) FOR src.Status_ID IN ([1], [2], [3]) ) pvt
For a dynamic number of columns (2), T-SQL offers only an indirect solution: dynamic queries. First, you must find all distinct values from Status_ID and the next move is to build the final query:
DECLARE #SQLStatement NVARCHAR(4000)
,#PivotValues NVARCHAR(4000);
SET #PivotValues = '';
SELECT #PivotValues = #PivotValues + ',' + QUOTENAME(src.Status_ID)
FROM
(
SELECT DISTINCT Status_ID
FROM Table
) src;
SET #PivotValues = SUBSTRING(#PivotValues,2,4000);
SELECT #SQLStatement =
'SELECT pvt.*
FROM
(
SELECT Date, Id, Status_ID, Status_Time
FROM Table
) src
PIVOT ( SUM(src.Status_Time) FOR src.Status_ID IN ('+#PivotValues+') ) pvt';
EXECUTE sp_executesql #SQLStatement;