How to transform rows into column? [duplicate] - sql

This question already has answers here:
Convert Rows to columns using 'Pivot' in SQL Server
(9 answers)
Closed 7 years ago.
I have a table like this and there are only two feature for all user in this table
+-------+---------+-----------+----------+
| User | Feature | StartDate | EndDate |
+-------+---------+-----------+----------+
| Peter | F1 | 2015/1/1 | 2015/2/1 |
| Peter | F2 | 2015/3/1 | 2015/4/1 |
| John | F1 | 2015/5/1 | 2015/6/1 |
| John | F2 | 2015/7/1 | 2015/8/1 |
+-------+---------+-----------+----------+
I want to transform to
+-------+--------------+------------+--------------+------------+
| User | F1_StartDate | F1_EndDate | F2_StartDate | F2_EndDate |
+-------+--------------+------------+--------------+------------+
| Peter | 2015/1/1 | 2015/2/1 | 2015/3/1 | 2015/4/1 |
| John | 2015/5/1 | 2015/6/1 | 2015/7/1 | 2015/8/1 |
+-------+--------------+------------+--------------+------------+

If you are using SQL Server 2005 or up by any chance, PIVOT is what you are looking for.

The best general way to perform this sort of operation is a simple group by statement. This should work across all major ODBMS:
select user,
max(case when feature='F1' then StartDate else null end) F1_StartDate,
max(case when feature='F1' then EndDate else null end) F1_EndDate,
max(case when feature='F2' then StartDate else null end) F2_StartDate,
max(case when feature='F2' then EndDate else null end) F2_EndDate
from table
group by user
Note: as mentioned in the comments, this is often bad practice, as depending on your needs, it can make the data harder to work with. However, there are cases where it makes sense, when you have a small, limited number of values.

This is a bit of a hack with a CTE:
;WITH CTE AS (
SELECT [User], [Feature] + '_StartDate' AS [Type], StartDate AS [Date]
FROM Table1
UNION ALL
SELECT [User], [Feature] + '_EndDate' AS [Type], EndDate AS [Date]
FROM Table1)
SELECT * FROM CTE
PIVOT(MAX([Date]) FOR [Type] IN ([F1_StartDate],[F2_StartDate], [F1_EndDate], [F2_EndDate])) PIV

Use UNPIVOT & PIVOT like this:
Test data:
DECLARE #t table
(User1 varchar(20),Feature char(2),StartDate date,EndDate date)
INSERT #t values
('Pete','F1','2015/1/1 ','2015/2/1'),
('Pete','F2','2015/3/1 ','2015/4/1'),
('John','F1','2015/5/1 ','2015/6/1'),
('John','F2','2015/7/1 ','2015/8/1')
Query:
;WITH CTE AS
(
SELECT User1, date1, Feature + '_' + Seq cat
FROM #t as p
UNPIVOT
(date1 FOR Seq IN
([StartDate], [EndDate]) ) AS unpvt
)
SELECT * FROM CTE
PIVOT
(MIN(date1)
FOR cat
IN ([F1_StartDate],[F1_EndDate],[F2_StartDate],[F2_EndDate])
) as p
Result:
User1 F1_StartDate F1_EndDate F2_StartDate F2_EndDate
John 2015-05-01 2015-06-01 2015-07-01 2015-08-01
Pete 2015-01-01 2015-02-01 2015-03-01 2015-04-01

Related

SQL Ranking by blocks

