How to convert rows into columns in oracle into particular set of columns? - sql

I have a table in which there are 2 columns. I want to convert the rows into sets of 4 columns.
For example, the table I have:
Column1
Column2
1
N
2
N
3
N
4
N
5
N
I want to transpose them as:
Column1
Columnn2
Column3
Column4
1
2
3
4
5
N
N
N
N
N
How can I do this in Oracle?
Thanks for the help!

Here's one option; read comments within code.
SQL> with
2 test (col1, col2) as
3 -- sample data
4 (select 1, 'N' from dual union all
5 select 2, 'N' from dual union all
6 select 3, 'N' from dual union all
7 select 4, 'N' from dual union all
8 select 5, 'N' from dual
9 ),
10 temp as
11 -- union of two columns will produce a single-column "table"
12 (select to_char(col1) col from test union all
13 select col2 from test
14 ),
15 temp2 as
16 -- ordinal numbers for each row
17 (select col,
18 row_number() over (order by null) rn
19 from temp
20 ),
21 temp3 as
22 -- a little bit of calculation so that you'd be able to create groups of 4 columns
23 (select col,
24 mod(rn, 4) rn,
25 ceil(rn / 4) grp
26 from temp2
27 )
28 -- final query
29 select max(case when rn = 1 then col end) col1,
30 max(case when rn = 2 then col end) col2,
31 max(case when rn = 3 then col end) col3,
32 max(case when rn = 0 then col end) col4
33 from temp3
34 group by grp;
COL1 COL2 COL3 COL4
----- ----- ----- -----
1 2 3 4
5 N N N
N N
SQL>

Related

Compare before column in before row with next column in next row

