Pivot Multiple Fields With Dynamic Descriptions - sql

I'm trying to pivot the following and I don't know why I'm having such a hard time figuring it out.
Data Script
create table #data (ID varchar(50)
, nm varchar(50)
, val decimal(18,2)
)
insert into #data values (1,'Name1', 100.00),
(1,'Name2', 200.00),
(2,'Name3', 300.00),
(2,'Name4', 400.00),
(2,'Name5', 500.00),
(3,'Name6', 600.00),
(4,'Name7', 700.00),
(4,'Name8', 800.00),
(5,'Name9', 900.00)
Wanted Results As A Table in SQL Server
1 Name1 100 Name2 200
2 Name3 300 Name4 400 Name5 500
3 Name6 600
4 Name7 700 Name8 800
5 Name9 900
Update:
The following provides results in two fields, but what I really want is for the Name and Values to all exist in separate columns, not in one,
SELECT id,
(
SELECT nm,val
FROM #data
WHERE id = d.id
ORDER BY id FOR XML PATH('')
)
FROM #data d
WHERE
id IS NOT NULL
GROUP BY id;

This is an example of "pivot" not aggregate string concatenation. One issue with SQL queries is that you need to specify the exact columns being returned. So, this cannot be dynamic with respect to the returning columns.
The following returns up to three values per nm:
select id,
max(case when seqnum = 1 then nm end) as nm_1,
max(case when seqnum = 1 then val end) as val_1,
max(case when seqnum = 2 then nm end) as nm_2,
max(case when seqnum = 2 then val end) as val_2,
max(case when seqnum = 3 then nm end) as nm_3,
max(case when seqnum = 3 then val end) as val_3
from (select d.*,
row_number() over (partition by id order by (select null)) as seqnum
from #data d
) d
group by id;
Note that you probably want the columns in insertion order. If so, you need to specify a column with the ordering. I would recommend defining the table as:
create table #data (
dataId int identity(1, 1,) primary key,
ID varchar(50),
nm varchar(50),
val decimal(18,2)
);

Related

Insert records from multiple rows of table to multiple columns of other table

I have an existing table structure with sample data like
Id
BookingId
Value
TypeId
AddedTime
1
100
10
T1
2021-03-22 08:51:52.6333333
2
100
20
T2
2021-03-22 08:50:55.8133333
3
100
30
T3
2021-03-22 08:50:22.1033333
4
200
50
T1
2021-03-22 08:50:22.1033333
5
200
60
T2
2021-03-22 08:50:22.1000000
6
200
70
T3
2021-03-22 08:50:22.0800000
and now data model is changed and it becomes like
Id
BookingId
Type1Value
Type2Value
Type3Value
AddedTime
Please help me what would be query to copy data from previous table to new table.
Output should be something like
Id
BookingId
Type1Value
Type2Value
Type3Value
AddedTime
1
100
10
20
30
2
200
50
60
70
I tried:
select BookingId
, Type1Value = max(case when RN=1 then Value else null end)
, Type2Value = max(case when RN=2 then Value else null end)
, Type3Value = max(case when RN=3 then Value else null end)
from (
select *
, rn = Row_Number() over (Partition By TypeId Order by AddedTime)
from Values_M
) a
where rn <= 3
group by BookingId
This will gives you the required result using conditional case expression.
Using row_number() to generate new running number Id
select Id = row_number() over (order by BookingId),
BookingId = BookingId,
Type1Value = max(case when TypeId = 'T1' then Value end),
Type2Value = max(case when TypeId = 'T2' then Value end),
Type3Value = max(case when TypeId = 'T3' then Value end),
AddedTime = min(AddedTime)
from Values_M
group by BookingId
dbfiddle
select BookingId, min(T1) as Type1Value, min(T2) as Type2Value, min(T3) as Type3Value
from table1
pivot (sum(value) for Typeid in (T1,T2,T3)) as PivotTable
group by BookingId
You can use row_number() and self join.
with cte as (
select Id, BookingId, [Value], TypeId, AddedTime
, row_number() over (partition by BookingId order by id asc) rn
from Values_M
)
select C1.rn, C1.BookingId, C1.[Value] Type1Value, C2.[Value] Type2Value, C3.[Value] Type3Value, C1.AddedTime
from cte C1
inner join cte C2 on C2.BookingId = C1.BookingId and C2.rn = 2
inner join cte C3 on C3.BookingId = C1.BookingId and C3.rn = 3
where C1.rn = 1
order by BookingId asc;

Convert column value to row value