Im sure the answer to this is going to end up being really obvious, but i just cant get this bit of sql to work.
I have a table that has 3 columns in:
User | Date | AchievedTarget
----------------------------------------
1 | 2018-01-01 | 1
1 | 2018-02-01 | 0
1 | 2018-03-01 | 1
1 | 2018-04-01 | 1
1 | 2018-05-01 | 0
I want to add a ranking as follows based on the AchievedTarget column, is this possible with the data in the table above to create the ranking in the table below:
User | Date | AchievedTarget | Rank
----------------------------------------
1 | 2018-01-01 | 1 | 1
1 | 2018-02-01 | 0 | 1
1 | 2018-03-01 | 1 | 1
1 | 2018-04-01 | 1 | 2
1 | 2018-05-01 | 0 | 1
This is a guess, based on that this is actually a gaps and island question. if so, this does result in the second dataset the OP has provided:
CREATE TABLE dbo.TestTable ([User] tinyint, --Avoid using keywords for column names
[date] date, --Avoid using datatypes for column names
AchievedTarget bit);
GO
INSERT INTO dbo.TestTable ([User],[date],AchievedTarget)
VALUES (1,'20180101',1),
(1,'20180201',0),
(1,'20180301',1),
(1,'20180401',1),
(1,'20180501',0);
GO
WITH Grps AS(
SELECT [User],[date],AchievedTarget,
ROW_NUMBER() OVER (ORDER BY [date]) -
ROW_NUMBER() OVER (PARTITION BY AchievedTarget ORDER BY [date]) AS Grp
FROM dbo.TestTable)
SELECT [User],[date],AchievedTarget,
ROW_NUMBER() OVER (PARTITION BY AchievedTarget, Grp ORDER BY [date]) AS [Rank] --Avoid using keywords for column names
FROM Grps
ORDER BY [date]
GO
DROP TABLE dbo.TestTable;
Other method:
with tmp as (
select row_number() over(order by date) ID, *
from dbo.TestTable
)
select f1.*, NbBefore + 1
from tmp f1
outer apply
(
select top 1 f2.ID IDLimit from tmp f2 where f2.ID<f1.ID and f2.AchievedTarget<>f1.AchievedTarget
order by f2.ID desc
) f3
outer apply
(
select count(*) NbBefore from tmp f4 where f4.ID<f1.ID and f4.ID> f3.IDLimit
) f5

SQL use of OVER and PARTITION BY

I have the following table;
ClientID | Location | Episode | Date
001 | Area1 | 4 | 01Dec16
001 | Area2 | 3 | 01Nov16
001 | Area2 | 2 | 01Oct16
001 | Area1 | 1 | 01Sep16
002 | Area2 | 3 | 21Dec16
002 | Area1 | 2 | 21Nov16
002 | Area1 | 1 | 21Oct16
And I'm looking to create 2 new columns based to the latest episode of the client
ClientID | Location | Episode | Date | LatestEpisode | LatestLocation
001 | Area1 | 4 | Dec | 4 | Area1
001 | Area2 | 3 | Nov | 4 | Area1
001 | Area2 | 2 | Oct | 4 | Area1
001 | Area1 | 1 | Sep | 4 | Area1
002 | Area2 | 3 | Dec | 3 | Area2
002 | Area1 | 2 | Nov | 3 | Area2
002 | Area1 | 1 | Oct | 3 | Area2
I have worked out I can use OVER to work out the LatestEspisode:
LatestEpisode = MAX(Episode) OVER(PARTITION BY ClientID)
But can't work out how to get the LatestLocation?
EDIT: Sorry if I haven't got the format right, this is my first post. I was trying to look at how to post correctly but I found it quite confusing
I have searched stackoverflow many times over the last 3 days and have found various ways using OVER and ROW NUMBER() but I don't have a lot of experience of them. Many of the examples I had found previously were fine for producing an aggregated table but I want to keep the full table, this is why I thought using OVER was the way to go.
Sql server 2012 version introduced the FIRST_VALUE() function,
That enables you to write your select query like this:
SELECT ClientID,
Location,
Episode,
[Date],
LatestEpisode = FIRST_VALUE(Episode) OVER(PARTITION BY ClientID ORDER BY [Date] DESC),
LatestLocation = FIRST_VALUE(Location) OVER(PARTITION BY ClientID ORDER BY [Date] DESC)
FROM tableName
In SQL Server, I would do this with cross apply:
select e.*, e2.episode as LatestEpisode, e2.location as LatestLocation
from episodes e cross apply
(select top 1 e2.*
from episodes e2
where e2.clientId = e.clientId
order by e2.episode desc
) elast;
Although you can express this logic with window functions, the lateral join (implemented in SQL Server using the apply keyword) is more natural way of expressing the logic.
If you are not familiar with lateral joins, you can think of them as a correlated subqueries in the from clause . . . but queries that allow you to return multiple columns. I should add, though, that one of the main use cases is for table-valued functions, so it is a very powerful construct.
First, you need to select LatestEpisode per each client and then you can use this value to identify row, where you can get LatestLocation from
SELECT *
,(
SELECT Location
FROM Episodes
WHERE ClientId = MyTable.ClientId
AND Episode = MyTable.LatestEpisode
) AS LatestLocation
FROM (
SELECT *
,MAX(Episode) OVER (PARTITION BY ClientId) AS LatestEpisode
FROM Episodes
) AS MyTable
You can also use common table expression (CTE):
WITH cte
AS (
SELECT *
,MAX(Episode) OVER (PARTITION BY ClientId) AS LatestEpisode
FROM Episodes
)
SELECT cte.*
,(
SELECT Location
FROM Episodes
WHERE ClientId = cte.ClientId
AND Episode = cte.LatestEpisode
) AS LatestLocation
FROM cte
I have worked on it and able to produce the required result
Please try below
Declare #Table table ( ClientID varchar(max), Location varchar(500), Episode int, Dated varchar(30))
Insert Into #Table
Values ('001', 'Area1', 4 ,'01Dec16' )
,('001', 'Area2', 3, '01Nov16')
, ('001', 'Area2', 2, '01Oct16')
,('001' ,'Area1' ,1, '01Sep16')
,('002' ,'Area2' ,3, '21Dec16')
,('002' ,'Area1' ,2, '21Nov16')
,('002' ,'Area1' ,1, '21Oct16')
; WITH LL AS
(
SELECT CLientID ,MAX(CAST (Dated as Date)) as maxdate
FROM #table
GROUP BY ClientID
)
, Area AS
(
SELECT Location, x.ClientID, x.Dated FROM #Table x INNER JOIN LL b ON x.ClientID = b.ClientID AND x.Dated = b.maxdate
)
SELECT a.*
, LatestEpisode = MAX(Episode) OVER(PARTITION BY a.ClientID)
, LatestLocation = b.Location
FROM #Table a
INNER JOIN Area b ON a.ClientID = b.ClientID