My code is :
with x as
(
select 1 col from dual union all
select 2 col from dual union all
select 8 col from dual union all
select 4 col from dual union all
select 3 col from dual union all
select 2 col from dual
)
select col col1, col col2, col col3, rownum
from x
where col2.ROWNUM > col1.ROWNUM -1
and col2.ROWNUM > col3ROWNUM +1 ;
I want to compare col2.ROWNUM > col1.ROWNUM -1 and col2.ROWNUM > col3ROWNUM + 1 but that doesn't work and I got an error
ORA-01747: invalid user.table.column, table.column, or column specification
01747. 00000 - "invalid user.table.column, table.column, or column specification"
*Cause:
*Action:
Error at Line: 10 Column: 13
Please help me
It looks you got something wrong.
Result of that CTE is a single-column table whose only column is named col. There are no other columns.
SQL> with x as (
2 select 1 col from dual union all --> in UNION, all columns are
3 select 2 col from dual union all named by column name(s) from the
4 select 8 col from dual union all first SELECT statement
5 select 4 col from dual union all
6 select 3 col from dual union all
7 select 2 col from dual)
8 select x.*, rownum
9 from x;
COL ROWNUM
---------- ----------
1 1
2 2
8 3
4 4
3 5
2 6
6 rows selected.
SQL>
Therefore, where clause you wrote doesn't make any sense. Perhaps you should explain what you really have, rules that should be applied to source data and result you'd like to get.
Based on text you put into the title:
compare before column in before row with next column in next row
maybe you'd be interested in lag and lead analytic functions which then let you compare values in adjacent rows (pay attention to NULL values; I didn't). For example:
SQL> with x as (
2 select 1 col from dual union all
3 select 2 col from dual union all
4 select 8 col from dual union all
5 select 4 col from dual union all
6 select 3 col from dual union all
7 select 2 col from dual
8 ),
9 temp as
10 (select col,
11 rownum as rn
12 from x
13 ),
14 temp2 as
15 (select
16 rn,
17 col as this_row,
18 lag(col) over (order by rn) as previous_row,
19 lead(col) over (order by rn) as next_row
20 from temp
21 )
22 select this_row,
23 previous_row,
24 next_row,
25 --
26 case when this_row < previous_row then 'This < previous'
27 when this_row < next_row then 'This < next'
28 else 'something else'
29 end as result
30 from temp2
31 order by rn;
Result:
THIS_ROW PREVIOUS_ROW NEXT_ROW RESULT
---------- ------------ ---------- ---------------
1 2 This < next
2 1 8 This < next
8 2 4 something else
4 8 3 This < previous
3 4 2 This < previous
2 3 This < previous
6 rows selected.
SQL>
Use lead or lag functions. But, please, do not use rownum for such purposes.
Rownum indicates simply the order in which a row was found in the database and cannot be used for other purposes except limiting the number of rows fetched, like where rownum<=1 to be certain you won't get a too_many_rows exception, for instance. Still, if in a query you do fetch the pseud-column rownum, give it an alias so that you may use that value later on.
Moreover, what is supposed to mean col2.ROWNUM or col1.ROWNUM? That is not clear. col1 and col2 are two columns, which do not have the attribute rownum.
Something that may help in the future for analytic queries:
https://oracle-base.com/articles/misc/lag-lead-analytic-functions
And, if you wish to get a working SQL, please explain clearly what you wish to achieve, for I haven't really understood what that code is intended to do.
A way you may use rownum without getting errors:
with x as (
select 1 col from dual union all
select 2 col from dual union all
select 8 col from dual union all
select 4 col from dual union all
select 3 col from dual union all
select 2 col from dual)
,x2 as (
select col col1 ,col col2, col col3 ,rownum rn
from x
)
select *
from x2
where rn between 2 and 3 --- rownum cannot be used in such a
condition!!!
;
Or, to be certain you get only the first row from a table satisfying a given condition:
select x_col1, x_col2 into v_col1, v_col2
from x_table
where ... --- logical conditions
and rownum<=1; --- rownum <= 1 avoids too_many_rows_exception if several rows satisfy the logical conditions given before
In Oracle, results sets have a non-deterministic order (i.e. they are unordered) unless you use an ORDER BY clause. Therefore, if you have a physical table, you need another column to provide the order (rather than relying on the ROWNUM pseudo-column, which may result in unexpected behaviour):
CREATE TABLE x (order_id, col) AS
SELECT 1, 1 FROM DUAL UNION ALL
SELECT 2, 2 FROM DUAL UNION ALL
SELECT 3, 8 FROM DUAL UNION ALL
SELECT 4, 4 FROM DUAL UNION ALL
SELECT 5, 3 FROM DUAL UNION ALL
SELECT 6, 2 FROM DUAL;
If you want to find the rows that go up in succession, then you can use MATCH_RECOGNIZE for row-by-row pattern matching:
SELECT *
FROM x
MATCH_RECOGNIZE(
ORDER BY order_id
MEASURES
any_row.col AS col1,
FIRST(up.col) AS col2,
LAST(up.col) AS col3,
FIRST(order_id) AS start_order_id
PATTERN ( any_row up{2} )
DEFINE up AS ( col > PREV(col) )
)
or the LEAD analytic function:
SELECT *
FROM (
SELECT col AS col1,
LEAD(col, 1) OVER (ORDER BY order_id) AS col2,
LEAD(col, 2) OVER (ORDER BY order_id) AS col3,
order_id
FROM x
)
WHERE col2 > col1
AND col3 > col2;
Which both output:
COL1
COL2
COL3
START_ORDER_ID
1
2
8
1
fiddle
It looks like you want to find the rows where the value of the column is bigger than it is in both - the previous and next row. If so, you could try this:
WITH
tbl (ID, COL) AS -- Sample data (ID column is just to preserve order of the rows)
(
Select 1, 1 From Dual Union All
Select 2, 2 From Dual Union All
Select 3, 8 From Dual Union All
Select 4, 4 From Dual Union All
Select 5, 3 From Dual Union All
Select 6, 2 From DUAL
)
Select ID, COL, CASE WHEN COL > LAG(COL, 1) OVER(Order By ID) And COL > LEAD(COL, 1) OVER(Order By ID) THEN 'YES' END "BIGGER_THAN_PREV_AND_NEXT"
From tbl
Order By ID
ID COL BIGGER_THAN_PREV_AND_NEXT
---------- ---------- -------------------------
1 1
2 2
3 8 YES
4 4
5 3
6 2
... with a bit different sample data this will find the other row(s) that satisfy the condition ...
WITH
tbl (ID, COL) AS -- Sample data (ID column is just to preserve order of the rows)
(
Select 1, 1 From Dual Union All
Select 2, 2 From Dual Union All
Select 3, 8 From Dual Union All
Select 4, 4 From Dual Union All
Select 5, 5 From Dual Union All -- value of COL changed from 3 to 5
Select 6, 2 From DUAL
)
Select ID, COL, CASE WHEN COL > LAG(COL, 1) OVER(Order By ID) And COL > LEAD(COL, 1) OVER(Order By ID) THEN 'YES' END "BIGGER_THAN_PREV_AND_NEXT"
From tbl
Order By ID
ID COL BIGGER_THAN_PREV_AND_NEXT
---------- ---------- -------------------------
1 1
2 2
3 8 YES
4 4
5 5 YES
6 2
OR without ID - using ROWNUM (as in your question), - not adviseable, though...
WITH
tbl (COL) AS -- Sample data (without ID column)
(
Select 1 From Dual Union All
Select 2 From Dual Union All
Select 8 From Dual Union All
Select 4 From Dual Union All
Select 5 From Dual Union All
Select 2 From DUAL
)
Select COL, CASE WHEN COL > LAG(COL, 1) OVER(Order By ROWNUM) And COL > LEAD(COL, 1) OVER(Order By ROWNUM) THEN 'YES' END "BIGGER_THAN_PREV_AND_NEXT"
From tbl
COL BIGGER_THAN_PREV_AND_NEXT
---------- -------------------------
1
2
8 YES
4
5 YES
2
Any Order By clause added to the query could change the ROWNUM values and the result...

