Conditional transpose columns into rows in oracle sql - sql

I have this row in my table mtinre:
INREPRCO INRESELO INRECAPI INRECFRA INRECAPO
---------- ---------- ---------- ---------- ----------
32.42 1.87 1 5
I build a query to transpose this row as 5 different rows for each column.
SELECT CASE pivot
WHEN 1 THEN 'VAPRS'
WHEN 2 THEN 'VAFRC'
WHEN 3 THEN 'VACTA'
WHEN 4 THEN 'VIMSL'
WHEN 5 THEN 'VINEM'
END
component,
CASE pivot
WHEN 1 THEN inreprco
WHEN 2 THEN inrecfra
WHEN 3 THEN inrecapo
WHEN 4 THEN inreselo
WHEN 5 THEN inreinem
ELSE NULL
END
VALUE,
CASE pivot
WHEN 4
THEN
(NVL (inreprco, 0) + NVL (inrecfra, 0) + NVL (inrecapo, 0))
WHEN 5
THEN
(NVL (inreprco, 0) + NVL (inrecfra, 0) + NVL (inrecapo, 0))
ELSE
NULL
END
AS base
FROM mtinre,
( SELECT ROWNUM pivot
FROM DUAL
CONNECT BY LEVEL <= 5)
The output is:
COMPONENT VALUE BASE
--------- ---------- ----------
VAPRS 32.42
VAFRC
VACTA 5
VIMSL 1.87 37.42
VINEM .94 37.42
But these 5 fields(INREPRCO,INRESELO,INRECAPI,INRECFRA,INRECAPO) can have null or zero(0) values. So I need to select only those how have values.
In the last example, just show me:
COMPONENT VALUE BASE
--------- ---------- ----------
VAPRS 32.42
VACTA 5
VIMSL 1.87 37.42
VINEM .94 37.42
I've tried to put some where conditions, but the connect by level statement creates me always 5 rows.
So, I changed my query and made this:
SELECT *
FROM (SELECT CASE pivot
WHEN 1 THEN 'VAPRS'
WHEN 2 THEN 'VAFRC'
WHEN 3 THEN 'VACTA'
WHEN 4 THEN 'VIMSL'
WHEN 5 THEN 'VINEM'
END
component,
CASE pivot
WHEN 1 THEN inreprco
WHEN 2 THEN inrecfra
WHEN 3 THEN inrecapo
WHEN 4 THEN inreselo
WHEN 5 THEN inreinem
ELSE NULL
END
VALUE,
CASE pivot
WHEN 4
THEN
( NVL (inreprco, 0)
+ NVL (inrecfra, 0)
+ NVL (inrecapo, 0))
WHEN 5
THEN
( NVL (inreprco, 0)
+ NVL (inrecfra, 0)
+ NVL (inrecapo, 0))
ELSE
NULL
END
AS base
FROM mtinre,
( SELECT ROWNUM pivot
FROM DUAL
CONNECT BY LEVEL <= 5))
WHERE VALUE IS NOT NULL
It works, but is there any other way to do that without using a sub select statement?
Any suggestion?
Thanks
Filipe

