PIVOT entire column set by group - sql

For a table like:
COL1 COL2 COL3 COL4
item1 7/29/13 cat blue
item3 7/29/13 fish purple
item1 7/30/13 rat green
item2 7/30/13 bat grey
item3 7/30/13 bird orange
How would you PIVOT to get rows by COL2, all other columns repeated across as blocks by COL1 values?
COL2 COL1 COL3 COL4 COL1 COL3 COL4 COL1 COL3 COL4
7/29/13 item1 cat blue item2 NULL NULL item3 fish purple
7/30/13 item1 rat green item2 bat grey item3 bird orange

In order to get this result you will need to do a few things:
get a distinct list of values from col1 and col2
unpivot the data in your columns col1, col3 and col4
pivot the result from the unpivot
To get the distinct list of dates and items (col1 and col2) along with the values from your existing table you will need to use something similar to the following:
select t.col1, t.col2,
t2.col3, t2.col4,
row_number() over(partition by t.col2
order by t.col1) seq
from
(
select distinct t.col1, c.col2
from yourtable t
cross join
(
select distinct col2
from yourtable
) c
) t
left join yourtable t2
on t.col1 = t2.col1
and t.col2 = t2.col2;
See SQL Fiddle with Demo. Once you have this list, then you will need to unpivot the data. There are several ways you can do this, using the UNPIVOT function or using CROSS APPLY:
select d.col2,
col = col+'_'+cast(seq as varchar(10)),
value
from
(
select t.col1, t.col2,
t2.col3, t2.col4,
row_number() over(partition by t.col2
order by t.col1) seq
from
(
select distinct t.col1, c.col2
from yourtable t
cross join
(
select distinct col2
from yourtable
) c
) t
left join yourtable t2
on t.col1 = t2.col1
and t.col2 = t2.col2
) d
cross apply
(
select 'col1', col1 union all
select 'col3', col3 union all
select 'col4', col4
) c (col, value);
See SQL Fiddle with Demo. this will give you data that looks like:
| COL2 | COL | VALUE |
-------------------------------------------------
| July, 29 2013 00:00:00+0000 | col1_1 | item1 |
| July, 29 2013 00:00:00+0000 | col3_1 | cat |
| July, 29 2013 00:00:00+0000 | col4_1 | blue |
| July, 29 2013 00:00:00+0000 | col1_2 | item2 |
| July, 29 2013 00:00:00+0000 | col3_2 | (null) |
| July, 29 2013 00:00:00+0000 | col4_2 | (null) |
Finally, you will apply the PIVOT function to the items in the col columns:
select col2,
col1_1, col3_1, col4_1,
col1_2, col3_2, col4_2,
col1_3, col3_3, col4_3
from
(
select d.col2,
col = col+'_'+cast(seq as varchar(10)),
value
from
(
select t.col1, t.col2,
t2.col3, t2.col4,
row_number() over(partition by t.col2
order by t.col1) seq
from
(
select distinct t.col1, c.col2
from yourtable t
cross join
(
select distinct col2
from yourtable
) c
) t
left join yourtable t2
on t.col1 = t2.col1
and t.col2 = t2.col2
) d
cross apply
(
select 'col1', col1 union all
select 'col3', col3 union all
select 'col4', col4
) c (col, value)
) src
pivot
(
max(value)
for col in (col1_1, col3_1, col4_1,
col1_2, col3_2, col4_2,
col1_3, col3_3, col4_3)
)piv;
See SQL Fiddle with Demo. If you have an unknown number of values, then you can use dynamic SQL to get the 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 col2
order by col1) seq
from yourtable
) t
cross apply
(
select 'col1', 1 union all
select 'col3', 2 union all
select 'col4', 3
) c (col, so)
group by col, seq, so
order by seq, so
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT col2, ' + #cols + '
from
(
select d.col2,
col = col+''_''+cast(seq as varchar(10)),
value
from
(
select t.col1, t.col2,
t2.col3, t2.col4,
row_number() over(partition by t.col2
order by t.col1) seq
from
(
select distinct t.col1, c.col2
from yourtable t
cross join
(
select distinct col2
from yourtable
) c
) t
left join yourtable t2
on t.col1 = t2.col1
and t.col2 = t2.col2
) d
cross apply
(
select ''col1'', col1 union all
select ''col3'', col3 union all
select ''col4'', col4
) c (col, value)
) x
pivot
(
max(value)
for col in (' + #cols + ')
) p '
execute sp_executesql #query;
See SQL Fiddle with Demo. All versions will give a result:
| COL2 | COL1_1 | COL3_1 | COL4_1 | COL1_2 | COL3_2 | COL4_2 | COL1_3 | COL3_3 | COL4_3 |
----------------------------------------------------------------------------------------------------------------
| July, 29 2013 00:00:00+0000 | item1 | cat | blue | item2 | (null) | (null) | item3 | fish | purple |
| July, 30 2013 00:00:00+0000 | item1 | rat | green | item2 | bat | grey | item3 | bird | orange |