Oracle SQL - Create identifier for couple of values

I'm struggling with the following problem, I have the follwing data in a table:
Param ID
Param Val
Other Cols
1
15
XXX
1
15
XXX
1
16
XXX
1
16
XXX
2
21
XXX
2
21
XXX
2
22
XXX
2
22
XXX
I would like to select a new colum in order to create 4 sets of data to have all the possible combination between the values of parameter 1 and 2; so I would like to obtain something like this:
Set
Param ID
Param Val
Other Cols
1
1
15
XXX
2
1
15
XXX
3
1
16
XXX
4
1
16
XXX
1
2
21
XXX
3
2
21
XXX
2
2
22
XXX
4
2
22
XXX
So for example for the Set 1 I will have the Couple of values 15 and 21, for the set 2 the values 15 and 22 etc etc.
I tried using different analytic functions, but I was not able to have what I need.
Thanks in advance.
Despite a good hint on the MODEL clause, I guess I will go for a solution combining cross join and unpivot, maybe it is not the best, but it fit my needs.
WITH tbl AS
(
Select 1 "ID", 15 "VAL" From Dual Union All
Select 1 "ID", 16 "VAL" From Dual Union All
Select 2 "ID", 21 "VAL" From Dual Union All
Select 2 "ID", 22 "VAL" From Dual )
SELECT *
FROM (
SELECT ROWNUM AS SET_ID,
id1,
id4
FROM (
SELECT
CASE a.id
WHEN 1 THEN a.val
ELSE 0
END AS id1,
CASE a.id
WHEN 2 THEN a.val
ELSE 0
END AS id2,
CASE b.id
WHEN 1 THEN b.val
ELSE 0
END AS id3,
CASE b.id
WHEN 2 THEN b.val
ELSE 0
END AS id4
FROM tbl a,
tbl b)
WHERE id2 = 0
AND id3 = 0) UNPIVOT (VAL FOR ID IN (id1 AS '1',
id4 AS '2'))
that results in :
SET_ID
ID
VAL
1
1
15
1
2
21
2
1
15
2
2
22
3
1
16
3
2
21
4
1
16
4
2
22
tried with all the combination of source data, and it seems work :)
Sample data:
WITH
tbl AS
(
Select 1 "ID", 15 "VAL" From Dual Union All
Select 1 "ID", 15 "VAL" From Dual Union All
Select 1 "ID", 16 "VAL" From Dual Union All
Select 1 "ID", 16 "VAL" From Dual Union All
Select 2 "ID", 21 "VAL" From Dual Union All
Select 2 "ID", 21 "VAL" From Dual Union All
Select 2 "ID", 22 "VAL" From Dual Union All
Select 2 "ID", 22 "VAL" From Dual
)
UPDATED AFTER THE COMMENT
If you want the combinations then you will have to use the MODEL clause. To do that you should prepare the data (cte named grid) a bit so you could do the addressing to the particular data and manage all the combinations you want. It looks like here:
grid AS
( Select Distinct
Sum(1) OVER(Partition By ID Order By ID ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) "IDS_TOTAL_ORDER",
Sum(1) OVER(Partition By VAL Order By VAL ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) "VALS_TOTAL_ORDER",
ID "ID",
VAL "VAL"
From
tbl
Order By ID
)
Select SET_ID, ID, VAL
From ( Select 0 "SET_ID", IDS_TOTAL_ORDER, VALS_TOTAL_ORDER, ID "ID", VAL "VAL"
From grid
)
MODEL
Dimension By (ID, IDS_TOTAL_ORDER, VALS_TOTAL_ORDER)
Measures(SET_ID, VAL)
RULES
(
SET_ID[1, ANY, ANY] = CV(IDS_TOTAL_ORDER),
SET_ID[2, 1, 1] = CV(IDS_TOTAL_ORDER),
SET_ID[2, 2, 2] = CV(IDS_TOTAL_ORDER) + 1,
SET_ID[2, 3, 1] = CV(IDS_TOTAL_ORDER) - 1,
SET_ID[2, 4, 2] = CV(IDS_TOTAL_ORDER)
)
Order By ID, IDS_TOTAL_ORDER
This way you can get any combination. Here is your result:
SET_ID
ID
VAL
1
1
15
2
1
15
3
1
16
4
1
16
1
2
21
3
2
21
2
2
22
4
2
22
More about MODEL clause: https://www.oracle.com/webfolder/technetwork/tutorials/obe/db/10g/r2/prod/bidw/sqlmodel/sqlmodel_otn.htm
Sets now looks like here:
SET_ID
IDS_VALS
1
1/15, 2/21
2
1/15, 2/22
3
1/16, 2/21
4
1/16, 2/22
Regards...