Using UNPIVOT and a little trick can do the job. Almost all tables have an id column (primary or unique key). Assuming that the table has id_col as id column, this query will do the job
SQL> WITH table_(id_col, inreprco,inreselo,inrecapi,inrecfra,inrecapo) AS
2 (SELECT 1, 32.42,1.87,0.94,NULL,5 FROM dual UNION ALL
3 SELECT 2, 33.43,2.87,0.87,12,9 FROM dual ),
4 ---------
5 -- End of data preparation
6 ---------
7 table2_ AS (SELECT id_col, component, VALUE
8 FROM table_
9 UNPIVOT (VALUE FOR component IN (inreprco AS 'VAPRS', inrecfra AS 'VAFRC', inrecapo AS 'VACTA', inreselo AS 'VIMSL', inrecapi AS 'VINEM')))
10 select a.id_col,
11 a.COMPONENT,
12 a.VALUE,
13 CASE WHEN a.component IN ('VIMSL', 'VINEM') THEN nvl(b.inreprco, 0) + nvl(b.inrecfra, 0) + NVL(b.inrecapo, 0) ELSE NULL END AS base
14 FROM table2_ a
15 INNER JOIN table_ b
16 ON b.id_col = a.id_col;
ID_COL COMPONENT VALUE BASE
---------- --------- ---------- ----------
1 VAPRS 32.42
1 VACTA 5
1 VIMSL 1.87 37.42
1 VINEM 0.94 37.42
2 VAPRS 33.43
2 VAFRC 12
2 VACTA 9
2 VIMSL 2.87 54.43
2 VINEM 0.87 54.43
9 rows selected
But if there are no Id column, then modifying the join as cross join will do but that will return correct result if there is only one row in the table.
SQL> WITH table_(inreprco,inreselo,inrecapi,inrecfra,inrecapo) AS
2 (SELECT 32.42,1.87,0.94,NULL,5 FROM dual),
3 ---------
4 -- End of data preparation
5 ---------
6 table2_ AS (SELECT component, VALUE
7 FROM table_
8 UNPIVOT (VALUE FOR component IN (inreprco AS 'VAPRS', inrecfra AS 'VAFRC', inrecapo AS 'VACTA', inreselo AS 'VIMSL', inrecapi AS 'VINEM')))
9 select a.COMPONENT,
10 a.VALUE,
11 CASE WHEN a.component IN ('VIMSL', 'VINEM') THEN nvl(b.inreprco, 0) + nvl(b.inrecfra, 0) + NVL(b.inrecapo, 0) ELSE NULL END AS base
12 FROM table2_ a
13 CROSS JOIN table_ b
14 /
COMPONENT VALUE BASE
--------- ---------- ----------
VAPRS 32.42
VACTA 5
VIMSL 1.87 37.42
VINEM 0.94 37.42
Or wait for someone who comes with some other approach ;)

Related

What is the most efficient SQL query to find the max N values for every entities in a table

I wrote these 2 queries, the first one is keeping duplicates and the second one is dropping them
Does anyone know a more efficient way to achieve this?
Queries are for MSSQL, returning the top 3 values
1-
SELECT TMP.entity_id, TMP.value
FROM(
SELECT TAB.entity_id, LEAD(TAB.entity_id, 3, 0) OVER(ORDER BY TAB.entity_id, TAB.value) AS next_id, TAB.value
FROM mytable TAB
) TMP
WHERE TMP.entity_id <> TMP.next_id
2-
SELECT TMP.entity_id, TMP.value
FROM(
SELECT TMX.entity_id, LEAD(TMX.entity_id, 3, 0) OVER(ORDER BY TMX.entity_id, TMX.value) AS next_id, TMX.value
FROM(
SELECT TAB.entity_id, LEAD(TAB.entity_id, 1, 0) OVER(ORDER BY TAB.entity_id, TAB.value) AS next_id, TAB.value, LEAD(TAB.value, 1, 0) OVER(ORDER BY TAB.entity_id, TAB.value) AS next_value
FROM mytable TAB
) TMX
WHERE TMP.entity_id <> TMP.next_id OR TMX.value <> TMX.next_value
) TMP
WHERE TMP.entity_id <> TMP.next_id
Example:
Table:
entity_id value
--------- -----
1 9
1 11
1 12
1 3
2 25
2 25
2 5
2 37
3 24
3 9
3 2
3 15
Result Query 1 (25 appears twice for entity_id 2):
entity_id value
--------- -----
1 9
1 11
1 12
2 25
2 25
2 37
3 9
3 15
3 24
Result Query 2 (25 appears only once for entity_id 2):
entity_id value
--------- -----
1 9
1 11
1 12
2 5
2 25
2 37
3 9
3 15
3 24
You can use the ROW_NUMBER which will allow duplicates as follows:
select entity_id, value from
(select t.*, row_number() over (partition by entity_id order by value desc) as rn
from your_Table) where rn <= 3
You can use the rank to remove the duplicate as follows:
select distinct entity_id, value from
(select t.*, rank() over (partition by entity_id order by value desc) as rn
from your_Table) where rn <= 3

SQL for a generated table with column 1 a sequence of numbers and column 2 a running sum