The dynamic UNPIVOT+PIVOT method is always cool, when doing this sort of thing for a known and limited set of values subsequent JOIN's work nicely too (being lazy on the SELECT list):
WITH cte AS (SELECT *,ROW_NUMBER() OVER (PARTITION BY COL2 ORDER BY COL1)'RowRank'
FROM #Table1)
SELECT *
FROM cte a
LEFT JOIN cte b
ON a.COL2 = b.COL2
AND a.RowRank = b.RowRank - 1
LEFT JOIN cte c
ON b.COL2 = c.COL2
AND b.RowRank = c.RowRank - 1
WHERE a.RowRank = 1
Or if the order of the fields is to be maintained:
WITH cte AS (SELECT a.*,b.RowRank
FROM #Table1 a
JOIN (SELECT Col1,ROW_NUMBER() OVER (ORDER BY Col1)'RowRank'
FROM #Table1
GROUP BY COL1) b
ON a.Col1 = b.Col1)
SELECT *
FROM cte a
LEFT JOIN cte b
ON a.COL2 = b.COL2
AND a.RowRank = b.RowRank - 1
LEFT JOIN cte c
ON a.COL2 = c.COL2
AND a.RowRank = c.RowRank - 2
WHERE a.RowRank = 1
But this falls apart without an 'anchor' value, ie if no record had item1 for a given date it wouldn't be included.

Related

Get column name where values differ between two rows

I have a table with lots of columns. Sometimes I need to find differences between two rows. I can do it just by scrolling through screen but it is dull. I'm looking for a query that will do this for me, something like
SELECT columns_for_id_1 != columns_for_id_2
FROM xyz
WHERE id in (1,2)
Table:
id col1 col2 col3 col4
1 qqq www eee rrr
2 qqq www XXX rrr
Result:
"Different columns: id, col3"
Is there a simple way to do this?
UPDATE
Another example as wanted:
What I have (table has more than 50 column, not only 7):
Id| Col1 | Col2 | Col3 | Col4 | Col5 | Col6 |
==============================================
1 | aaa | bbb | ccc | ddd | eee | fff |
----------------------------------------------
2 | aaa | XXX | ccc | YYY | eee | fff |
Query:
SELECT *
FROM table
WHERE Id = 1 OR Id = 2
AND "columns value differs"
Query result: "Id, Col2, Col4"
OR something like:
Id|Col2 |Col4 |
===============
1 |bbb |ddd |
---------------
2 |XXX |YYY |
Right now I have to scroll through more than 50 columns to see if rows are the same, it's not efficient and prone to mistakes. I don't want any long query like
SELECT (COMPARE Id1.Col1 with Id2.Col1 if different then print "Col1 differs", COMPARE Id1.Col2 with Id2.Col2...) because I will compare the rows myself faster ;)
Something like this:
SELECT col, MIN(VAL) AS val1, MAX(val) AS val2
FROM (
SELECT id, val, col
FROM (
SELECT id, [col1], [col2], [col3], [col4]
FROM mytable
WHERE id IN (1,2)) AS src
UNPIVOT (
val FOR col IN ([col1], [col2], [col3], [col4])) AS unpvt ) AS t
GROUP BY col
HAVING MIN(val) <> MAX(val)
Output:
col val1 val2
================
col3 eee XXX
Try this simple query, may be help you
SELECT (CASE WHEN a.col1 <> b.col1 THEN 'Different Col1'
WHEN a.col2 <> b.col2 THEN 'Different Col2'
...
ELSE 'No Different' END) --You can add only required columns here
FROM xyz AS a
INNER JOIN xyz AS b ON b.id = 1 --First Record
WHERE a.id = 2 --Second record to compare
If you are on SQL Server 2012 then you can also use LEAD/LAG windowed funuction to do this. MSDN Reference is here - https://msdn.microsoft.com/en-us/library/hh213125.aspx
select
id,
col1,
col2,
col3,
col4,
stuff(diff_cols,len(diff_cols-1),1,'') diff_cols
from
(
SELECT
id,
col1,
col2,
col3,
col4,
concat
(
'Different columns:',
CASE
WHEN LEAD(id, 1,0) OVER (ORDER BY id) <> id THEN 'id,'
WHEN LEAD(col1, 1,0) OVER (ORDER BY id) <> col1 THEN 'col1,'
WHEN LEAD(col2, 1,0) OVER (ORDER BY id) <> col2 THEN 'col2,'
WHEN LEAD(col3, 1,0) OVER (ORDER BY id) <> col3 THEN 'col3,'
WHEN LEAD(col4, 1,0) OVER (ORDER BY id) <> col4 THEN 'col4,'
) diff_cols
FROM xyz
) tmp

