Complicated irregular pivot in SQL Server 2012 - sql

I have two tables:
a (column1, column2, column3)
b (column6, column7, column8)
a.column1 is foreign key as in b.column6.
One row from table a sometimes matches 3 rows in table b, sometimes 5, sometimes 1.... no definite count of returned rows.
I have a business requirement to flip all corresponding columns in table b into one row.. like this:
a.column1, a.column2, a.column3, b.column7, b.column8, b.column7, b.column8
a.column1, a.column2, a.column3, b.column7, b.column8
a.column1, a.column2, a.column3, b.column7, b.column8, b.column7, b.column8, b.column7, b.column8
a.column1, a.column2, a.column3, b.column7, b.column8, b.column7, b.column8, b.column7, b.column8b.column7, b.column8, b.column7, b.column8
You see , the number of columns in each row from Table a is always 3... but from table b, you might have a variable number of columns.... And column7 and column8 have to repeated appear in that order.
How can I do this? Thanks.

It sounds like you are going to need to unpivot and then pivot the data. If you have an unknown number of values you will have to use dynamic SQL but I would first suggest writing a hard-code or static version of the query first, then convert it to dynamic SQL.
The process to unpivot the data is going to take your multiple columns in tableB and convert it into multiple rows. Since you are using SQL Server 2012 you can use CROSS APPLY to unpivot the data:
select column1, column2, column3,
col = col + '_' + cast(seq as varchar(10)),
value
from
(
select a.column1, a.column2, a.column3,
b.column6, b.column7, b.column8,
row_number() over(partition by a.column1
order by a.column1) seq
from tablea a
inner join tableb b
on a.column1 = b.column6
) d
cross apply
(
select 'column6', column6 union all
select 'column7', column7 union all
select 'column8', column8
) c (col, value);
See SQL Fiddle with Demo. This will give you a result similar to:
| COLUMN1 | COLUMN2 | COLUMN3 | COL | VALUE |
| 1 | 2 | 3 | column6_1 | 1 |
| 1 | 2 | 3 | column7_1 | 18 |
| 1 | 2 | 3 | column8_1 | 56 |
| 1 | 2 | 3 | column6_2 | 1 |
| 1 | 2 | 3 | column7_2 | 25 |
| 1 | 2 | 3 | column8_2 | 89 |
As you can see you now have multiple rows that you can easily apply the pivot function to. The PIVOT code will be:
select column1, column2, column3,
column6_1, column7_1, column8_1,
column6_2, column7_2, column8_2,
column6_3, column7_3, column8_3
from
(
select column1, column2, column3,
col = col + '_' + cast(seq as varchar(10)),
value
from
(
select a.column1, a.column2, a.column3,
b.column6, b.column7, b.column8,
row_number() over(partition by a.column1
order by a.column1) seq
from tablea a
inner join tableb b
on a.column1 = b.column6
) d
cross apply
(
select 'column6', column6 union all
select 'column7', column7 union all
select 'column8', column8
) c (col, value)
) src
pivot
(
max(value)
for col in (column6_1, column7_1, column8_1,
column6_2, column7_2, column8_2,
column6_3, column7_3, column8_3)
) piv;
See SQL Fiddle with Demo. Since you stated that you might have an unknown or dynamic number of entries in tableB you will need to use dynamic SQL. This will generate a sql string that will be executed to get you the final result:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(col +'_'+cast(seq as varchar(10)))
from
(
select row_number() over(partition by column6
order by column6) seq
from tableB
) t
cross apply
(
select 'column6', 1 union all
select 'column7', 2 union all
select 'column8', 3
) c (col, so)
group by col, so, seq
order by seq, so
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT column1, column2, column3,' + #cols + '
from
(
select column1, column2, column3,
col = col + ''_'' + cast(seq as varchar(10)),
value
from
(
select a.column1, a.column2, a.column3,
b.column6, b.column7, b.column8,
row_number() over(partition by a.column1
order by a.column1) seq
from tablea a
inner join tableb b
on a.column1 = b.column6
) d
cross apply
(
select ''column6'', column6 union all
select ''column7'', column7 union all
select ''column8'', column8
) c (col, value)
) x
pivot
(
max(value)
for col in (' + #cols + ')
) p '
execute sp_executesql #query;
See SQL Fiddle with Demo. Both versions give a result:
| COLUMN1 | COLUMN2 | COLUMN3 | COLUMN6_1 | COLUMN7_1 | COLUMN8_1 | COLUMN6_2 | COLUMN7_2 | COLUMN8_2 | COLUMN6_3 | COLUMN7_3 | COLUMN8_3 |
| 1 | 2 | 3 | 1 | 18 | 56 | 1 | 25 | 89 | (null) | (null) | (null) |
| 2 | 4 | 6 | 2 | 78 | 245 | (null) | (null) | (null) | (null) | (null) | (null) |
| 3 | 8 | 9 | 3 | 10 | 15 | 3 | 45 | 457 | 3 | 89 | 50 |

