Select data which was changed once - sql

Hi I am new in Oracle/PLSQL.
I have a question. I have a table which stores data like this:
Example:
Table 1
col1 col2 col3
1 true 2023-01-01
1 true 2022-12-06 <- I need this row
1 false 2022-11-03
2 true 2018-05-01 <- I need this row
2 false 2018-04-06
2 false 2018-01-03
I want to select the first row which changed col2 to true. It means I need second and fourth rows from the above example. I have a lot of data in a table and I need query which will work for all of them. col1 is not unique, cot3 is a timestamp when it was changed.
How can I do that with a Query?

row_number analytic function helps.
Note that date values you posted are ambiguous; it is impossible to know what any of dates represents because days and months are valid in any format model (yyyy-mm-dd and yyyy-dd-mm), and you didn't explain which is which. For "2022-12-06", what is it? 6th of December, or 12th of June? Could be both. The same goes for all sample dates.
I suppose it is yyyy-mm-dd.
SQL> alter session set nls_date_Format = 'yyyy-mm-dd';
Session altered.
Sample data:
SQL> with test (col1, col2, col3) as
2 (select 1, 'true' , date '2023-01-01' from dual union all
3 select 1, 'true' , date '2022-12-06' from dual union all
4 select 1, 'false', date '2022-11-03' from dual union all
5 select 2, 'true' , date '2018-05-01' from dual union all
6 select 2, 'false', date '2018-04-06' from dual union all
7 select 2, 'false', date '2018-01-03' from dual
8 )
Query:
9 select col1, col2, col3
10 from (select t.*,
11 row_number() over (partition by t.col1 order by t.col3) rn
12 from test t
13 where t.col2 = 'true'
14 )
15 where rn = 1;
COL1 COL2 COL3
---------- ----- ----------
1 true 2022-12-06
2 true 2018-05-01
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...

How do I concatenate two columns separated by '/' only if both columns are not null?

