Pivot Columns to Rows in SQL Server - sql-server

I have a query that returns an entire row and I need to pivot this result into a new table.
SELECT id_no, stud_name, group_no, class_1, class_2, class_3, class_4 FROM tbl_stud_class
This returns the following:
| id_no | stud_name | group_no | class_1 | class_2 | class_3 | class 4 |
| 1 | John Doe | A11 | 84 | 60 | 80 | 79 |
I need to be able to return this row as:
| id_no | stud_name | group_no | class | grade |
| 1 | John Doe | A11 | class_1 | 84 |
| 1 | John Doe | A11 | class_2 | 60 |
| 1 | John Doe | A11 | class_3 | 80 |
| 1 | John Doe | A11 | class_4 | 79 |
Can someone point me to a way to do this please?
I am converting my PostgreSQL function (where i'm using CROSS JOIN LATERAL to SQL Server)
Thank you!

Just another option is using a CROSS APPLY
Example
Select A.id_no
,A.stud_name
,A.group_no
,B.*
From YourTable A
Cross Apply ( values ('class_1',class_1)
,('class_2',class_2)
,('class_3',class_3)
,('class_4',class_4)
) B(class,grade)
Returns
id_no stud_name group_no class grade
1 John Doe A11 class_1 84
1 John Doe A11 class_2 60
1 John Doe A11 class_3 80
1 John Doe A11 class_4 79

You can use UNPIVOT to get this done in SQL Server. Below is an example using your sample data.
CREATE TABLE #tbl_stud_class
(
id_no int,
stud_name varchar(50),
group_no varchar(50),
class_1 int,
class_2 int,
class_3 int,
class_4 int
)
INSERT INTO #tbl_stud_class VALUES (1, 'John Doe', 'A11', 84, 60, 80, 79)
SELECT *
FROM #tbl_stud_class
UNPIVOT
(
Class FOR Classes IN (class_1, class_2, class_3, class_4)
) AS UP
DROP TABLE #tbl_stud_class

Based on your example, I'd do something like this:
Select
id_no
, stud_name
, group_no
, 'class_1'
, class_1 as grade
From tbl_stud_class
Union All
Select
id_no
, stud_name
, group_no
, 'class_2'
, class_2 as grade
From tbl_stud_class
Union All
etc.
I would also use the full word "student" in my table and field names, but that's not really a database issue... ;)

Related

I'm getting a rollup of the same value twice

I have table sellers where I list every single one of my sellers, and I added the column objective recently.
id |name | team_leader_id | team_leader | objective
--------------------------------------------------
1 |John | 50 | Mark | 30
2 |Jane | 66 | Ryu | 30
3 |Angela | 66 | Ryu | 45
4 |Arthur | 190 | Carol | 35
5 |Anthony| 20 | Adam | 50
I have another table sales where I link my sellers table on seller_id.
sale_id |seller_id |seller_name |item
-------------------------------------
56879 |2 |Jane |4P
23512 |2 |Jane |3P
54827 |2 |Jane |3P
12345 |5 |Anthony |4P
55435 |4 |Arthur |GSM
The query I'm trying is:
SELECT coalesce(seller.team_leader,'') team_leader,
coalesce(sales.seller_name,'TOTAL') seller_name,
seller.objective,
count(*) as quantity
FROM sales
JOIN seller ON seller.id = sales.seller_id
WHERE seller.team_leader_id = 66
GROUP BY seller.team_leader, ROLLUP(sales.seller_name), seller.objective
I noticed that the result I'm getting a duplicate of every line that now has an objective.
I think the problem is because my objective column is new, and I'm joining my sales table with my seller table, it counts the records I had before creating the objective column separately.
So, my expected result would be
team_leader | seller_name | objective | quantity
------------------------------------------------
Ryu | TOTAL | | 3
| Jane | 30 | 3
| Angela | 45 | 0
But this is what I'm getting
team_leader | seller_name | objective | quantity
------------------------------------------------
Ryu | TOTAL | | 1
Ryu | TOTAL | 30 | 2
| Jane | | 1
| Jane | 30 | 2
| Angela | 45 | 0
When the objective appears blank with Jane, it is a sale that she did before I added the objective column.
You can try the following :
SELECT case when seller.name is null then max(seller.team_leader) else '' end as team_leader,
isnull(seller.name,'TOTAL') seller_name,
case when seller.name is null then '' else sum(seller.objective) end objective,
count(sales.seller_id) as quantity
FROM seller
LEFT JOIN sales ON seller.id = sales.seller_id
WHERE seller.team_leader_id = 66
group by ROLLUP(seller.name)
order by team_leader desc, quantity desc
OR if you are okay with not using ROLLUP, you can get the exact same result using the following query.
;with cte as (
SELECT max(team_leader) team_leader,
max(name) seller_name,
max(objective) objective,
count(seller_id) as quantity
FROM seller
LEFT JOIN sales ON seller.id = sales.seller_id
WHERE seller.team_leader_id = 66
GROUP BY seller.id
)
SELECT team_leader, 'TOTAL' seller_name, '' objective, sum(quantity) quantity
FROM cte
GROUP BY team_leader
UNION ALL
SELECT '', seller_name, objective, quantity
FROM cte

How to use last_value with group by with count in SQL Server?

I have table like:
name | timeStamp | previousValue | newValue
--------+---------------+-------------------+------------
Mark | 13.12.2020 | 123 | 155
Mark | 12.12.2020 | 123 | 12
Tom | 14.12.2020 | 123 | 534
Mark | 12.12.2020 | 123 | 31
Tom | 11.12.2020 | 123 | 84
Mark | 19.12.2020 | 123 | 33
Mark | 17.12.2020 | 123 | 96
John | 22.12.2020 | 123 | 69
John | 19.12.2020 | 123 | 33
I'd like to mix last_value, count (*) and group to get this result:
name | count | lastValue
--------+-----------+-------------
Mark | 5 | 33
Tom | 2 | 534
John | 2 | 69
This part:
select name, count(*)
from table
group by name
returns table:
name | count
--------+---------
Mark | 5
Tom | 2
John | 2
but I have to add the last value for each name.
How to do it?
Best regards!
LAST_VALUE is a windowed function, so you'll need to get that value first, and then aggregate:
WITH CTE AS(
SELECT [name],
[timeStamp], --This is a poor choice for a column's name. timestamp is a (deprecated) synonym of rowversion, and a rowversion is not a date and time value
previousValue,
newValue,
LAST_VALUE(newValue) OVER (PARTITION BY [name] ORDER BY [timeStamp] ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS lastValue
FROM dbo.YourTable)
SELECT [Name],
COUNT(*) AS [count],
lastValue
FROM CTE
GROUP BY [Name],
lastValue;
I got a solution that works, but here's another one:
SELECT
[name], COUNT([name]), [lastValue]
FROM (
SELECT
[name], FIRST_VALUE([newValue]) OVER (PARTITION BY [name] ORDER BY TimeStamp DESC ROWS UNBOUNDED PRECEDING) AS [lastValue]
FROM [table]
) xyz GROUP BY [name], [lastValue]
Keep well!

SSIS Lookup Multiple Columns in one table to the same ID column in another

I have the following table:
EventValue | Person1 | Person2 | Person3 | Person4 | Meta1 | Meta2
-------------------------------------------------------------------------------------------
123 | joePerson01 | samRock01 | nancyDrew01 | steveRogers01 | 505 | 606
321 | steveRogers02 | yoMama01 | ruMo01 | lukeJedi01 | 707 | 808
I want to transform the Person columns into IDs for my destination table, so all of the ID's would be coming from the same Person table in my Destination DB:
ID | FirstName | LastName | DatabaseOneID | DatabaseTwoID
----------------------------------------------------------
1 | Joe | Person | joePerson01 | personJoe01
2 | Sam | Rockwell | samRock01 | rockSam01
3 | Nancy | Drew | nancyDrew01 | drewNancy01
4 | Steve | Rogers | steveRogers01 | rogersSteve01
5 | Steve R | Rogers | steveRogers02 | rogersSteve02
6 | Yo | Mama | yoMama01 | mamaYo01
7 | Rufus | Murdock | ruMo01 | moRu01
8 | Luke | Skywalker | lukeJedi01 | jediLuke01
With results like so:
MetaID | EventValue | Person1ID | Person2ID | Person3ID | Person4ID
------------------------------------------------------------------------
1 | 123 | 1 | 2 | 3 | 4
2 | 321 | 5 | 6 | 7 | 8
I currently have a Lookup Transform looking up the first Person column, but couldn't figure out how to convert all 4 Person columns into IDs within the same lookup.
You could do it in one query, or use UNPIVOT, or use a scalar function if you think it'll be more fixable for your implementation. Then, you just create a view of it, in which it'll be an easy access for you.
here is a quick example :
DECLARE
#tb1 TABLE
(
EventValue INT
, Person1 VARCHAR(250)
, Person2 VARCHAR(250)
, Person3 VARCHAR(250)
, Person4 VARCHAR(250)
, Meta1 INT
, Meta2 INT
)
DECLARE
#Person TABLE
(
ID INT
, FirstName VARCHAR(250)
, LastName VARCHAR(250)
, DatabaseOneID VARCHAR(250)
, DatabaseTwoID VARCHAR(250)
)
INSERT INTO #tb1
VALUES
(123,'joePerson01','samRock01','nancyDrew01','steveRogers01',505,606),
(321,'steveRogers02','yoMama01','ruMo01','lukeJedi01',707,808)
INSERT INTO #Person
VALUES
(1,'Joe','Person','joePerson01','personJoe01'),
(2,'Sam','Rockwell','samRock01','rockSam01'),
(3,'Nancy','Drew','nancyDrew01','drewNancy01'),
(4,'Steve','Rogers','steveRogers01','rogersSteve01'),
(5,'SteveR','Rogers','steveRogers02','rogersSteve02'),
(6,'Yo','Mama','yoMama01','mamaYo01'),
(7,'Rufus','Murdock','ruMo01','moRu01'),
(8,'Luke','Skywalker','lukeJedi01','jediLuke01')
SELECT ROW_NUMBER() OVER(ORDER BY EventValue) AS MetaID, *
FROM (
SELECT
t.EventValue
, MAX(CASE WHEN t.Person1 IN(p.DatabaseOneID, p.DatabaseTwoID) THEN p.ID ELSE NULL END) AS Person1ID
, MAX(CASE WHEN t.Person2 IN(p.DatabaseOneID, p.DatabaseTwoID) THEN p.ID ELSE NULL END) AS Person2ID
, MAX(CASE WHEN t.Person3 IN(p.DatabaseOneID, p.DatabaseTwoID) THEN p.ID ELSE NULL END) AS Person3ID
, MAX(CASE WHEN t.Person4 IN(p.DatabaseOneID, p.DatabaseTwoID) THEN p.ID ELSE NULL END) AS Person4ID
FROM #tb1 t
LEFT JOIN #Person p
ON p.DatabaseOneID IN(t.Person1, t.Person2, t.Person3, t.Person4)
OR p.DatabaseTwoID IN(t.Person1, t.Person2, t.Person3, t.Person4)
GROUP BY t.EventValue
) D
I currently have a Lookup Transform looking up the first Person column, but couldn't figure out how to convert all 4 Person columns into IDs within the same lookup.
You cannot do this within the same lookup, you have to add a Lookup Transformation for each Column. In your case you should add 4 Lookup Transformation.
If source database and destination database are on the same server, then you can use a SQL query to achieve that as mentioned in the other answer, but in case that each database is on a separate server you have to go with Lookup transformation or you have to import data into a staging table and perform Join operations using SQL.

Select row satisfying certain condition and rows next to it

Let's say I have a historical table keeping who has modified data
-------------------------------------------------------------
| ID | Last_Modif | User_Modif | Col3, Col4...
-------------------------------------------------------------
| 1 | 2018-04-09 12:12:00 | John
| 2 | 2018-04-09 11:10:00 | Jim
| 3 | 2018-04-09 11:05:00 | Mary
| 4 | 2018-04-09 11:00:00 | John
| 5 | 2018-04-09 10:56:00 | David
| 6 | 2018-04-09 10:53:00 | John
| 7 | 2018-04-08 19:50:00 | Eric
| 8 | 2018-04-08 18:50:00 | Chris
| 9 | 2018-04-08 15:50:00 | John
| 10 | 2018-04-08 12:50:00 | Chris
----------------------------------------------------------
I would like to find the modifs done by John and previous version before he did that, to check what he had modified. For example in this scenario I would like to return row 1,2,4,5,6,7,9,10
I am thinking of ranking first based on Last_modif then do a join to pick up the next row, but somehow the result is not correct. This seems not a LAG/LEAD case since I am not picking a single value from the next row, but instead the whole next row. Any idea ?
-- sample 1000 rows with RowNumber
with TopRows as
(select top 1000 *, ROW_NUMBER() OVER(ORDER BY Last_modif desc) RowNum from [Table])
--Reference rows : Rows modif by John
, ModifByJohn as
(Select * from TopRows where USER_MODIF = 'John')
select * from ModifByJohn
UNION
select ModifByNext.* from ModifByJohn join TopRows ModifbyNext on ModifByJohn.RowNum + 1 = ModifByNext.RowNum
order by RowNum
How will the code look like if we would like to return last 2 modifs before John did instead of 1 ?
Maybe you can take advantage of your current ID:
with x as
(
select t1.*,
(select top 1 id from tbl where id > t1.id) prev_id
from tbl t1
where t1.User_Modif = 'John'
)
select * from x;
GO
ID | Last_Modif | User_Modif | prev_id
-: | :------------------ | :--------- | ------:
1 | 09/04/2018 12:12:00 | John | 2
4 | 09/04/2018 11:00:00 | John | 5
6 | 09/04/2018 10:53:00 | John | 7
9 | 08/04/2018 15:50:00 | John | 10
with x as
(
select t1.*,
(select top 1 id from tbl where id > t1.id) prev_id
from tbl t1
where t1.User_Modif = 'John'
)
select ID, Last_Modif, User_Modif from x
union all
select ID, Last_Modif, User_Modif
from tbl
where ID in (select prev_id from x)
order by ID
GO
ID | Last_Modif | User_Modif
-: | :------------------ | :---------
1 | 09/04/2018 12:12:00 | John
2 | 09/04/2018 11:10:00 | Jim
4 | 09/04/2018 11:00:00 | John
5 | 09/04/2018 10:56:00 | David
6 | 09/04/2018 10:53:00 | John
7 | 08/04/2018 19:50:00 | Eric
9 | 08/04/2018 15:50:00 | John
10 | 08/04/2018 12:50:00 | Chris
dbfiddle here

Insert two column values into single SQL Server

The following two tables Table 1 and Table 2 are given-
Table 1
+-----+------+---------+
| ID | Name | Earning |
+-----+------+---------+
| 101 | John | HRA |
| 101 | John | Travel |
| 102 | Andy | Travel |
+-----+------+---------+
Table 2
+-----+------+---------+
| ID | Name |Deduction|
+-----+------+---------+
| 101 | John | ENP |
| 102 | Andy | ENP |
| 102 | Andy | RA |
+-----+------+---------+
and I need to create a third table Table 3 with following columns
I have already created two columns ID and Name .I only need EarningOrDeduction column.
With
INSERT INTO Table3 (ID, Name, EarningOrDeduction)
SELECT ID, Name, Earning FROM Table1
UNION ALL
SELECT ID, Name, Deduction FROM Table2;
I'm getting
Table 3
+-----+------+------------------+
| ID | Name |EarningOrDeduction|
+-----+------+------------------+
| 101 | John | HRA |
| 101 | John | Travel |
| 102 | Andy | Travel |
| 101 | John | ENP |
| 102 | Andy | ENP |
| 102 | Andy | RA |
+-----+------+------------------+
But I want output as
Table 3
+-----+------+------------------+
| ID | Name |EarningOrDeduction|
+-----+------+------------------+
| 101 | John | HRA |
| 101 | John | Travel |
| 101 | John | ENP |
| 102 | Andy | Travel |
| 102 | Andy | ENP |
| 102 | Andy | RA |
+-----+------+------------------+
You can select both table data with Union clause.
And if you don't want to insert already entered values use following query.
INSERT INTO Table3 (EarningOrDeduction)
SELECT X FROM(
SELECT Earning X FROM Table1
UNION
SELECT Deduction X FROM Table2
) T
LEFT JOIN Table3 T3 ON T.X=T3.EarningOrDeduction
WHERE T3.EarningOrDeduction IS NULL
You could try inserting a union of values from the two tables:
INSERT INTO Table3 (ID, Name, EarningOrDeduction)
SELECT ID, Name, Earning FROM Table1
UNION ALL
SELECT ID, Name, Deduction FROM Table2;
Or, if you don't really want to populate Table3 with these values, you could just run the above select without the first insert line.
UNION ALL with Order by should work.
INSERT INTO Table3 (ID, Name, EarningOrDeduction)
SELECT ID, Name, EarningOrDeduction from
(SELECT ID, Name, Earning as [EarningOrDeduction] FROM Table1
UNION ALL
SELECT ID, Name, Deduction FROM Table2) ORDER BY ID, Name;
I assume Earnings and Deduction will not produce duplicate value for particular Name.

Resources