What would be the SQL (standard, or any major variant) to produce a table like the following?
1 1 -- 1
2 3 -- 2+1
3 6 -- 3+2+1
4 10 -- 4+3+2+1
5 15 -- 5+4+3+2+1
6 21 -- 6+5+4+3+2+1
... ...
The second column is the sum of the numbers in the first.
I couldn't get past this:
select rownum from all_objects where rownum <= 10;
Which produces column 1 (PL/SQL)
Tried to think on the following lines but clearly it is wrong, even syntactically:
select rownum, count(t2.rownum)
from
(select sum(rownum) from all_objects where rownum <= 10) t2,
all_objects
where rownum <= 10;
It is simple math:
select rownum, rownum * (rownum + 1) / 2
from all_objects
where rownum <= 10;
You don't need to hit the all_objects view; you could use a hierarchical query:
select level as position, sum(level) over (order by level) as running_sum
from dual
connect by level <= 10;
POSITION RUNNING_SUM
---------- -----------
1 1
2 3
3 6
4 10
5 15
6 21
7 28
8 36
9 45
10 55
or using #forpas' arithmetic-series method:
select level as position, level * (level + 1) / 2 as running_sum
from dual
connect by level <= 10;
POSITION RUNNING_SUM
---------- -----------
1 1
2 3
3 6
4 10
5 15
6 21
7 28
8 36
9 45
10 55
Or recursive subquery factoring (11gR2+):
with rcte (position, running_sum) as (
select 1, 1 from dual
union all
select position + 1, running_sum + position + 1
from rcte
where position < 10
)
select * from rcte
order by position;
POSITION RUNNING_SUM
---------- -----------
1 1
2 3
3 6
4 10
5 15
6 21
7 28
8 36
9 45
10 55
You are looking for a cumulative sum:
select rownum, sum(rownum) over (order by rownum)
from all_objects
where rownum <= 10;
I wasn't sure this would actually work on rownum, but it does.

How to select ranges in a range of record in oracle

If I have a table like this
Number Status
------ ------
1 A
2 A
3 A
4 U
5 U
6 A
7 U
8 U
9 A
10 A
What query can I use to group the range into ranges where Status = A?
Range Count Status
----- ----- ------
1-3 3 A
6-6 1 A
9-10 2 A
My query is
select min(number) || '--' || max(number), count(*), Status
from table
where Status = 'A'
group by Status
Range Count Status
----- ----- ------
1-10 6 A
This is a nice way, fancy name "Tabibitosan method" given by Aketi Jyuuzou.
SQL> WITH data AS
2 (SELECT num - DENSE_RANK() OVER(PARTITION BY status ORDER BY num) grp,
3 status,
4 num
5 FROM t
6 )
7 SELECT MIN(num)
8 ||' - '
9 || MAX(num) range,
10 COUNT(*) cnt
11 FROM data
12 WHERE status='A'
13 GROUP BY grp
14 ORDER BY grp
15 /
RANGE CNT
------ ----------
1 - 3 3
6 - 6 1
9 - 10 2
SQL>
Note It is better to use DENSE_RANK to avoid duplicates.
Table
SQL> SELECT * FROM t ORDER BY num;
NUM S
---------- -
1 A
1 A
2 A
2 A
3 A
4 U
5 U
6 A
7 U
8 U
9 A
NUM S
---------- -
10 A
12 rows selected.
There are duplicates for num = 1.
Using DENSE_RANK:
SQL> WITH data AS
2 (SELECT num - DENSE_RANK() OVER(PARTITION BY status ORDER BY num) grp,
3 status,
4 num
5 FROM t
6 )
7 SELECT MIN(num)
8 ||' - '
9 || MAX(num) range,
10 COUNT(*) cnt
11 FROM data
12 WHERE status='A'
13 GROUP BY grp
14 ORDER BY grp
15 /
RANGE CNT
------ ----------
1 - 3 5
6 - 6 1
9 - 10 2
SQL>
Using ROW_NUMBER:
SQL> WITH DATA AS
2 (SELECT num - ROW_NUMBER() OVER(PARTITION BY status ORDER BY num) grp,
3 status,
4 num
5 FROM t
6 )
7 SELECT MIN(num)
8 ||' - '
9 || MAX(num) range,
10 COUNT(*) cnt
11 FROM data
12 WHERE status='A'
13 GROUP BY grp
14 ORDER BY grp
15 /
RANGE CNT
------ ----------
2 - 3 2
1 - 2 2
1 - 6 2
9 - 10 2
SQL>
So, in case of duplicates, the ROW_NUMBER query would give incorrect results. You should use DENSE_RANK.
SQL Fiddle
Oracle 11g R2 Schema Setup:
create table x(
num_ number,
status_ varchar2(1)
);
insert into x values(1,'A');
insert into x values(2,'A');
insert into x values(3,'A');
insert into x values(4,'U');
insert into x values(5,'U');
insert into x values(6,'A');
insert into x values(7,'U');
insert into x values(8,'U');
insert into x values(9,'A');
insert into x values(10,'A');
Query 1:
select min(num_) || '-' || max(num_) range_, status_,
count(1) count_
from
(
select num_, status_,
num_ - row_number() over (order by status_, num_) y --gives a group number to each groups, which have same status over consecutive records.
from x
)
where status_ = 'A'
group by y, status_
order by range_
Results:
| RANGE_ | STATUS_ | COUNT_ |
|--------|---------|--------|
| 1-3 | A | 3 |
| 6-6 | A | 1 |
| 9-10 | A | 2 |