I'm trying to concatenate two columns, with the values separated by '/' only if both columns are not null.
If either column is null then '/' should not be printed.
Example:
current output :
concat(concat(purpose,'/'),priority)
abc/xyz
/xyz
abc/
Expected Output :
abc/xyz
xyz
abc
Use CASE (see line #9):
SQL> with test (col1, col2) as
2 (select 'abc', 'xyz' from dual union all
3 select null , 'xyz' from dual union all
4 select 'abc', null from dual
5 )
6 select col1,
7 col2,
8 --
9 col1 || case when col1 is not null and col2 is not null then '/' end || col2 result
10 from test;
COL1 COL2 RESULT
----- ----- -------
abc xyz abc/xyz
xyz xyz
abc abc
SQL>
Or, use a trick - remove leading/trailing (read: both) slashes:
SQL> with test (col1, col2) as
2 (select 'abc', 'xyz' from dual union all
3 select null , 'xyz' from dual union all
4 select 'abc', null from dual
5 )
6 select col1,
7 col2,
8 trim(both '/' from col1 ||'/'|| col2) result
9 from test;
COL1 COL2 RESULT
----- ----- -------
abc xyz abc/xyz
xyz xyz
abc abc
SQL>
Here we are using CASE WHEN to look for the conditions explained in the question. The first condition is checking if both columns are not Null. If that is the case then we concat the two columns together with
/ in between.
If we have a case when one of the column is Null then we simply return the value of the column which is not Null. For that purpose we are using COALESCE. COALESCE always returns the firs non-null value.
Select
col_one,
col_two,
case when col_one is not null and col_two is not null
then concat(col_one, "/",col_two)
else COALESCE(col_one,col_two) end as concat_final
from table

Duplicate value removal on field by field basis in Informatica or Oracle

I have a scenario for duplicate removal on field by field basis as mentioned below which I need to implement in Informatica or Oracle. Please let me know how to do it.
In Oracle, if you use SQL*Plus, break on columns you want (col1 and col2 in your case). Sample data in lines #1 - 10; query is a simple select everything from the table. As I said, break does the job.
Though, in my opinion, you shouldn't be doing it in SQL itself. Any decent reporting tool (Oracle Reports, Apex, ...) is capable of breaking on desired columns. You should use it.
SQL> break on col1 on col2
SQL>
SQL> with test (col1, col2, datum) as
2 (select 'haryana', 1, '7th feb' from dual union all
3 select 'haryana', 12, '8th feb' from dual union all
4 select 'haryana', 12, '9th feb' from dual union all
5 select 'haryana', 11, '10th feb' from dual union all
6 select 'pune' , 1, '11th feb' from dual union all
7 select 'pune' , 2, '12th feb' from dual union all
8 select 'pune' , 3, '13th feb' from dual union all
9 select 'pune' , 3, '14th feb' from dual
10 )
11 select col1, col2, datum
12 from test
13 order by col1, col2, datum;
COL1 COL2 DATUM
------- ---------- --------
haryana 1 7th feb
11 10th feb
12 8th feb
9th feb
pune 1 11th feb
2 12th feb
3 13th feb
14th feb
8 rows selected.
SQL>
It looks like you want to remove duplicates in your result set from your query, not in the actual data. Grouping/breaking report values like that is a function of whatever software is producing the report, not of the database itself. The raw data would always contain all of the values, or you wouldn't be able to group it. In sqlplus, for example, this would be accomplished with the BREAK command:
https://docs.oracle.com/en/database/oracle/oracle-database/19/sqpug/BREAK.html#GUID-8C5811BC-01DF-4D95-A3A0-5C42C546534F
Other clients would have their own way of specifying break columns in reports.
I recommend doing this on the reporting side, but you can do it in SQL:
select (case when row_number() over (partition by col1 order by date) = 1
then col1
end) as col1,
(case when row_number() over (partition by col1, col2 order by date) = 1
then col2
end) as col2,
col3
from t
order by col1, col2, date;

Oracle 12c Analytic Function

Is there a way to obtain the corresponding value X for a minimum value Y in a given dataset, in the same record, using Oracle Analytic functions, and without using a subquery?
For example:
If I have the following dataset "ds1":
Col1 Col2
A 1
B 2
C 3
D 4
E 4
A 10
Normally, in order to find the value "A" in Col1, which corresponds to the minimum value "1" in Col2, I would write the following query:
select ds1.col1
from ds1
, (select min (col2) col2
from ds1) min_ds1
where ds1.col2 = min_ds1.col2
/
Here is the executed code for such a Test Case:
### 1014.010, Start time is: 10/30/2019 11:39:35am
MYUN#MYDB-C1>>create table ds1 (col1 varchar2 (1), col2 number)
2 /
Table created.
Elapsed: 00:00:00.01
MYUN#MYDB-C1>>insert into ds1 (col1, col2)
2 select 'A', 1 from dual
3 union all select 'B', 2 from dual
4 union all select 'C', 3 from dual
5 union all select 'D', 4 from dual
6 union all select 'E', 4 from dual
7 union all select 'A', 10 from dual
8 /
6 rows created.
Elapsed: 00:00:00.02
MYUN#MYDB-C1>>commit
2 /
Commit complete.
Elapsed: 00:00:00.01
MYUN#MYDB-C1>>col col1 format a10
MYUN#MYDB-C1>>select ds1.col1
2 from ds1
3 , (select min (col2) col2
4 from ds1) min_ds1
5 where ds1.col2 = min_ds1.col2
6 /
COL1
----------
A
1 row selected.
Elapsed: 00:00:00.01
MYUN#MYDB-C1>>drop table ds1
2 /
Table dropped.
Elapsed: 00:00:00.03
The time now: 10/30/2019 11:39:36am
My question is:
Is it possible to derive the value "A" using an Analytic Function and without requiring a subquery? I am aware I can use the analytic function "ROW_NUMBER", sort the result in the ORDER BY clause, all in a subquery and then add a WHERE clause on the outer query where I say something like "WHERE RN = 1", where "RN" is the alias for the column in the subquery where the ROW_NUMBER function is used.
Use an aggregation function with KEEP to get the minimum values for another column:
Oracle Setup:
create table ds1 ( col1, col2 ) AS
select 'A', 1 from dual
union all select 'B', 2 from dual
union all select 'C', 3 from dual
union all select 'D', 4 from dual
union all select 'E', 4 from dual
union all select 'F', 10 from dual;
Aggregation Query:
SELECT MIN( col1 ) KEEP ( DENSE_RANK FIRST ORDER BY col2 ) AS col1
FROM ds1
Output:
| COL1 |
| :--- |
| A |
Analytic Query:
If you particularly want an analytic function then:
SELECT col1, col2
FROM (
SELECT ds1.*,
DENSE_RANK() OVER ( ORDER BY col2 ASC ) AS rnk
FROM ds1
)
WHERE rnk = 1
This has a sub-query but there is only a single table-scan.
You can easily integrate it into a huge query:
WITH my_huge_query AS (
<paste your huge query here>
)
SELECT *
FROM (
SELECT m.*,
DENSE_RANK() OVER( ORDER BY col2 ASC ) AS rnk
FROM my_huge_query m
)
WHERE rnk = 1
Output:
COL1 | COL2
:--- | ---:
A | 1
db<>fiddle here

compare one row with multiple rows

Ex: I have other main table which is having below data
Create table dbo.Main_Table
(
ID INT,
SDate Date
)
Insert Into dbo.Main_Table Values (1,'01/02/2018')
Insert Into dbo.Main_Table Values (2,'01/30/2018')
Create table dbo.test
(
ID INT,
SDate Date
)
Insert Into dbo.test Values (1,'01/01/2018')
Insert Into dbo.test Values (1,'01/02/2018')
Insert Into dbo.test Values (1,'01/30/2018')
Insert Into dbo.test Values (2,'10/01/2018')
Insert Into dbo.test Values (2,'01/02/2018')
Insert Into dbo.test Values (2,'01/30/2018')
I would like to compare data in main table data with test table. We have to join based on ID and if date match found then "yes" else "No". We have to compare one row with multiple rows.
Please let me know if any questions , thanks for you;re help
Something like this?
SQL> with main_table (id, sdate) as
2 (select 1, date '2018-01-02' from dual union all
3 select 2, date '2018-01-30' from dual union all
4 select 3, date '2018-07-25' from dual
5 ),
6 test_table (id, sdate) as
7 (select 1, date '2018-01-02' from dual union all
8 select 2, date '2018-08-30' from dual
9 )
10 select m.id,
11 m.sdate,
12 case when m.sdate = t.sdate then 'yes' else 'no' end status
13 from main_table m left join test_table t on t.id = m.id
14 order by m.id;
ID SDATE STATUS
---------- -------- ------
1 02.01.18 yes
2 30.01.18 no
3 25.07.18 no
SQL>
[EDIT, after reading the comment - if you find a match, you don't need that ID at all]
Here you are:
SQL> with test (id, sdate) as
2 (select 1, date '2018-01-01' from dual union all
3 select 1, date '2018-01-02' from dual union all
4 select 1, date '2018-01-30' from dual union all
5 --
6 select 2, date '2018-10-01' from dual union all
7 select 2, date '2018-01-02' from dual union all
8 select 2, date '2018-01-30' from dual
9 )
10 select id, sdate
11 from test t
12 where not exists (select null
13 from test t1
14 where t1.id = t.id
15 and t1.sdate = to_date('&par_sdate', 'yyyy-mm-dd'));
Enter value for par_sdate: 2018-01-01
ID SDATE
---------- ----------
2 2018-01-30
2 2018-01-02
2 2018-10-01
SQL> /
Enter value for par_sdate: 2018-01-02
no rows selected
SQL>