Related

Convert row into column like pivot

Table is:
+----+------+
| Id | Name |
+----+------+
| 1 | aaa |
| 1 | bbb |
| 2 | ccc |
| 2 | ddd |
| 3 | eee |
+----+------+
Required output:
+----+---------------------++---------------------+
| Id | colum1 | column2 |
+----+---------------------+ +--------------------+
| 1 | aaa | | bbb |
+----+---------------------++---------------------+
+----+---------------------+ +--------------------+
| 2 | ccc | | ddd |
+----+---------------------++---------------------+
+----+---------------------+ +--------------------+
| 3 | eee | | null |
+----+---------------------++---------------------+
I've been trying on 'with' a and pivot but it seems not in the right way I want a column if I have more than one id
like the image
You can use row_number() & do aggregation if you have a some limited amount of names else you would need to use dynamic SQL for this:
select id,
max(case when seq = 1 then name end) as col1,
max(case when seq = 2 then name end) as col2,
max(case when seq = 3 then name end) as col3,
. . .
from (select t.*, row_number() over (partition by id order by name) as seq
from table t
) t
group by id;
You can use PIVOT for making this.
DECLARE #Tbl TABLE ( Id INT, Name VARCHAR(10))
INSERT INTO #Tbl VALUES
(1, 'aaa'),
(1, 'bbb'),
(2, 'ccc'),
(2, 'ddd'),
(3, 'eee')
SELECT Id, [1] column1, [2] column2 FROM (
SELECT *,
ROW_NUMBER() OVER(PARTITION BY Id ORDER BY Name) RN
FROM #Tbl ) AS SRC PIVOT (MAX(Name) FOR RN IN ([1], [2])) PVT
Result:
Id column1 column2
----------- ---------- ----------
1 aaa bbb
2 ccc ddd
3 eee NULL
Using row_number() build a dynamic query to execute()
--example table
create table #t (Id int, Name varchar(100))
insert into #t values (1,'aaa'),(1,'bbb'),(2,'ccc'),(2,'ddd'),(3,'eee')
-- number of columns to create
declare #columns int
select top 1 #columns = count(*) from #t group by id order by COUNT(*) desc
--build a query
declare #i int = 2, #qry varchar(max) = 'select Id, column1 = max(case when ord = 1 then name end)'
while #i<=#columns begin
select #qry = #qry + ', column'+cast(#i as varchar(5))+' = max(case when ord = '+cast(#i as varchar(5))+' then name end)'
set #i = #i + 1
end
select #qry = #qry + ' from (select *, ord = row_number() over (partition by id order by name)
from #t
) t
group by id'
--execute the query
execute (#qry)
drop table #t

pgsql 1 to n relation into json

Long story short, how can i use 1 to n select data to build json like shown in example:
SELECT table1.id AS id1,table2.id AS id2,t_id,label
FROM table1 LEFT JOIN table2 ON table2.t_id = table1.id
result
|id1|id2|t_id|label|
+---+---+----+-----+
|1 | 1 | 1 | a |
| | 2 | 1 | b |
| | 3 | 1 | c |
| | 4 | 1 | d |
|2 | 5 | 2 | x |
| | 6 | 2 | y |
turn into this
SELECT table1.id, build_json(table2.id,table2.label) AS json_data
FROM table1 JOIN table2 ON table2.t_id = table1.id
GROUP BY table1.id
|id1|json_data
+--+-----------------
|1 |{"1":"a","2":"b","3":"c","4":"d"}
|2 |{"5":"x","6":"y"}
My guess the best start woulb be building an array from columns
Hstore instead of json would be ok too
well your table structure is a bit strange (is looks more like report than table), so I see two tasks here:
Replace nulls with correct id1. You can do it like this
with cte1 as (
select
sum(case when id1 is null then 0 else 1 end) over (order by t_id) as id1_partition,
id1, id2, label
from Table1
), cte2 as (
select
first_value(id1) over(partition by id1_partition) as id1,
id2, label
from cte1
)
select *
from cte2
Now you have to aggregate data into json. As far as I remember, there's no such a function in PostgreSQL, so you have to concatenate data manually:
with cte1 as (
select
sum(case when id1 is null then 0 else 1 end) over (order by t_id) as id1_partition,
id1, id2, label
from Table1
), cte2 as (
select
first_value(id1) over(partition by id1_partition) as id1,
id2, label
from cte1
)
select
id1,
('{' || string_agg('"' || id2 || '":' || to_json(label), ',') || '}')::json as json_data
from cte2
group by id1
sql fiddle demo
And if you want to convert into hstore:
with cte1 as (
select
sum(case when id1 is null then 0 else 1 end) over (order by t_id) as id1_partition,
id1, id2, label
from Table1
), cte2 as (
select
first_value(id1) over(partition by id1_partition) as id1,
id2, label
from cte1
)
select
c.id1, hstore(array_agg(c.id2)::text[], array_agg(c.label)::text[])
from cte2 as c
group by c.id1
sql fiddle demo

Select Distinct From Table - Two Columns No Column Should Have Repetition

How do I a select against a table A for example which contains these records.
|Column1|Column2|
| A |F |
| A | G |
| B |G |
| B |H |
| C |H |
| D |H |
| E |I |
My expected result is:
|Column1 |Column2|
| A | F |
| B | G |
| C | H |
| E | I |
All columns should have a unique value in them.
What query statement can I use for this?
Thanks
Please try:
select
MIN(Column1) Column1,
Column2
from(
select
Column1,
MIN(Column2) Column2
from YourTable
group by Column1
)x group by Column2
order by 1
SQL Fiddle Demo
It didn't work for this scenario.
create table YourTable (Column1 varchar2(10),
Column2 varchar2(10));
insert into YourTable values ('B','F');
insert into YourTable values ('B','G');
insert into YourTable values ('B','H');
insert into YourTable values ('C','F');
insert into YourTable values ('C','G');
insert into YourTable values ('C','H');
insert into YourTable values ('D','F');
insert into YourTable values ('D','G');
insert into YourTable values ('D','H');
My expectation is
B F
C G
D H
but I only got
B F
Thanks a lot!
SELECT a.val, b.val FROM
(
SELECT val, rownum as rno
FROM
(
SELECT distinct column1 as val
FROM YourTable
)) a,
(
SELECT val, rownum as rno
FROM
(
SELECT distinct column2 as val
FROM YourTable
)) b
WHERE a.rno = b.rno
ORDER BY 1
/
VAL VAL_1
-----------
B F
C G
D H
OR
select column1 as val from YourTable
UNION
select column2 from YourTable
VAL
-----
B
C
D
F
G
H

Pivot Table Missing Column

I am trying to use a pivot to get information in a diff format.
Here is my table:
CREATE TABLE yourtable
([case] int, [category] varchar(4))
;
INSERT INTO yourtable
([case], [category])
VALUES
(1, 'xx'),
(1, 'xyx'),
(1, 'abc'),
(2, 'ghj'),
(2, 'asdf'),
(3, 'dfgh')
;
Here is my pivot command courtesy of bluefeet:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME('cat'+cast(seq as
varchar(10)))
from
(
select row_number() over(partition by [case]
order by category) seq
from yourtable
) d
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT [case],' + #cols + '
from
(
SELECT [case], category,
''cat''+
cast(row_number() over(partition by [case]
order by category) as varchar(10)) seq
FROM yourTable
) x
pivot
(
max(category)
for seq in (' + #cols + ')
) p '
execute sp_executesql #query;
The output is good, it is in the format I need.
CASE CAT1 CAT2 CAT3
1 abc xx xyx
2 asdf ghj (null)
3 dfgh (null) (null)
However, I also need to add additional columns to the table. The modified table would be as follows, but I'm not sure how to add this to the QUOTENAME.
CREATE TABLE yourtable
([case] int, [category] varchar(4), [status] varchar(4))
;
INSERT INTO yourtable
([case], [category], [status])
VALUES
(1, 'xx', '00'),
(1, 'xyx', '01'),
(1, 'abc', '00'),
(2, 'ghj', '01'),
(2, 'asdf', '00'),
(3, 'dfgh', '01')
;
How can this be done? Should I add an additional QUOTENAME command? Results should be:
CASE CAT1 status1 CAT2 status2 CAT3 status3
1 abc 00 xx 00 xyx 01
2 asdf 00 ghj 01 (null) (null)
3 dfgh 01 (null) (null) (null) (null)
Since you now have two columns that you want to PIVOT, you can first unpivot the category and status columns into a single column with multiple rows.
There are a few different ways you can unpivot the data, you can use UNPIVOT or CROSS APPLY. The basic syntax will be:
select [case],
col+cast(seq as varchar(10)) seq,
value
from
(
SELECT [case], status, category,
row_number() over(partition by [case]
order by status) seq
FROM yourTable
) d
cross apply
(
select 'cat', category union all
select 'status', status
) c (col, value)
See SQL Fiddle with Demo This will convert your multiple columns of data into something that looks like this:
| CASE | SEQ | VALUE |
|------|---------|-------|
| 1 | cat1 | xx |
| 1 | status1 | 00 |
| 1 | cat2 | abc |
| 1 | status2 | 00 |
| 1 | cat3 | xyx |
| 1 | status3 | 01 |
| 2 | cat1 | asdf |
| 2 | status1 | 00 |
Once the data is in this format, then you can apply the PIVOT function to it.
SELECT [case], cat1, status1, cat2, status2, cat3, status3
FROM
(
select [case],
col+cast(seq as varchar(10)) seq,
value
from
(
SELECT [case], status, category,
row_number() over(partition by [case]
order by status) seq
FROM yourTable
) d
cross apply
(
select 'cat', category union all
select 'status', status
) c (col, value)
) x
PIVOT
(
max(value)
for seq in (cat1, status1, cat2, status2, cat3, status3)
)p;
See SQL Fiddle with Demo
Then you can convert it to dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(col+cast(seq as varchar(10)))
from
(
select row_number() over(partition by [case]
order by category) seq
from yourtable
) d
cross apply
(
select 'cat', 1 union all
select 'status', 2
) c (col, so)
group by seq, col, so
order by seq, so
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT [case],' + #cols + '
from
(
select [case],
col+cast(seq as varchar(10)) seq,
value
from
(
SELECT [case], status, category,
row_number() over(partition by [case]
order by status) seq
FROM yourTable
) d
cross apply
(
select ''cat'', category union all
select ''status'', status
) c (col, value)
) x
pivot
(
max(value)
for seq in (' + #cols + ')
) p '
execute sp_executesql #query;
See SQL Fiddle with Demo The final result will be:
| CASE | CAT1 | STATUS1 | CAT2 | STATUS2 | CAT3 | STATUS3 |
|------|------|---------|--------|---------|--------|---------|
| 1 | xx | 00 | abc | 00 | xyx | 01 |
| 2 | asdf | 00 | ghj | 01 | (null) | (null) |
| 3 | dfgh | 01 | (null) | (null) | (null) | (null) |

Is it possible to Pivot data like this?

I have data that looks as follows:
I would like to pivot this data so that there is only a single row for the SubId. The columns would be SubId, 802Lineage, 802ReadTime, 1000Lineage, 1000ReadTime etc.
If it wasn't for the requirement to have the Lineage included, this would be pretty straightforward, as follows:
Select SubId, [800] as [800Time], [1190] as [1190Time], [1605] as [1605Time]
From
(Select SubId, ProcessNumber, ReadTime From MyTable) as SourceTable
PIVOT
(
Max(ReadTime)
For ProcessNumber IN ([800],[802],[1190],[1605])
) as PivotTable;
I'm just not sure how to do this with the Lineage included. This is for SQL Server 2012
you can pivot manually:
select
SubId,
max(case when ProcessNumber = 802 then ReadTime end) as [802Time],
max(case when ProcessNumber = 802 then Lineage end) as [802Lineage],
....
from SourceTable
group by SubId
Example of joining two pivot tables, as requested in comments.
CREATE TABLE #MyTable (SubId int, ProcessNumber int, Lineage varchar(16), ReadTime datetime)
INSERT INTO #MyTable 
(SubID, ProcessNumber, Lineage, ReadTime)
VALUES
(1, 9, 'A', GETDATE()),
(1, 8, 'A', GETDATE()),
(1, 7, 'B', GETDATE()),
(2, 9, 'C', GETDATE()),
(2, 8, 'C', GETDATE())
SELECT * 
FROM (
Select SubId, [9] as [9Time], [8] as [8Time], [7] as [7Time]
From
(Select SubId, ProcessNumber, ReadTime From #MyTable) as SourceTable
PIVOT(Max(ReadTime) For ProcessNumber IN ([9],[8],[7],[6])) as PivotTable1
) AS T1
INNER JOIN (
Select SubId, [9] as [9Lineage], [8] as [8Lineage], [7] as [7Lineage]
From
(Select SubId, ProcessNumber, Lineage From #MyTable) as SourceTable
PIVOT(Max(Lineage) For ProcessNumber IN ([9],[8],[7],[6])) as PivotTable1
) AS T2
ON T1.SubId = T2.SubId
GO
You can use the PIVOT function to get the result but you will have to unpivot the Lineage and ReadTime columns from the multiple columns into multiple rows.
Since you are using SQL Server 2012 you can unpivot the data using CROSS APPLY with VALUES:
select subid,
colname = cast(processNumber as varchar(10)) + colname,
value
from mytable
cross apply
(
values
('Lineage', Lineage),
('ReadTime', convert(varchar(20), readtime, 120))
) c (colname, value)
See SQL Fiddle with Demo. This will convert your current data into the format:
| SUBID | COLNAME | VALUE |
|-------------|--------------|---------------------|
| 12010231146 | 802Lineage | PBG12A |
| 12010231146 | 802ReadTime | 2012-01-02 21:44:00 |
| 12010231146 | 1000Lineage | PBG12A |
| 12010231146 | 1000ReadTime | 2012-01-02 21:43:00 |
| 12010231146 | 1190Lineage | PBG11B |
| 12010231146 | 1190ReadTime | 2012-01-03 14:36:00 |
Once the data is in this format, then you can easily apply the PIVOT function to get your final result:
select *
from
(
select subid,
colname = cast(processNumber as varchar(10)) + colname,
value
from mytable
cross apply
(
values
('Lineage', Lineage),
('ReadTime', convert(varchar(20), readtime, 120))
) c (colname, value)
) d
pivot
(
max(value)
for colname in ([802Lineage], [802ReadTime],
[1000Lineage], [1000ReadTime],
[1190Lineage], [1190ReadTime])
) piv;
See SQL Fiddle with Demo.
The above works great if you have a limited number of rows that you want to convert, but if you have an unknown number then you can use dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(cast(processnumber as varchar(10))+col)
from mytable
cross apply
(
select 'Lineage', 0 union all
select 'ReadTime', 1
) c (col, so)
group by processnumber, col, so
order by processnumber, so
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT subid, ' + #cols + '
from
(
select subid,
colname = cast(processNumber as varchar(10)) + colname,
value
from mytable
cross apply
(
values
(''Lineage'', Lineage),
(''ReadTime'', convert(varchar(20), readtime, 120))
) c (colname, value)
) x
pivot
(
max(value)
for colname in (' + #cols + ')
) p '
execute sp_executesql #query;
See SQL Fiddle with Demo. This gives a result:
| SUBID | 802LINEAGE | 802READTIME | 1000LINEAGE | 1000READTIME | 1190LINEAGE | 1190READTIME | 1605LINEAGE | 1605READTIME | 1745LINEAGE | 1745READTIME | 1790LINEAGE | 1790READTIME | 1990LINEAGE | 1990READTIME | 2690LINEAGE | 2690READTIME | 2795LINEAGE | 2795READTIME | 2990LINEAGE | 2990READTIME | 3090LINEAGE | 3090READTIME | 3290LINEAGE | 3290READTIME |
|-------------|------------|---------------------|-------------|---------------------|-------------|---------------------|-------------|---------------------|-------------|---------------------|-------------|---------------------|-------------|---------------------|-------------|---------------------|-------------|---------------------|-------------|---------------------|-------------|---------------------|-------------|---------------------|
| 12010231146 | PBG12A | 2012-01-02 21:44:00 | PBG12A | 2012-01-02 21:43:00 | PBG11B | 2012-01-03 14:36:00 | PBG11B | 2012-01-03 15:15:00 | PBG11A | 2012-01-03 15:16:00 | PBG11A | 2012-01-03 15:19:00 | PBG11A | 2012-01-03 15:23:00 | PBG11A | 2012-01-03 15:32:00 | PBG11A | 2012-01-03 15:39:00 | PBG11A | 2012-01-03 15:41:00 | PBG11A | 2012-01-03 15:46:00 | PBG11A | 2012-01-03 15:47:00 |