Table transformation logic in SQL - sql

I have a table T :
CREATE TABLE T
(
id INT,
type VARCHAR(200),
type_value VARCHAR(10),
value VARCHAR(200)
);
INSERT INTO T VALUES (1, 'RoomColor', 'room1', 'yellow');
INSERT INTO T VALUES (1, 'RoomColor', 'room2', 'red');
INSERT INTO T VALUES (2, 'RoomColor', 'room1', 'blue');
INSERT INTO T VALUES (2, 'RoomColor', 'room1', 'pink');
INSERT INTO T VALUES (3, 'RoomColor', 'room1', 'white');
INSERT INTO T VALUES (3, 'RoomColor', 'room2', 'grey');
INSERT INTO T VALUES (3, 'RoomColor', 'room2', 'brown');
INSERT INTO T VALUES (4, 'RoomColor', 'room3', 'green');
I need to transform it into :
id BedRoomColor DiningRoomColor
-------------------------------------------
1 yellow red
2 blue pink
3 white grey
4 green null
Logic behind the transformation:
If there are more than two room type_value then discard the third room type_value
For same id if there are more than one room type_value ( for example room1,room1 or room2,room2 or room1,room2) then use first type_value to create as BedRoomColor and second type_value to create DiningRoomColor
If there is only 1 room type_value (for eg. room1 or room2 or room3) for an id then corresponding value ( red,green,yellow etc ) will be placed in BedRoomColor and DiningRoomColor will be null
I am struggling with this logic for couple of days. Can anyone please help me.
Thanks

You can use this script
;WITH CTE AS (
SELECT *, RN = ROW_NUMBER() OVER(PARTITION BY id ORDER BY type_value) FROM T
)
SELECT id, [1] BedRoomColor, [2] DiningRoomColor FROM
(SELECT id,value, RN FROM CTE ) SRC
PIVOT (MAX(value) FOR RN IN ([1], [2]) ) AS PVT
Result:
id BedRoomColor DiningRoomColor
----------- ------------------ ---------------
1 yellow red
2 blue pink
3 white grey
4 green NULL

Another way and with adding type to query is:
;with tt as (
select *,
row_number() over (partition by [type], id order by type_value) rn
-- ^^^^^^ I add type to support other types if there is
from t
)
select id,
max(case when [type] = 'RoomColor' and rn = 1 then [value] end) 'BedRoomColor',
max(case when [type] = 'RoomColor' and rn = 2 then [value] end) 'DiningRoomColor'
from tt
group by id;
SQL Server Fiddle Demo

try this:
with tmp as (
select T.*, rownumber() over(patition by id order by type_value) rang
from T
)
select f1.id, f1.value as BedRoomColor, f2.value as DiningRoomColor
from tmp f1
left outer join tmp f2 on f1.id=f2.id and f2.rang=2
where f1.rang=1

Related

Order by row groups with the highest value, then by the highest value per group

