I have an Oracle table with multiple columns some populated with a variable, there a large number of possible variables, the example below is not exhaustive.
ID Col1 Col2 Col3
--------------------
1 A B
2 B A D
3 B C
4 C B
5 B B
6 E D
7 B A C
I need to create a query that resorts the variables in each row:
ID Col1 Col2 Col3
--------------------
1 A B
2 A B D
3 B C
4 B C
5 B B
6 D E
7 A B C
I am looking for an elegant solution as the real world problem has 20 columns with up to 40 different variables (up to four characters in length each) and several million records.
Below is a variant for Oracle 10g and later. Because there are a big number of rows in original dataset I'll tried to avoid solutions which involves grouping and analytic functions atop of a full result set.
Base table for this example:
create table tab1 (
ID number,
col1 varchar2(4),
col2 varchar2(4),
col3 varchar2(4),
col4 varchar2(4)
)
First, collect all columns for each ID to sorted collection:
select
tab1.ID,
cast( multiset(
select
decode(level,
1, tab1.col1,
2, tab1.col2,
3, tab1.col3,
4, tab1.col4,
null
)
from dual
connect by level <= 4
order by
decode(level,
1, tab1.col1,
2, tab1.col2,
3, tab1.col3,
4, tab1.col4,
null
)
nulls last
) as sys.ODCIVarchar2List) sorted_values
from tab1;
Having such a dataset it's possible to decode values back to columns while maintaining specified order:
select
ID,
(
select column_value
from table(data_list.sorted_values)
where rownum = 1
) as col1,
(
select max(decode(rownum, 2, column_value, null))
from table(data_list.sorted_values)
) as col2,
(
select max(decode(rownum, 3, column_value, null))
from table(data_list.sorted_values)
) as col3,
(
select max(decode(rownum, 4, column_value, null))
from table(data_list.sorted_values)
) as col4
from (
select
rownum, -- this needed as workaround for Oracle bug
tab1.ID,
cast( multiset(
select
decode(level,
1, tab1.col1,
2, tab1.col2,
3, tab1.col3,
4, tab1.col4,
null
)
from dual
connect by level <= 4
order by
decode(level,
1, tab1.col1,
2, tab1.col2,
3, tab1.col3,
4, tab1.col4,
null
)
nulls last
) as sys.ODCIVarchar2List) sorted_values
from tab1
)
data_list
SQLFiddle test
Note that rownum must be present in inner select clause as a workaround for this Oracle error.
Related
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...
I have this table
COL1 COL2
---------
A 1
B 5
C 12
D 14
And I would like to obtain this other one. This is, until the next col2 for each col1 is reached, a row with the COL1 and incremental values.
COL1 COL2
---------
A 1
A 2
A 3
A 4
B 5
B 6
B 7
B 8
B 9
B 10
B 11
C 12
C 13
D 14
EDIT: this is what I've tried so far. It seems I'm not far away from the solution but struggling to progress further than this.
WITH aux (
col1,
col2
) AS (
SELECT
'A',
1
FROM
dual
UNION ALL
SELECT
'B',
5
FROM
dual
UNION ALL
SELECT
'C',
12
FROM
dual
UNION ALL
SELECT
'D',
14
FROM
dual
), aux1 AS (
SELECT
a.*,
nvl(LEAD(a.col2) OVER(
ORDER BY
a.col2
), a.col2) h
FROM
aux a
)
SELECT
*
FROM
aux1
CONNECT BY level >= col2
AND level <= h;
testseq is the table containing your initial 4 rows. Use lead to find the stop value for col2 for each col1, and recursion to iterate and create the additional rows.
WITH xrows (col1, col2, lastcol2) AS (
SELECT t.*, LEAD(col2) OVER (ORDER BY col1) - 1
FROM testseq t
UNION ALL
SELECT col1, col2+1, lastcol2
FROM xrows t
WHERE col2 < lastcol2
)
SELECT col1, col2
FROM xrows
ORDER BY col1, col2
;
First you need to find the "next" number (whatever ordering you prefer) and then generate such number of rows with recursive subquery:
with a(code, num) as(
select 'A', 1 from dual union all
select 'B', 5 from dual union all
select 'C', 12 from dual union all
select 'D', 14 from dual
)
, b as (
select
a.*
, lead(num - 1, 1, num) over(order by code asc) as next_num
from a
)
select
b.code
, gen.val
from b
cross join lateral(
select num + level - 1 as val
from dual
connect by num + level - 1 <= next_num
) gen
order by 2 asc
Or if you prefer recursive CTE:
with a(code, num) as(
select 'A', 1 from dual union all
select 'B', 5 from dual union all
select 'C', 12 from dual union all
select 'D', 14 from dual
)
, b(code, next_num, val) as (
select
a.code
, lead(num - 1, 1, num) over(order by code asc) as next_num
, num
from a
union all
select
code
, next_num
, val + 1
from b
where val < next_num
)
select
b.code
, val
from b
order by 2 asc
CODE
VAL
A
1
A
2
A
3
A
4
B
5
B
6
B
7
B
8
B
9
B
10
B
11
C
12
C
13
D
14
livesql demo
Input:
col1 col2
------------
1 A
2 1
3 B
4 2
5 C
6 Null
Output i want
Col1 Col2
__________
A 1
B 2
C Null
Oh, I see. Assuming there are no gaps in col1, you can use aggregation using arithmetic:
select max(case when mod(id, 2) = 0 then col2 end),
max(case when mod(id, 2) = 1 then col2 end)
from t
group by floor((id - 1) / 2);
Another method uses lead():
select col2, next_col2
from (select t.*, lead(col2) over (order by id) as next_col2
from t
) t
where mod(id, 2) = 1;
Use join to the next row of the same table and limit to every other row.
select t1.col2 col1, t2.col2
from tab t1
join tab t2 on t1.col1 = t2.col1 -1
where mod(t1.col1,2) = 1
C C
- -
A 1
B 2
C
Again, it assumes that your sequence in col1is without gaps.
This is an application of PIVOT, available since Oracle 11.1. (A much shorter solution is possible in Oracle 12.1 and higher, using MATCH_RECOGNIZE, but the question is explicitly tagged oracle11g.)
I use the analytic function ROW_NUMBER() to prepare the data, so that we don't need to assume anything about the ordering column - it may have gaps, and/or it doesn't even have to be a number column, it can be date or string or anything else that can be ordered.
Setup:
create table sample_data (col1, col2) as
select 1, 'A' from dual union all
select 2, '1' from dual union all
select 3, 'B' from dual union all
select 4, '2' from dual union all
select 5, 'C' from dual union all
select 6, null from dual
;
Query and output:
select col1, col2
from (
select ceil(row_number() over (order by col1) / 2) as r,
mod (row_number() over (order by col1) , 2) as c, col2
from sample_data
)
pivot (min(col2) for c in (1 as col1, 0 as col2))
order by r
;
COL1 COL2
---- ----
A 1
B 2
C
Just for fun, and for whoever may have this problem in Oracle 12 or higher, here is the match_recognize solution:
select col1, col2
from sample_data
match_recognize(
order by col1
measures x.col2 as col1, y.col2 as col2
pattern ( x y? )
define x as null is null
);
I've got a table A with 3 columns that contains the same data, for exemple:
TABLE A
KEY COL1 COL2 COL3
1 A B C
2 B C null
3 A null null
4 D E F
5 null C B
6 B C A
7 D E F
As a result I expect the distinct values of this table and the order doesn't matter. So key 1 and 6 are the same and 2 and 5 also and 4 and 7. The rest is different.
Ofcourse, I can't use a distinct in my select that will only filter 4 and 7.
I could use a very complex case statement, or a select in a select with an order by. But this needs to be used in a conversion, so performance is an issue here.
Does anyone have a good performant way to do this?
The result I expect
COL1 COL2 COL3
A B C
B C null
A null null
D E F
If you can have many columns then you can UNPIVOT then order the values and then PIVOT and take the DISTINCT rows:
Oracle Setup:
CREATE TABLE table_name ( KEY, COL1, COL2, COL3 ) AS
SELECT 1, 'A', 'B', 'C' FROM DUAL UNION ALL
SELECT 2, 'B', 'C', null FROM DUAL UNION ALL
SELECT 3, 'A', null, null FROM DUAL UNION ALL
SELECT 4, 'D', 'E', 'F' FROM DUAL UNION ALL
SELECT 5, null, 'C', 'B' FROM DUAL UNION ALL
SELECT 6, 'B', 'C', 'A' FROM DUAL UNION ALL
SELECT 7, 'D', 'E', 'F' FROM DUAL
Query:
SELECT DISTINCT
COL1, COL2, COL3
FROM (
SELECT key,
value,
ROW_NUMBER() OVER ( PARTITION BY key ORDER BY value ) AS rn
FROM table_name
UNPIVOT ( value FOR name IN ( COL1, COL2, COL3 ) ) u
)
PIVOT ( MAX( value ) FOR rn IN (
1 AS COL1,
2 AS COL2,
3 AS COL3
) )
Output:
COL1 | COL2 | COL3
:--- | :--- | :---
A | B | C
B | C | null
D | E | F
A | null | null
db<>fiddle here
The complicated case expression is going to have the best performance. But the simplest method is going to be conditional aggregation:
select key,
max(case when seqnum = 1 then col end) as col1,
max(case when seqnum = 2 then col end) as col2,
max(case when seqnum = 3 then col end) as col3
from (select key,col,
row_number() over (partition by key order by col asc) as seqnum
from ((select key, col1 as col from t) union all
(select key, col2 as col from t) union all
(select key, col3 as col from t)
) kc
where col is not null
) kc
group by key;
I have a table as such
Col 1 Col 2 Col 3
1 A 1
2 A 2
3 B 1
4 C 1
5 C 2
6 D 1
How do I only get unique rows which have Col 3 = 1?
I want to get rows 3 and 6 (Col 2 = B and D respectively). I do not want A nor C since they have Col 3 = 2 as well.
I've tried something along the lines of:
select col 2 from table group by col 2 having count(col 3) = 1
But that only brings up Col 2 for results so I'm uncertain if Col 3 contents = 1 or not.
EDIT: Sorry guys maybe I've not worded my question clearly. I want to get all of the rows of Col 2 which contain only Col 3 = 1 AND ONLY 1.
So if I tried WHERE Col 3= 1, it would return 4 rows because A has 1. But since A also has a row where Col 3 = 2, I do not want that, same for C. From this example table, I would want the end result to only show 2 rows, B and D.
My example table is an example, I actually have about 5000 rows to filter through, otherwise I'd do as you guys have suggested :)
SELECT col2
FROM your_table
GROUP BY col2
HAVING MAX(col3) = 1 AND MIN(Col3) = 1
Or
SELECT a.col2
FROM your_table a
WHERE a.col3=1 AND NOT EXISTS(SELECT *
FROM your_table b
WHERE a.col2=b.col2 AND b.col3<>1)
What you are probably looking for is WHERE clause.
SELECT * FROM YouTable WHERE col3 = 1 AND col2 in ('B','D');
;with T ([Col 1], [Col 2], [Col 3]) as
(
select 1, 'A', 1 union all
select 2, 'A', 2 union all
select 3, 'B', 1 union all
select 4, 'C', 1 union all
select 5, 'C', 2 union all
select 6, 'D', 1
)
select *
from T
left outer join
(
select distinct [Col 2]
from T
where [Col 3] <> 1
) as T2
on T.[Col 2] = T2.[Col 2]
where T.[Col 3] = 1 and
T2.[Col 2] is null
It's a bit hard to know exactly what you're trying to get, but this is my best guess:
SELECT * FROM theTable WHERE col2 NOT IN
(SELECT col2 FROM theTable WHERE col3 <> 1)
SELECT * FROM #temp t1
WHERE EXISTS
(
select Col2 from #Temp t2
WHERE t2.Col2 = t1.Col2
group by col2
having count(col3) = 1
)
tested with MS SQL2008 and the following (so if my answer is not the correct one it may halp others test theirs...):
CREATE TABLE #temp
(
Col1 INT,
Col2 CHAR(1),
Col3 INT
)
INSERT INTO #Temp
(Col1, Col2, Col3)
SELECT 1,'A',1
UNION
SELECT 2,'A',2
UNION
SELECT 3,'B', 1
UNION
SELECT 4,'C',1
UNION
SELECT 5,'C',2
UNION
SELECT 6,'D',1
SELECT * FROM #temp t1
WHERE EXISTS
(
select Col2 from #temp t2
WHERE t2.Col2 = t1.Col2
group by col2
having count(col3) = 1
)
DROP TABLE #temp