SQL Server - Insert lines with null values when month doesn't exist

I have a table like this one:
Yr | Mnth | W_ID | X_ID | Y_ID | Z_ID | Purchases | Sales | Returns |
2015 | 10 | 1 | 5210 | 1402 | 2 | 1000.00 | etc | etc |
2015 | 12 | 1 | 5210 | 1402 | 2 | 12000.00 | etc | etc |
2016 | 1 | 1 | 5210 | 1402 | 2 | 1000.00 | etc | etc |
2016 | 3 | 1 | 5210 | 1402 | 2 | etc | etc | etc |
2014 | 3 | 9 | 880 | 2 | 7 | etc | etc | etc |
2014 | 12 | 9 | 880 | 2 | 7 | etc | etc | etc |
2015 | 5 | 9 | 880 | 2 | 7 | etc | etc | etc |
2015 | 7 | 9 | 880 | 2 | 7 | etc | etc | etc |
For each combination of (W, X, Y, Z) I would like to insert the months that don't appear in the table and are between the first and last month.
In this example, for combination (W=1, X=5210, Y=1402, Z=2), I would like to have additional rows for 2015/11 and 2016/02, where Purchases, Sales and Returns are NULL. For combination (W=9, X=880, Y=2, Z=7) I would like to have additional rows for months between 2014/4 and 2014/11, 2015/01 and 2015/04, 2016/06.
I hope I have explained myself correctly.
Thank you in advance for any help you can provide.
The process is rather cumbersome in this case, but quite possible. One method uses a recursive CTE. Another uses a numbers table. I'm going to use the latter.
The idea is:
Find the minimum and maximum values for the year/month combination for each set of ids. For this, the values will be turned into months since time 0 using the formula year*12 + month.
Generate a bunch of numbers.
Generate all rows between the two values for each combination of ids.
For each generated row, use arithmetic to re-extract the year and month.
Use left join to bring in the original data.
The query looks like:
with n as (
select row_number() over (order by (select null)) - 1 as n -- start at 0
from master.spt_values
),
minmax as (
select w_id, x_id, y_id, z_id, min(yr*12 + mnth) as minyyyymm,
max(yr*12 + mnth) as maxyyyymm
from t
group by w_id, x_id, y_id, z_id
),
wxyz as (
select minmax.*, minmax.minyyyymm + n.n,
(minmax.minyyyymm + n.n) / 12 as yyyy,
((minmax.minyyyymm + n.n) % 12) + 1 as mm
from minmax join
n
on minmax.minyyyymm + n.n <= minmax.maxyyyymm
)
select wxyz.yyyy, wxyz.mm, wxyz.w_id, wxyz.x_id, wxyz.y_id, wxyz.z_id,
<columns from t here>
from wxyz left join
t
on wxyz.w_id = t.w_id and wxyz.x_id = t.x_id and wxyz.y_id = t.y_id and
wxyz.z_id = t.z_id and wxyz.yyyy = t.yr and wxyz.mm = t.mnth;
Thank you for your help.
Your solution works, but I noticed it is not very good in terms of performance, but meanwhile I have managed to get a solution for my problem.
DECLARE #start_date DATE, #end_date DATE;
SET #start_date = (SELECT MIN(EOMONTH(DATEFROMPARTS(Yr , Mnth, 1))) FROM Table_Input);
SET #end_date = (SELECT MAX(EOMONTH(DATEFROMPARTS(Yr , Mnth, 1))) FROM Table_Input);
DECLARE #tdates TABLE (Period DATE, Yr INT, Mnth INT);
WHILE #start_date <= #end_date
BEGIN
INSERT INTO #tdates(PEriod, Yr, Mnth) VALUES(#start_date, YEAR(#start_date), MONTH(#start_date));
SET #start_date = EOMONTH(DATEADD(mm,1,DATEFROMPARTS(YEAR(#start_date), MONTH(#start_date), 1)));
END
DECLARE #pks TABLE (W_ID NVARCHAR(50), X_ID NVARCHAR(50)
, Y_ID NVARCHAR(50), Z_ID NVARCHAR(50)
, PerMin DATE, PerMax DATE);
INSERT INTO #pks (W_ID, X_ID, Y_ID, Z_ID, PerMin, PerMax)
SELECT W_ID, X_ID, Y_ID, Z_ID
, MIN(EOMONTH(DATEFROMPARTS(Ano, Mes, 1))) AS PerMin
, MAX(EOMONTH(DATEFROMPARTS(Ano, Mes, 1))) AS PerMax
FROM Table1
GROUP BY W_ID, X_ID, Y_ID, Z_ID;
INSERT INTO Table_Output(W_ID, X_ID, Y_ID, Z_ID
, ComprasLiquidas, RTV, DevManuais, ComprasBrutas, Vendas, Stock, ReceitasComerciais)
SELECT TP.DB, TP.Ano, TP.Mes, TP.Supplier_Code, TP.Depart_Code, TP.BizUnit_Code
, TA.ComprasLiquidas, TA.RTV, TA.DevManuais, TA.ComprasBrutas, TA.Vendas, TA.Stock, TA.ReceitasComerciais
FROM
(
SELECT W_ID, X_ID, Y_ID, Z_ID
FROM #tdatas CROSS JOIN #pks
WHERE Period BETWEEN PerMin And PerMax
) AS TP
LEFT JOIN Table_Input AS TA
ON TP.W_ID = TA.W_ID AND TP.X_ID = TA.X_ID AND TP.Y_ID = TA.Y_ID
AND TP.Z_ID = TA.Z_ID
AND TP.Yr = TA.Yr
AND TP.Mnth = TA.Mnth
ORDER BY TP.W_ID, TP.X_ID, TP.Y_ID, TP.Z_ID, TP.Yr, TP.Mnth;
I do the following:
Get the Min and Max date of the entire table - #start_date and #end_date variables;
Create an auxiliary table with all dates between Min and Max - #tdates table;
Get all the combinations of (W_ID, X_ID, Y_ID, Z_ID) along with the min and max dates of that combination - #pks table;
Create the cartesian product between #tdates and #pks, and in the WHERE clause I filter the results between the Min and Max of the combination;
Compute a LEFT JOIN of the cartesian product table with the input data table.