In SQL Server, I am trying to convert the from table 1 to table 2. From reading other answers from stack overflow, I can do some sort of row_number(). But the problem is I need do some inner join after the conversion because the following script use max() aggregate function, it kind force other fields from other tables to have some sort of aggregate function as well. So I was wondering if there is an alternative approach to solve this problem? Or if there is a way to handle this aggregate function when do join with another table.
select max(case when key = 'ab' then Value end) as ab,
max(case when key = 'cd' then Value end) as cd
from (select t.*,
row_number() over (partition by key order by Value) as seq
from table t
) t
group by seq;
table 1
table 2
You can try with this below script-
SELECT id,
MAX(CASE WHEN name = 'car1' THEN name END) car1,
MAX(CASE WHEN name = 'car2' THEN name END) car2,
MAX(CASE WHEN name = 'car3' THEN name END) car3
FROM your_table
GROUP BY id
You can go for PIVOT feature.
;WITH src as
(
SELECT *
FROM
(
VALUES
(1, 'Car1', 'nissan'),
(1, 'Car2', 'audi'),
(1, 'Car3', 'toyota')
) as t (id, name, value)
)
SELECT *
FROM src
PIVOT
(
max(VALUE) FOR NAME IN ([Car1], [Car2], [Car3])
) as pvt
+----+--------+------+--------+
| id | Car1 | Car2 | Car3 |
+----+--------+------+--------+
| 1 | nissan | audi | toyota |
+----+--------+------+--------+

How to split an SQL Table into half and send the other half of the rows to new columns with SQL Query?