Suppose I have the following table:
Key
Value
5
1.0
2
0.860
7
0.686
5
0.886
7
1.0
7
0.478
2
1.0
5
0.921
2
1.0
And want to order by Key-groups with the highest values and then by the value in descending order, as:
Key
Value
2
1.0
2
1.0
2
0.860
5
1.0
5
0.921
5
0.886
7
1.0
7
0.686
7
0.478
EDIT 1: when there is multiple groups with the same highest value, then the second highest would determine the order of the groups.
EDIT 2: updated the values in order to better represent the data better.
How can I accomplish this in SQL Server?
I might be late to a party, and solution is probably overcomplicated, but it should be suitable for all cases.
The idea is to pivot Values for Keys in columns 1,[2],... from biggest to lowest, and then just order by these columns descending.
I changed data sample a bit to make a propper tests:
create table t (
[key] int,
[value] money
)
insert into t
values
(5, 1),
(5, 0.9212),
(5, 0.8867),
(5, 0.8394),
(5, 0.8279),
(5, 0.82),
(5, 0.8047),
(5, 0.8018),
(5, 0.7997),
(5, 0.7893),
(2, 1),
(2, 1),
(2, 0.8595),
(2, 0.7872),
(2, 0.7479),
(2, 0.7455),
(2, 0.7276),
(2, 0.7202),
(2, 0.6944),
(2, 0.6925);
And a script:
declare #temp as table([key] nvarchar(64), rn int);
declare #depth as int = 99, -- how many values you would like to take into account when you sort your Keys values
#sql as varchar(max);
with cte_ordered as
(-- here we find which values from keys should be compared. rn=1 - for biggest values
select [key], [value],
row_number() over (partition by [key] order by [value] desc) as rn
from t
),
cte_columns as
(-- distinct N values to use it in select list
select STRING_AGG('['+cast(rn as varchar(max))+']', ',') as cols
from ( select distinct top (#depth) rn
from cte_ordered order by rn) as qq
),
cte_order as
(-- distinct N values to use it in Order by
select STRING_AGG('['+cast(rn as varchar(max))+'] desc', ',') as ord
from ( select distinct top (#depth) rn
from cte_ordered order by rn) as qq
),
cte_dynamic as
(
select '
select [key],
row_number() over(order by ' + ord + ', [key])
from (
select [key], [value], rn
from ( select [key], [value],
row_number() over (partition by [key] order by [value] desc) as rn
from t
) as tt ) as ttt
pivot (
sum([value])
for rn in (' + cols + ')) as pv' as query
from cte_columns
cross join cte_order
)
select #sql = query
from cte_dynamic;
insert into #temp([key], rn)
exec(#sql);
select t.[key], t.[value]
from t
inner join #temp as tt
on t.[key] = tt.[key]
order by tt.rn, t.[value] DESC
;
DBfiddle example
It's not clear from the comments if you have the solution you need, but the following ordering criteria should give your resired result:
select *
from t
order by Max([value]) over(partition by [key]) desc, [key], [value] desc;
Demo fiddle

Limit Number of elements in a string aggregation query

I have below table and string aggregation using XML:
CREATE TABLE TestTable ([ID] INT, [Name] CHAR(1))
INSERT INTO TestTable ([ID],[Name]) VALUES (1,'A')
INSERT INTO TestTable ([ID],[Name]) VALUES (2,'B')
INSERT INTO TestTable ([ID],[Name]) VALUES (3,'C')
INSERT INTO TestTable ([ID],[Name]) VALUES (1,'D')
INSERT INTO TestTable ([ID],[Name]) VALUES (1,'E')
INSERT INTO TestTable ([ID],[Name]) VALUES (2,'F')
INSERT INTO TestTable ([ID],[Name]) VALUES (3,'G')
INSERT INTO TestTable ([ID],[Name]) VALUES (4,'H')
SELECT
[ID],
STUFF((
SELECT ' ' + [Name]
FROM TestTable
WHERE (ID = Results.ID)
FOR XML PATH(''),TYPE).value('(./text())[1]','VARCHAR(MAX)')
,1,2,'') AS Names
FROM TestTable Results
GROUP BY ID
I get below result
ID Names
1 A D E
2 B F
3 C G
4 H
However i have requirement to limit number of Names to two, if its more than two, it should split to next row, Something like below. Here for ID=1, there were 3 Names, so 3rd name split into next row. How can i achieve this requirement
ID Names
1 A D
1 E
2 B F
3 C G
4 H
Thanks
It sounds like you want conditional aggregation, with up to two name columns per row. You can do this using row_number() and some arithmetic:
select id,
max(case when seqnum % 2 = 1 then name end) as name1,
max(case when seqnum % 2 = 0 then name end) as name2
from (select t.*,
row_number() over (partition by id order by name) as seqnum
from testtable t
) t
group by id, ceiling(seqnum / 2.0)
order by id, min(seqnum);
Here is a db<>fiddle.
I should note that you can concatenate these into a single column. I don't see a reason to do so, because you know the maximum number on each row.

how to pick top 2 rows in a table based on the indicator

I have a sample data like this
Declare #table Table
(
ID INT,
Value VARCHAR(10),
Is_failure int
)
insert into #table(ID, Value, Is_failure) values (1, 'Bits', 0)
insert into #table(ID, Value, Is_failure) values (2, 'Ip', 0)
insert into #table(ID, Value, Is_failure) values (3, 'DNA', 0)
insert into #table(ID, Value, Is_failure) values (6, 'DCP', 1)
insert into #table(ID, Value, Is_failure) values (8, 'Bits', 0)
insert into #table(ID, Value, Is_failure) values (11, 'calc', 0)
insert into #table(ID, Value, Is_failure) values (14, 'DISC', 0)
insert into #table(ID, Value, Is_failure) values (19, 'DHCP', 1)
Looks like this:
ID Value Is_failure
1 Bits 0
2 Ip 0
3 DNA 0
6 DCP 1
8 Bits 0
11 calc 0
14 DISC 0
19 DHCP 1
Data continuous like this ... I need to fetch top 2 records along with Is_failure whenever Is_failure = 1 comes if it is 0 no need to pick up .
Sample output:
ID Value Is_failure
2 Ip 0
3 DNA 0
6 DCP 1
11 calc 0
14 DISC 0
19 DHCP 1
Suggest on this I have tried with having count(*) and other things but not fruitful.
You can use this query
Declare #tmptable Table
(
ID INT,
Value VARCHAR(10),
Is_failure int,
rowNum int
)
Declare #continuousRows int =2
insert into #tmptable
select *,ROW_NUMBER() over (order by id) from #table
;with cte1 as
(select *
from #tmptable t
where (select sum(Is_failure) from #tmptable t1 where t1.rowNum between t.rowNum-#continuousRows and t.rowNum
having count(*)=#continuousRows+1)=1
and t.Is_failure=1
)
,cte2 as
(
select t.* from #tmptable t
join cte1 c on t.rowNum between c.rowNum-#continuousRows and c.rowNum
)
select c.ID,value,Is_failure from cte2 c
You can use window functions for this:
select id, value, is_failure
from (select t.*,
lead(Is_failure) over (order by id) as next_if,
lead(Is_failure, 2) over (order by id) as next_if2
from #table t
) t
where 1 in (Is_failure, next_if, next_if2)
order by id;
You can simplify this with a windowing clause:
select id, value, is_failure
from (select t.*,
max(is_failure) over (order by id rows between current row and 2 following) as has_failure
from #table t
) t
where has_failure > 0
order by id;

How to convert many rows into Columns in SQL Server?

How would you convert a field that is stored as multiple rows into columns?
I listed the code below as well. Below is an example of what is needed but it can really go up to 20 columns. Thanks!
COL1 COL2 COL3
----------------
TEST 30 NY
TEST 30 CA
TEST2 10 TN
TEST2 10 TX
I would like the output to be :
COL1 COL2 COL3 COL4
------------------------
TEST 30 NY CA
TEST2 10 TN TX
select * from (
select
ID,
Name,
STORE,
Group,
Type,
Date,
State,
row_number() over(partition by ID, state order by Date desc) as rn
from
#test
) t
where t.rn = 1
declare #Table AS TABLE
(
Col1 VARCHAR(100) ,
Col2 INT ,
Col3 VARCHAR(100)
)
INSERT #Table
( Col1, Col2, Col3 )
VALUES
( 'TEST', 30 ,'NY' ),
( 'TEST', 30 ,'CA' ),
( 'TEST2', 10 ,'TN' ),
( 'TEST2', 10 ,'TX' )
SELECT
xQ.Col1,
xQ.Col2,
MAX(CASE WHEN xQ.RowNumber = 1 THEN xQ.Col3 ELSE NULL END) AS Col3,
MAX(CASE WHEN xQ.RowNumber = 2 THEN xQ.Col3 ELSE NULL END) AS Col4
FROM
(
SELECT * , RANK() OVER(PARTITION BY T.Col1,T.Col2 ORDER BY T.Col1,T.Col2,T.Col3) AS RowNumber
FROM #Table AS T
)AS xQ
GROUP BY
xQ.Col1,
xQ.Col2
There are multiple options to convert data from rows into columns. In SQL, you can use PIVOT to transform data from rows into columns.
CREATE table #tablename
(Id int, Value varchar(10), ColumnName varchar(15);
INSERT INTO #tablename
(ID, Value, ColumnName)
VALUES
(1, ‘Lucy’, 'FirstName'),
(2, ‘James’, ‘LastName’),
(3, ‘ABCDXX’, ‘Adress’),
(4, ’New York’, ‘City’),
(5, '8572685', ‘PhoneNo’);
select FirstName, LastName, Address, City, PhoneNo
from
(
select Value, ColumnName
from #tablename
) d
pivot
(
max(Value)
for ColumnName in (FirstName, LastName, Address, City, PhoneNo)
) piv;
Refer the below link for other options of transforming data from rows to columns:
https://www.sqlshack.com/multiple-options-to-transposing-rows-into-columns/

How to get rows In each Group with minimum order group and special name in SQL Server

i have a table similar this
CREATE TABLE [dbo].[Test](
[Name] [NCHAR](10) NULL,
[GroupId] [INT] NULL,
[GroupOrder] [INT] NULL
)
and below values
INSERT INTO [dbo].[Test]([Name],[GroupId],[GroupOrder]) VALUES ('A',1,1)
INSERT INTO [dbo].[Test]([Name],[GroupId],[GroupOrder]) VALUES ('A-1',1,2)
INSERT INTO [dbo].[Test]([Name],[GroupId],[GroupOrder]) VALUES ('B',2,1)
INSERT INTO [dbo].[Test]([Name],[GroupId],[GroupOrder]) VALUES ('B',2,2)
INSERT INTO [dbo].[Test]([Name],[GroupId],[GroupOrder]) VALUES ('B-1',2,3)
INSERT INTO [dbo].[Test]([Name],[GroupId],[GroupOrder]) VALUES ('C',3,1)
INSERT INTO [dbo].[Test]([Name],[GroupId],[GroupOrder]) VALUES ('C-1',3,2)
INSERT INTO [dbo].[Test]([Name],[GroupId],[GroupOrder]) VALUES ('C-1',3,3)
INSERT INTO [dbo].[Test]([Name],[GroupId],[GroupOrder]) VALUES ('D',4,1)
INSERT INTO [dbo].[Test]([Name],[GroupId],[GroupOrder]) VALUES ('D',4,2)
i need output like this: rows In each Group with minimum order group and name field contain "-1"
Name GroupId GroupOrder
A-1 1 2
C-1 3 2
B-1 2 3
You could use charindex, top 1 with ties, and row_number
select Top (1) with ties
*
from Test t
where charindex('-1', t.Name ) > 0
order by row_number() over(partition by t.GroupId order by t.GroupOrder)
Demo link: http://rextester.com/RBBKX12749
You can use this
Select * from
(
select *,Row_Number() Over(Partition by GroupId Order by GroupOrder asc) as rn
From yourtable
Where Name like '%-1%'
) a
Where rn = 1
CREATE TABLE [dbo].[#Test](
[Name] [NCHAR](10) NULL,
[GroupId] [INT] NULL,
[GroupOrder] [INT] NULL
)
INSERT INTO [dbo].[#Test]([Name],[GroupId],[GroupOrder]) VALUES ('A',1,1)
INSERT INTO [dbo].[#Test]([Name],[GroupId],[GroupOrder]) VALUES ('A-1',1,2)
INSERT INTO [dbo].[#Test]([Name],[GroupId],[GroupOrder]) VALUES ('B',2,1)
INSERT INTO [dbo].[#Test]([Name],[GroupId],[GroupOrder]) VALUES ('B',2,2)
INSERT INTO [dbo].[#Test]([Name],[GroupId],[GroupOrder]) VALUES ('B-1',2,3)
INSERT INTO [dbo].[#Test]([Name],[GroupId],[GroupOrder]) VALUES ('C',3,1)
INSERT INTO [dbo].[#Test]([Name],[GroupId],[GroupOrder]) VALUES ('C-1',3,2)
INSERT INTO [dbo].[#Test]([Name],[GroupId],[GroupOrder]) VALUES ('C-1',3,3)
INSERT INTO [dbo].[#Test]([Name],[GroupId],[GroupOrder]) VALUES ('D',4,1)
INSERT INTO [dbo].[#Test]([Name],[GroupId],[GroupOrder]) VALUES ('D',4,2)
select * from #Test
Select * from
(
select *,Row_Number() Over(Partition by Name Order by GroupOrder asc) as rn
From #Test
Where Name like '%[-1]%'
) a
Where rn = 1
ORDER BY GroupOrder
OUTPUT
Name GroupId GroupOrder
A-1 1 2
B-1 2 3
C-1 3 2
Use ROW_NUMBER window function
Select * from
(
select *,Row_Number() Over(Partition by Name Order by GroupOrder asc) as rn
From yourtable
Where rtrim(Name) like '[A-Z]-1'
) a
Where rn = 1
If the Name should end with -1 then use Where Name like '%-1'. If Name just contains -1 then Where Name like '%-1%'