Reputation: 109
I have the following row in a table:
C1 |C2 |C3 |C4
7 | 3 | 1 | 6
I would like to construct a query in such a way that an order is given to each column;
O1 |O2 |O3 |O4
4 | 2 | 1 | 3
Is it possible to do this kind of row-by-row comparisons in a single query? Or is the only option to construct a complex case when statement?
Edit: the case when I am trying to bypass:
case
when C1 = greatest ( C1, C2, C3, C4) then 1
when C1 >= C2 and C1 >= C3 and C1 < C4
or C1 >= C2 and C1 < C3 and C1 >= C4
or C1 < C2 and C1 >= C3 and C1 >= C4 then 2
when C1 >= C2 and C1 < C3 and C1 < C4
or C1 < C2 and C1 >= C3 and C1 < C4
or C1 < C2 and C1 < C3 and C1 >= C4 then 3
when C1 = least (C1, C2, C3, C4 ) then 4
end as O1
In case of equal values, the index determines the order: if C2 = C3, O2 = 1, O2 = 3.
As you can image, this is quite prone to errors. Is there a way to do this comparison more elegant?
The comparison only needs to happen in a single row, the ordering of the columns in a single does not influence the order of the rows in the table.
Edit 2: there are multiple rows in the table, identified with ID_ROW.
Upvotes: 1
Views: 1424
Reputation: 65288
I think you mean using unpivot
and pivot
consecutively :
create table mytable(C1 int, C2 int, C3 int, C4 int );
insert into mytable values(7, 3, 1, 6);
select * from
(
select CX variable, dense_rank() over ( order by value ) value
from mytable
unpivot (value for CX in (C1, C2, C3, C4))
)
pivot
(
max(value) as vl for(variable) in ('C1' AS C1, 'C2' AS C2, 'C3' AS C3, 'C4' AS C4 )
);
C1_VL C2_VL C3_VL C4_VL
4 2 1 3
Upvotes: 1
Reputation:
One way is to unpivot the data, use the ROW_NUMBER() analytic function and re-pivot. This approach may take longer than working directly on each row, but is much easier to maintain. You will need to decide which goal is more important.
EDIT - based on the desired handling of ties (which is still not clear), perhaps ROW_NUMBER() should be replaced with either RANK() or DENSE_RANK(); othewise the solution is the same. END EDIT
create table inputs ( id_row, c1, c2, c3, c4 ) as
select 101, 7, 3, 1, 6 from dual union all
select 102, 1, 5, 5, 2 from dual union all
select 103, 0, 0, 0, 0 from dual union all
select 104, 8, 3, 4, 1 from dual
;
select id_row,
c1_val as c1, c2_val as c2, c3_val as c3, c4_val as c4,
c1_rn as o1, c2_rn as o2, c3_rn as o3, c4_rn as o4
from (
select id_row, val, col,
row_number() over
(partition by id_row order by val, col) as rn
from inputs
unpivot ( val for col in (c1 as 1, c2 as 2, c3 as 3, c4 as 4) )
)
pivot ( min(val) as val, min(rn) as rn
for col in (1 as c1, 2 as c2, 3 as c3, 4 as c4) )
;
ID_ROW C1 C2 C3 C4 O1 O2 O3 O4
-------- ------ ------ ------ ------ ------ ------ ------ ------
101 7 3 1 6 4 2 1 3
102 1 5 5 2 1 3 4 2
103 0 0 0 0 1 2 3 4
104 8 3 4 1 4 2 3 1
Upvotes: 1
Reputation: 36107
Complex nested case-when is not necessary, you can use a "simple" case-when with a sum (although a bit tedious)
select t.*,
case when c1>c2 then 1 else 0 end
+ case when c1>c3 then 1 else 0 end
+ case when c1>c4 then 1 else 0 end + 1 as q1,
case when c2>c1 then 1 else 0 end
+ case when c2>c3 then 1 else 0 end
+ case when c2>c4 then 1 else 0 end + 1 as q2,
case when c3>c1 then 1 else 0 end
+ case when c3>c2 then 1 else 0 end
+ case when c3>c4 then 1 else 0 end + 1 as q3 ,
case when c4>c1 then 1 else 0 end
+ case when c4>c2 then 1 else 0 end
+ case when c4>c3 then 1 else 0 end + 1 as q4
FROM table1 t;
| c1 | c2 | c3 | c4 | q1 | q2 | q3 | q4 |
|----|----|----|----|----|----|----|----|
| 7 | 3 | 1 | 6 | 4 | 2 | 1 | 3 |
| 6 | 5 | 4 | 1 | 4 | 3 | 2 | 1 |
Upvotes: 3