SQL Pivot for multiple columns [duplicate]

This question already has answers here:
SQL Server Pivot Table with multiple column aggregates
(3 answers)
Closed 7 years ago.
I have the below table 'EmpTemp':
Name | Category | Value1 | Value 2
John | Cat1 | 11 | 33
John | Cat2 | 22 | 44
I would like to have the below output for the table:
Name | Cat1_Value1 | Cat2_Value1 | Cat1_Value2 | Cat2_Value2
John | 11 | 11 | 33 | 44
I guess this give a basic idea of what kind of transformation i'm expecting. I have tried the below query that gives me a partial solution:
SELECT
Name,
Cat1 AS 'Cat1_Value1',
Cat2 AS 'Cat2_Value1',
FROM EmpTemp AS T
PIVOT
(
MAX(Value1)
FOR Category IN (Cat1, Cat2)
) AS PVT
The above query gives me the following result:
Name | Cat1_Value1 | Cat2_Value1
John | 11 | 11
I am stuck how can I extend this pivot query. Any help is appreciated in advance.
Here is a way to do it using Pivot.
First you need to unpivot the data then do the pivot
;with cte as
(
select name,val,col_name from yourtable
cross apply (values
(value1,Category+'value1'),
(value2,Category+'value2')
) cs(val,col_name)
)
select * from cte
pivot(max(val) for col_name in([Cat1value1],
[Cat1value2],
[Cat2value1],
[Cat2value2]
)) p
Here is a simple way of doing it without using the pivot syntax:
select name,
max(case when category = 'Cat1' then value1 end) as cat1_value1,
max(case when category = 'Cat2' then value1 end) as cat2_value1,
max(case when category = 'Cat1' then value2 end) as cat1_value2,
max(case when category = 'Cat2' then value2 end) as cat2_value2
from EmpTemp
group by name