Oracle - Distributing one row value across rows

As mention in the attached file. I need to distribute value in column 'B' as less or equal to column A.
Now for A>B Case 2, I need distributing of 65 as 44,16,5 but getting like 44,16,20.
SQL table represent unordered sets. But your results assume an ordering. I will assume there is an ordering column and represent it as ?.
This is a cumulative sum problem -- with arithmetic:
select t.*,
(case when b >= running_a then a
when b >= running_a - a then b - (running_a - a)
else 0
end) as new_a
from (select t.*, sum(a) over (order by ?) as running_a
from t
) t
Here is a db<>fiddle.
This is how I understood the question.
SQL> with test (a, b) as
2 -- sample data
3 (select 0, 60 from dual union all
4 select 88, 0 from dual union all
5 select 32, 0 from dual union all
6 select 7, 0 from dual
7 ),
8 -- query you need begins here
9 temp as
10 (select a,
11 max(b) over (order by null) b
12 from test
13 )
14 select
15 x.a,
16 case when x.a = 0 then y.b
17 else case when x.a > y.b then y.b
18 else x.b
19 end
20 end b
21 from test x left join temp y on x.a = y.a
22 order by x.a;
A B
---------- ----------
0 60
7 0
32 0
88 60
SQL>
SQL> with test (a, b) as
2 -- sample data
3 (select 0, 75 from dual union all
4 select 21, 0 from dual union all
5 select 12, 0 from dual union all
6 select 42, 0 from dual
7 ),
8 -- query you need begins here
9 temp as
10 (select a,
11 max(b) over (order by null) b
12 from test
13 )
14 select
15 x.a,
16 case when x.a = 0 then y.b
17 else x.a
18 end b
19 from test x left join temp y on x.a = y.a
20 order by x.a;
A B
---------- ----------
0 75
12 12
21 21
42 42
SQL>