Country Percentage
India 12%
USA 20%
Australia 15%
Qatar 10%
Output :
Country1 Percentage1 Country2 Percentage2
India 12% Australia 15%
USA 20% Qatar 10%
For example there is a table Country which has percentages, I need to divide the table in Half and show the remaining half (i.e. the remaining rows) in the new columns. I've also provided the table structure in text.
First, this type of operation should be done at the application layer and not in the database. That said, it can be an interesting exercise to see how to do this in the database.
I would use conditional aggregation or pivot. Note that SQL tables are inherently unordered. Your base table has no apparent ordering, so the values could come out in any order.
select max(case when seqnum % 2 = 0 then country end) as country_1,
max(case when seqnum % 2 = 0 then percentage end) as percentage_1,
max(case when seqnum % 2 = 1 then country end) as country_2,
max(case when seqnum % 2 = 1 then percentage end) as percentage_2
from (select c.*,
(row_number() over (order by (select null)) - 1) as seqnum
from country c
) c
group by seqnum / 2;
Try this
declare #t table
(
Country VARCHAR(20),
percentage INT
)
declare #cnt int
INSERT INTO #T
VALUES('India',12),('USA',20),('Australia',15),('Quatar',12)
select #cnt = count(1)+1 from #t
;with cte
as
(
select
SeqNo = row_number() over(order by Country),
Country,
percentage
from #t
)
select
*
from cte c1
left join cte c2
on c1.seqno = (c2.SeqNo-#cnt/2)
and c2.SeqNo >= (#cnt/2)
where c1.SeqNo <= (#cnt/2)
My variant
SELECT 'A' Country,1 Percentage INTO #Country
UNION ALL SELECT 'B' Country,2 Percentage
UNION ALL SELECT 'C' Country,3 Percentage
UNION ALL SELECT 'D' Country,4 Percentage
UNION ALL SELECT 'E' Country,5 Percentage
;WITH numCTE AS(
SELECT
*,
ROW_NUMBER()OVER(ORDER BY Country) RowNum,
COUNT(*)OVER() CountOfCountry
FROM #Country
),
set1CTE AS(
SELECT Country,Percentage,ROW_NUMBER()OVER(ORDER BY Country) RowNum
FROM numCTE
WHERE RowNum<=CEILING(CountOfCountry/2.)
),
set2CTE AS(
SELECT Country,Percentage,ROW_NUMBER()OVER(ORDER BY Country) RowNum
FROM numCTE
WHERE RowNum>CEILING(CountOfCountry/2.)
)
SELECT
s1.Country,s1.Percentage,
s2.Country,s2.Percentage
FROM set1CTE s1
LEFT JOIN set2CTE s2 ON s1.RowNum=s2.RowNum
DROP TABLE #Country
I just wanted to try something. I have used the function OFFSET. It does the requirement i think for your sample data, but dont know if its bulletproof all the way:
SQL Code
declare #myt table (country nvarchar(50),percentage int)
insert into #myt
values
('India' ,12),
('USA' ,20),
('Australia' ,15),
('Qatar' ,10),
('Denmark',10)
DECLARE #TotalRows int
SET #TotalRows = (select CEILING(count(*) / 2.) from #myt);
WITH dataset1 AS (
SELECT *,ROW_NUMBER() over(order by country ) as rn from (
SELECT Country,percentage from #myt a
ORDER BY country OFFSET 0 rows FETCH FIRST #TotalRows ROWS ONLY
) z
)
,dataset2 AS (
SELECT *,ROW_NUMBER() over(order by country ) as rn from (
SELECT Country,percentage from #myt a
ORDER BY country OFFSET #TotalRows rows FETCH NEXT #TotalRows ROWS ONLY
) z
)
SELECT * FROM dataset1 a LEFT JOIN dataset2 b ON a.rn = b.rn
Result
Assuming you want descending alphabetic country names, but the left column is determined by where India is located in the result:
with CoutryCTE as (
select c.*
, row_number() over (order by country)-1 as rn
from country c
)
, Col as (
select rn % 2 as num from CoutryCTE
where Country = 'India'
)
select max(case when rn % 2 = Col.num then country end) as country_1
, max(case when rn % 2 = Col.num then percentage end) as percentage_1
, max(case when rn % 2 <> Col.num then country end) as country_2
, max(case when rn % 2 <> Col.num then percentage end) as percentage_2
from CoutryCTE
cross join Col
group by rn / 2
;
SQLFiddle Demo
| country_1 | percentage_1 | country_2 | percentage_2 |
|-----------|--------------|-----------|--------------|
| India | 12% | Australia | 15% |
| USA | 20% | Qatar | 10% |
nb: this is extremely similar to an earlier answer by Gordon Linoff

Find Common Rows for some Row Values in SQL

I have a table with Ids and a subId column. And I have a user defined data type with a list of SubIds. I want all those ids which have all the sub-ids present in my user-defined data type. for example:
The table is:
ID SubID
1 2
1 3
1 4
2 3
2 4
2 2
3 3
3 2
and the data type is
CREATE TYPE SubIds AS TABLE
( SubId INT );
GO
With Value
SubID
3
4
I want the output to be
ID
1
2
Because only the ID 1 and 2 contain both the subIds 3 & 4
Note: the combination of Id and Sub ID will always be unique if its of any use
Let's assume that #s is your table of ids:
select t.ID
from t
Where t.SubId in (select SubId from #s)
group by t.Id
having count(*) = (select count(*) from #s);
This assumes that the two tables do not have duplicates. If duplicates are present, you can use:
select t.ID
from t
Where t.SubId in (select SubId from #s)
group by t.Id
having count(distinct t.SubId) = (select count(distinct s.SubId) from #s s);
Try this way
select ID
from yourtable
Where SubID in (3,4)
Group by ID
having Count(distinct SubID)=2
Another more flexible approach
select ID
from yourtable
Group by ID
having sum(case when SubID = 3 then 1 else 0 end) >= 1
and sum(case when SubID = 4 then 1 else 0 end) >= 1
If you want to pull SubId's from SubIds table type then,
SELECT ID
FROM yourtable T
JOIN (SELECT SubID,
Count(1) OVER() AS cnt
FROM SubIds) S
ON T.SubID = S.SubID
GROUP BY ID,Cnt
HAVING Count(DISTINCT T.SubID) = s.cnt

SQL Server 2008: Converting rows into columns

I have two tables:
CREATE TABLE #A (id int, cond_id int)
INSERT INTO #A (id, cond_id)
VALUES (101,20),
(101,22),
(101,24),
(102,23),
(102,22)
Now, each id can have max of 4 cond_ids. I want to populate table #B so that there is one id and all cond_ids will be populated in the columns as one row according to cond_id ascending.
like for id 102, cond_id 22 goes in cond_id and 23 goes in cond_id2.
create table #B (id int, cond_id1 int, cond_id2 int, cond_id3 int, cond_id4 int)
Desired result:
Table #B
id cond_id1 cond_id2 cond_id3 cond_id4
101 20 22 24 null
102 22 23 null null
Thanks in advance!
Because you know the maximum number of columns, one option is to use row_number, max and case:
with cte as (
select row_number() over (partition by id order by cond_id) rn, id, cond_id
from a)
select id,
max(case when rn = 1 then cond_id end) cond_id1,
max(case when rn = 2 then cond_id end) cond_id2,
max(case when rn = 3 then cond_id end) cond_id3,
max(case when rn = 4 then cond_id end) cond_id4
from cte
group by id
SQL Fiddle Demo
Or you could look at Pivot:
select id, [1] cond_id1, [2] cond_id2, [3] cond_id3, [4] cond_id4
from
(select row_number() over (partition by id order by cond_id) rn, id, cond_id
from a) t
pivot
(
max(cond_id)
for rn in ([1], [2], [3], [4])
) p
More Fiddle