SQL Order By and "Not-So-Much Group"

Lets say I have a table:
--------------------------------------
| ID | DATE | GROUP | RESULT |
--------------------------------------
| 1 | 01/06 | Group1 | 12345 |
| 2 | 01/05 | Group2 | 54321 |
| 3 | 01/04 | Group1 | 11111 |
--------------------------------------
I want to order the result by the most recent date at the top but group the "group" column together, but still have distinct entries. The result that I want would be:
1 | 01/06 | Group1 | 12345
3 | 01/04 | Group1 | 11111
2 | 01/05 | Group2 | 54321
What would be a query to get that result?
thank you!
EDIT:
I'm using MSSQL. I'll look into translating the oracle query into MS SQL and report my results.
EDIT
SQL Server 2000, so OVER/PARTITION is not supported =[
Thank you!
You should specify what RDBMS you are using. This answer is for Oracle, may not work in other systems.
SELECT * FROM table
ORDER BY MAX(date) OVER (PARTITION BY group) DESC, group, date DESC
declare #table table (
ID int not null,
[DATE] smalldatetime not null,
[GROUP] varchar(10) not null,
[RESULT] varchar(10) not null
)
insert #table values (1, '2009-01-06', 'Group1', '12345')
insert #table values (2, '2009-01-05', 'Group2', '12345')
insert #table values (3, '2009-01-04', 'Group1', '12345')
select t.*
from #table t
inner join (
select
max([date]) as [order-date],
[GROUP]
from #table orderer
group by
[GROUP]
) x
on t.[GROUP] = x.[GROUP]
order by
x.[order-date] desc,
t.[GROUP],
t.[DATE] desc
use an order by clause with two params:
...order by group, date desc
this assumes that your date column does hold dates and not varchars
SELECT table2.myID,
table2.mydate,
table2.mygroup,
table2.myresult
FROM (SELECT DISTINCT mygroup FROM testtable as table1) as grouptable
JOIN testtable as table2
ON grouptable.mygroup = table2.mygroup
ORDER BY grouptable.mygroup,table2.mydate
SORRY, could NOT bring myself to use columns that were reserved names, rename the columns to make it work :)
this is MUCH simpler than the accepted answer btw.