Sql Query for Unique and Duplicates in oracle sql?

I need to display unique records in one column and duplicates in another column in Oracle?
COL1 COL2
1 10
1 10
2 20
3 30
3 30
unique in one set duplicate in one set
col1 col2 col1 col2
2 20 1 10
1 10
3 30
3 30
You can use the group by for both cases with the having clause:
Unique records
select *
from table as t
inner join (
select col1, col2, count(*) as times
from table
group by col1, col2
having count(*) = 1) as t2 ON t.col1 = t2.col2 and t.col2 = t2.col2
Duplicate records:
select *
from table as t
inner join (
select col1, col2, count(*) as times
from table
group by col1, col2
having count(*) > 1) as t2 ON t.col1 = t2.col1 and t.col2 = t2.col2
Would something like this do? See comments within code.
SQL> with
2 test (col1, col2) as
3 -- sample data
4 (select 1, 10 from dual union all
5 select 1, 10 from dual union all
6 select 2, 20 from dual union all
7 select 3, 30 from dual union all
8 select 3, 30 from dual
9 ),
10 uni as
11 -- unique values
12 (select col1, col2
13 from test
14 group by col1, col2
15 having count(*) = 1
16 ),
17 dup as
18 -- duplicate values
19 (select col1, col2
20 from test
21 group by col1, col2
22 having count(*) > 1
23 )
24 -- the final result
25 select u.col1 ucol1,
26 u.col2 ucol2,
27 d.col1 dcol1,
28 d.col2 dcol2
29 from uni u full outer join dup d on u.col1 = d.col1;
UCOL1 UCOL2 DCOL1 DCOL2
---------- ---------- ---------- ----------
1 10
3 30
2 20
SQL>
You can identify the duplicate values using window functions, and then filter each query. Then to get unique records:
select col1, col2
from (select t.*, count(*) over (partition by col1) as cnt
from t
) t
where cnt = 1;
To get duplicates:
select col1, col2
from (select t.*, count(*) over (partition by col1) as cnt
from t
) t
where cnt > 1;

Count of one of the columns

Let's say I have the dataset that looks like:
col1 col2 col3
a 2 20
a 3 12
a 4 34
b 2 44
c 3 23
c 5 13
....
What I want is a count of col1.
Output:
col1 col2 col3 count
a 2 20 3
a 3 12 3
a 4 34 3
b 2 44 1
c 3 23 2
c 5 13 2
.......
I know I can do by:
with cte as (
select col1, count(*) count
from tab1)
select a.col1,a.col2,a.col3,cte.count
from tab1
join cte on a.col1=cte.col1
But is there any other I can do that without cross apply or cte?
Also, assuming there are more than 3 letters in col1, so I couldn't use sum function either:
SUM(CASE WHEN ItemID = 'a' THEN 1 ELSE 0 END) AS count_a
If you're using SQL Server 2008+, you can use COUNT() OVER():
SELECT *,
COUNT(*) OVER(PARTITION BY col1)
FROM tab1
ONLINE DEMO