Query to get previous value

I have a scenerio where I need previous column value but it should not be same as current column value.
Table A:
+------+------+-------------+
| Col1 | Col2 | Lead_Col2 |
+------+------+-------------+
| 1 | A | NULL |
| 2 | B | A |
| 3 | B | A |
| 4 | C | B |
| 5 | C | B |
| 6 | C | B |
| 7 | D | C |
+------+------+-------------+
As Given above, I need previuos column(Col2) value. which is not same as current value.
Try:
select *
from (select col1,
col2,
lag(col2, 1) over(order by col1) as prev_col2
from table_a)
where col2 <> prev_col2
The name lead_col2 is misleading, because you really want a lag.
Here is a brute force method that uses a correlated subquery to get the index of the value and then joins the value in:
select aa.col1, aa.col2, aa.col2
from (select col1, col2,
(select max(col1) as maxcol1
from a a2
where a2.id < a.id and a2.col2 <> a.col2
) as prev_col1
from a
) aa left join
a
on aa.maxcol1 = a.col1
EDIT:
You can also use logic with lead() and ignore NULLs. If a value is the last in its sequence, then use that value, otherwise set it to NULL. Then use lag() with ignoreNULL`s:
select col1, col2,
lag(col3) over (order by col1 ignore nulls)
from (select col1, col2,
(case when col2 <> lead(col2) over (order by col1) then col2
end) as col3
from a
) a;
Try this:
select t.col1
,t.col2
,first_value(lag_col2) over (partition by col2 order by ord) lag_col2
from (select t.*
,case when lag_col2 = col2 then 1 else 0 end ord
from (select t.*
,lag (col2) over (order by col1) lag_col2
from table1 t
)t
)t
order by col1
SQL Fiddle

How to remove null values and get row from values from table

Please help me with the task below. I have table a with four columns
col1,col2,col3 and col4. I want to retrieve from these columns, removing nulls.
So, if my table has
col1 | col2 | col3 | col4
-----+------+------+-----
A | B | NULL| NULL
C | D | NULL| NULL
NULL | NULL | E | F
NULL | NULL | G | H
I want result to be
col1 | col2 | col3 | col4
-----+------+------+-----
A | B | E | F
C | D | G | H
Here is a solution. I have used the analytic ROW_NUMBER() to synthesize a key for joining the rows. The join is full outer in order to cater for unequal assignments of nulls and values.
with cte as (select * from t23)
, a as ( select col1, row_number() over (order by col1) as rn
from cte
where col1 is not null )
, b as ( select col2, row_number() over (order by col2) as rn
from cte
where col2 is not null )
, c as ( select col3, row_number() over (order by col3) as rn
from cte
where col3 is not null )
, d as ( select col4, row_number() over (order by col4) as rn
from cte
where col4 is not null )
select a.col1
, b.col2
, c.col3
, d.col4
from a
full outer join b
on a.rn = b.rn
full outer join c
on a.rn = c.rn
full outer join d
on a.rn = d.rn
/
The SQL Fiddle is for Oracle, but this solution will work for any flavour of database which supports a ranking analytic function. The common table expression is optional, it just makes the other sub-queries easier to write.

Grouping multiple rows from a table into column

I have two table as below.
Table 1
+------+------+------+------+
| Col1 | Col2 | Col3 | Col4 |
+------+------+------+------+
| 1 | 1.5 | 1.5 | 2.5 |
| 1 | 2.5 | 3.5 | 1.5 |
+------+------+------+------+
Table 2
+------+--------+
| Col1 | Col2 |
+------+--------+
| 1 | 12345 |
| 1 | 678910 |
+------+--------+
I want the result as below.
+------+------+------+------+-------+--------+
| Col1 | Col2 | Col3 | Col4 | Col5 | Col6 |
+------+------+------+------+-------+--------+
| 1 | 4 | 5 | 4 | 12345 | 678910 |
+------+------+------+------+-------+--------+
Here Col2, Col3 and Col4 is the aggregate of value from Col2,3,4 in Table 1. And rows from Table 2 are transposed to Columns in the result.
I use Oracle 11G and tried the PIVOT option. But I couldn't aggregate values from Column 2,3,4 in Table 1.
Is there any function available in Oracle which provides direct solution without any dirty work around?
Thanks in advance.
Since you will always have only 2 records in second table simple grouping and join will do.
Since I dont have tables I am using CTEs and Inline views
with cte1 as (
select 1 as col1 , 1.5 as col2 , 1.5 as col3, 2.5 as col4 from dual
union all
select 1 , 2.5 , 3.5 , 1.5 fom dual
) ,
cte2 as (
select 1 as col1 , 12345 as col2 fom dual
union all
select 1,678910 fom dual )
select* from(
(select col1,sum(col2) as col2 , sum(col3) as col3,sum(col4) as col4
from cte1 group by col1) as x
inner join
(select col1 ,min(col2) as col5 ,max(col2) as col from cte2
group by col1
) as y
on x.col1=y.col1)
with
mytab1 as (select col1, col2, col3, col4, 0 col5, 0 col6 from tab1),
mytab2 as
(
select
col1, 0 col2, 0 col3, 0 col4, "1_COL2" col5, "2_COL2" col6
from
(
select
row_number() over (partition by col1 order by rowid) rn, col1, col2
from
tab2
)
pivot
(
max(col2) col2
for rn in (1, 2)
)
)
select
col1,
sum(col2) col2,
sum(col3) col3,
sum(col4) col4,
sum(col5) col5,
sum(col6) col6
from
(
select * from mytab1 union all select * from mytab2
)
group by
col1
Hello You can use the below query
with t1 (col1,col2,col3,col4)
as
(
select 1,1.5,1.5,2.5 from dual
union
select 1,2.5,3.5,1.5 from dual
),
t2 (col1,col2)
as
(
select 1,12345 from dual
union
select 1,678910 from dual
)
select * from
(
select col1
,max(decode(col2,12345,12345)) as co5
,max(decode(col2,678910,678910)) as col6
from t2
group by col1
) a
inner join
(
select col1,sum(col2) as col2,sum(col3) as col3,sum(col4) as col4
from t1
group by col1
) b
on a.col1=b.col1
Pivot only the second table. You can then do GROUP BY on the nested UNION ALL between table1 (col5 and col6 are null for subsequent group by) and pivoted table2 (col2, col3, col4 are null for subsequent group by).

Oracle rows to columns

I have a table with following columns and 2 rows :
COL1,COL2,COL3,NAME,DATE
Value of COL1,COL2,COL3 in both rows are A,B,C. Value of NAME in 1st row is 'DEL' and 2nd row is 'LAP'. Value of DATE in 1st row is '11.12.13' and 2nd row is '13.11.13'.
Now i want a view with singlerow and following columns
COL1,COL2,COL3,DEL,LAP with values A,B,C,11.12.13,13.11.13.
Is that possible with pivot or any other function
thanks
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE table_name (COL1,COL2,COL3,N_NAME,D_DATE) AS
SELECT 'A','B','C', 'DEL', '11.12.13' FROM DUAL
UNION ALL SELECT 'A','B','C', 'LAP', '13.11.13' FROM DUAL
UNION ALL SELECT 'A','B','C', 'DEL', '12.12.13' FROM DUAL
UNION ALL SELECT 'A','B','C', 'LAP', '14.11.13' FROM DUAL;
Query 1:
If the combination of COL1, COL2, COL3 and N_Name is unique then you can do:
SELECT Col1,
Col2,
Col3,
MIN( CASE N_Name WHEN 'DEL' THEN D_Date END ) AS DEL,
MIN( CASE N_Name WHEN 'LAP' THEN D_Date END ) AS LAP
FROM table_name
GROUP BY
Col1,
Col2,
Col3
Results:
| COL1 | COL2 | COL3 | DEL | LAP |
|------|------|------|----------|----------|
| A | B | C | 11.12.13 | 13.11.13 |
Query 2:
However, if you can have multiple rows with the same combination of COL1, COL2, COL3 and N_Name and you want all of them returning (in date order) then you can do:
WITH indexed_data AS (
SELECT Col1,
Col2,
Col3,
N_Name,
D_Date,
ROW_NUMBER() OVER ( PARTITION BY Col1, Col2, Col3, N_Name ORDER BY D_Date ) AS idx
FROM table_name
)
SELECT Col1,
Col2,
Col3,
MIN( CASE N_Name WHEN 'DEL' THEN D_Date END ) AS DEL,
MIN( CASE N_Name WHEN 'LAP' THEN D_Date END ) AS LAP
FROM indexed_data
GROUP BY
Col1,
Col2,
Col3,
idx
ORDER BY
Col1,
Col2,
Col3,
idx
Results:
| COL1 | COL2 | COL3 | DEL | LAP |
|------|------|------|----------|----------|
| A | B | C | 11.12.13 | 13.11.13 |
| A | B | C | 12.12.13 | 14.11.13 |
with tab (COL1,COL2,COL3,N_NAME,D_DATE) as (
select 'A','B','C', 'DEL', '11.12.13' from dual union all
select 'A','B','C', 'LAP', '13.11.13' from dual)
select COL1, COL2, COL3,
min(decode(N_NAME, 'DEL', D_DATE, NULL)) DEL,
min(DECODE(N_NAME, 'LAP', D_DATE, NULL)) LAP
from tab
group by COL1, COL2, COL3
output
| COL1 | COL2 | COL3 | DEL | LAP |
|------|------|------|----------|----------|
| A | B | C | 11.12.13 | 13.11.13 |
Assuming that the common fields in both rows are COL1, COL2 and COL3.
SELECT a.col1,
a.col2,
a.col3,
a.date AS DEL,
b.date AS LAP
FROM yourtable AS a
INNER JOIN yourtable AS b
ON a.col1 = b.col1
AND a.col2 = b.col2
AND a.col3 = b.col3
AND a.name = 'DEL'
AND b.name = 'LAP'