Select Data based on Sum of another columns value

I have a Table with Data as
RowIndex Id TicketCount
1 23 1
2 25 2
3 3 1
4 14 1
5 16 1
6 18 1
7 1 1
8 6 1
9 15 1 ===> at this row the sum of Ticket Count is 10
10 22 1
11 27 1
12 24 1
13 26 2
14 9 1
15 19 1
From this Data I want to Select All Records where The Sum of Ticket Count will be equal to 10(user input value)
In the Given data I want to Select all Records till Row Index 9.
Output should be:
RowIndex Id TicketCount
1 23 1
2 25 2
3 3 1
4 14 1
5 16 1
6 18 1
7 1 1
8 6 1
9 15 1
SQL Server 2008 doesn't have the cumulative sum function. I implement it using a correlated subquery:
select RowIndex, Id, TicketCount
from (select t.*,
(select sum(TicketCount)
from t t2
where t2.RowIndex <= t.RowIndex
) as cumTicketCount
from t
) t
where cumTicketCount <= 10;
In SQL Server 2012, you can phrase this using a window function:
select RowIndex, Id, TicketCount
from (select t.*, sum(TicketCount) over (order by RowIndex) as CumTicketCount
from t
) t
where cumTicketCount <= 10;
You can do it using recursive CTE:
WITH RCTE AS
(
SELECT *, TicketCount AS Total
FROM Table1
WHERE RowIndex = 1
UNION ALL
SELECT t.*, r.Total + t.TicketCount
FROM RCTE r
INNER JOIN Table1 t ON r.RowIndex + 1 = t.RowIndex
WHERE r.Total + t.TicketCount <= 10 --your input value
)
SELECT * FROM RCTE
SQLFiddle DEMO

Convert row to column using sql server 2008?

Table name is Looupvalue
id Ptypefield Value
1 1 D
2 1 E
3 1 F
4 1 G
5 1 H
6 2 FL
7 2 IF
8 2 VVS1
9 2 VVS2
10 2 VS1
11 2 VS2
12 3 0.50
13 3 1.00
14 3 1.50
15 3 2.00
16 4 Marquise
17 4 Round
18 4 Pear
19 4 Radiant
20 4 Princess
Lookupvalue table value convert roow to column depends on ptypefield
Like
id 1 id 2 id 3 id 4
1 D 6 fl 12 0.50 16 Marquise
2 E 7 If 13 1 17 Round....
3 F 8 vvs2 14 1.5
4 G 9 vvs2 15 2
5 H 10 vs1
11 vs2
Thanks
In your sample output, it is not clear why values from columns 1 and 2 would be related to columns 3 and 4. However, here is a possible solution:
;With RowNumbers As
(
Select Id, PTypeField, Value
, Row_Number() Over( Partition By PTypeField Order By Id ) As Rownum
From #Test
)
Select RowNum
, Min( Case When PTypeField = 1 Then Id End ) As Id
, Min( Case When PTypeField = 1 Then Value End ) As [1]
, Min( Case When PTypeField = 2 Then Id End ) As Id
, Min( Case When PTypeField = 2 Then Value End ) As [2]
, Min( Case When PTypeField = 3 Then Id End ) As Id
, Min( Case When PTypeField = 3 Then Value End ) As [3]
, Min( Case When PTypeField = 4 Then Id End ) As Id
, Min( Case When PTypeField = 4 Then Value End ) As [4]
From RowNumbers
Group By RowNum
If you wanted to dynamically generate the columns, the only way to do that in SQL is to use some fugly dynamic SQL. T-SQL was not designed for this sort of output and instead you should use a reporting tool or do the crosstabbing in a middle tier component or class.
This data schema looks like an EAV which would explain why retrieving the data you want is so difficult.