compare one row with multiple rows - sql

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>

Related

How to select all data before current_state in history data in Oracle SQL?

So I have data look like in Picture.
Column name Track is to show the step of the state.
Column name Current_state is the status of the app right now.
Column name Current_state_hist is the history of the status.
So right now the current status now is AP.
I want to Select all the status before the last status right now (AP in Track 13 & 14) without remove the status AP in track 5 - 8.
Can somebody help me for this case? Thank you
Example of the data
You can use EXISTS for that:
Schema and insert statements:
create table table1(id int, track int, current_state varchar(10), current_state_hist varchar(10), total_unit int);
insert into table1 values(1,1,'AP','OD',1)
insert into table1 values(1,2,'AP','OD',1)
insert into table1 values(1,3,'AP','OD',1)
insert into table1 values(1,4,'AP','OD',1)
insert into table1 values(1,5,'AP','AP',1)
insert into table1 values(1,6,'AP','AP',1)
insert into table1 values(1,7,'AP','AP',1)
insert into table1 values(1,8,'AP','AP',1)
insert into table1 values(1,9,'AP','OD',1)
insert into table1 values(1,10,'AP','OD',1)
insert into table1 values(1,11,'AP','OD',1)
insert into table1 values(1,12,'AP','OD',1)
insert into table1 values(1,13,'AP','AP',1)
insert into table1 values(1,14,'AP','AP',1)
Query:
SELECT ID,TRACK,CURRENT_STATE,CURRENT_STATE_HIST,TOTAL_UNIT
FROM TABLE1 T1
WHERE EXISTS
(
SELECT 1 FROM TABLE1 T2
WHERE CURRENT_STATE<>CURRENT_STATE_HIST
AND T1.TRACK<=T2.TRACK
)
Output:
ID
TRACK
CURRENT_STATE
CURRENT_STATE_HIST
TOTAL_UNIT
1
1
AP
OD
1
1
2
AP
OD
1
1
3
AP
OD
1
1
4
AP
OD
1
1
5
AP
AP
1
1
6
AP
AP
1
1
7
AP
AP
1
1
8
AP
AP
1
1
9
AP
OD
1
1
10
AP
OD
1
1
11
AP
OD
1
1
12
AP
OD
1
db<>fiddle here
You can find the latest status without having to query the table twice using the ROW_NUMBER analytic function:
SELECT id, track, current_state, current_state_hist, total_unit
FROM (
SELECT t.*,
ROW_NUMBER() OVER (ORDER BY track DESC)
- ROW_NUMBER() OVER (
PARTITION BY current_state_hist ORDER BY track DESC
) AS rn
FROM table_name t
)
WHERE rn > 0;
Or, from Oracle 12:
SELECT *
FROM table_name
MATCH_RECOGNIZE(
ORDER BY track DESC
ALL ROWS PER MATCH
PATTERN ( ^ {- same_hist+ -} any_hist* )
DEFINE
same_hist AS FIRST(current_state_hist) = current_state_hist
)
Which, for the sample data:
CREATE TABLE table_name (id, track, current_state, current_state_hist, total_unit) AS
SELECT 1, 1, 'AP', 'OD', 1 FROM DUAL UNION ALL
SELECT 1, 2, 'AP', 'OD', 1 FROM DUAL UNION ALL
SELECT 1, 3, 'AP', 'OD', 1 FROM DUAL UNION ALL
SELECT 1, 4, 'AP', 'OD', 1 FROM DUAL UNION ALL
SELECT 1, 5, 'AP', 'AP', 1 FROM DUAL UNION ALL
SELECT 1, 6, 'AP', 'AP', 1 FROM DUAL UNION ALL
SELECT 1, 7, 'AP', 'AP', 1 FROM DUAL UNION ALL
SELECT 1, 8, 'AP', 'AP', 1 FROM DUAL UNION ALL
SELECT 1, 9, 'AP', 'OD', 1 FROM DUAL UNION ALL
SELECT 1, 10, 'AP', 'OD', 1 FROM DUAL UNION ALL
SELECT 1, 11, 'AP', 'OD', 1 FROM DUAL UNION ALL
SELECT 1, 12, 'AP', 'OD', 1 FROM DUAL UNION ALL
SELECT 1, 13, 'AP', 'AP', 1 FROM DUAL UNION ALL
SELECT 1, 14, 'AP', 'AP', 1 FROM DUAL;
Both output:
ID
TRACK
CURRENT_STATE
CURRENT_STATE_HIST
TOTAL_UNIT
1
12
AP
OD
1
1
11
AP
OD
1
1
10
AP
OD
1
1
9
AP
OD
1
1
8
AP
AP
1
1
7
AP
AP
1
1
6
AP
AP
1
1
5
AP
AP
1
1
4
AP
OD
1
1
3
AP
OD
1
1
2
AP
OD
1
1
1
AP
OD
1
I want the ouput is to select all except the last 2 row... because it's current status...
If you just want to ignore the last 2 rows then: order the rows, then assign a ROWNUM pseudo-column to the ordered rows, then filter on the ROWNUM to exclude the latest two rows:
SELECT *
FROM (
SELECT t.*,
ROWNUM AS rn
FROM (
SELECT *
FROM table_name
ORDER BY track DESC
) t
)
WHERE rn >= 3;
Or, using the ROW_NUMBER analytic function:
SELECT *
FROM (
SELECT t.*,
ROW_NUMMBER() OVER (ORDER BY track DESC) AS rn
FROM table_name t
)
WHERE rn >= 3;
Or, from Oracle 12:
SELECT *
FROM table_name
ORDER BY track DESC
OFFSET 2 ROWS
FETCH FIRST 100 PERCENT ONLY;
db<>fiddle here

select only those users whose contacts length is not 5

I have table like this:
id
name
contact
1
A
65489
1
A
1
A
45564
2
B
3
C
12345
3
C
1234
4
D
32
4
D
324
I only want users who have no contact or the contact length is not five.
If the user has two or more contacts and the length of one of them is five and the rest is not, then such users should not be included in the table.
so,If the customer has at least one contact length of five, I do not want that.
so, i want table like this:
id
name
contact
2
B
4
D
32
4
D
324
Can you halp me?
You could actually do a range check here:
SELECT id, name, contact
FROM yourTable t1
WHERE NOT EXISTS (
SELECT 1
FROM yourTable t2
WHERE t2.id = t1.id AND TO_NUMBER(t2.contact) BETWEEN 10000 AND 99999
);
Note that if contact already be a numeric column, then just remove the calls to TO_NUMBER above and compare directly.
Yet another option:
SQL> with test (id, name, contact) as
2 (select 1, 'a', 65879 from dual union all
3 select 1, 'a', null from dual union all
4 select 1, 'a', 45564 from dual union all
5 select 2, 'b', null from dual union all
6 select 3, 'c', 12345 from dual union all
7 select 3, 'c', 1234 from dual union all
8 select 4, 'd', 32 from dual union all
9 select 4, 'd', 324 from dual
10 )
11 select *
12 from test a
13 where exists (select null
14 from test b
15 where b.id = a.id
16 group by b.id
17 having nvl(max(length(b.contact)), 0) < 5
18 );
ID N CONTACT
---------- - ----------
2 b
4 d 32
4 d 324
SQL>
COUNT analytic function can also be used to get the job done.
select id, name, contact
from (
select id, name, contact
, count( decode( length(contact), 5, 1, null ) ) over( partition by id, name ) cnt
from YourTable
)
where cnt = 0
demo

Apply a select for every element of a column

I would like to update third with a select that uses column first
|first #|second #|third #|
|_______|________|_______|
|___1___|___1____|_null__|
|___5___|___2____|_null__|
|___3___|___6____|_null__|
|___2___|___4____|_null__|
In pseudo code:
for row in table:
row.third = result_of_a_select(row.first)
What is the equivalent on SQL?
My wrong attempt:
update example_table
set third=
(
SELECT MAX(CDARTI) FROM
(
SELECT A.CDARTI
FROM PGMR.UT_ART_CODALT T,
PGMR.MRP_ARCH_ARTICOLI A
WHERE
A.CDARTI = T.CDARTI AND
T.CDARTI = first
UNION
SELECT A.CDARTI
FROM PGMR.UT_ART_CODALT T,
PGMR.MRP_ARCH_ARTICOLI A
WHERE
A.CDARTI = T.CDARTI_A AND
T.CDARTI = first
UNION
SELECT A.CDARTI
FROM PGMR.UT_ART_CODALT T,
PGMR.MRP_ARCH_ARTICOLI A
WHERE
A.CDARTI = T.CDARTI AND
T.CDARTI_A = first
UNION
SELECT A.CDARTI
FROM PGMR.UT_ART_CODALT T,
PGMR.MRP_ARCH_ARTICOLI A
WHERE
A.CDARTI = T.CDARTI_A AND
T.CDARTI = (SELECT T2.CDARTI FROM PGMR.UT_ART_CODALT T2 WHERE T2.CDARTI_A = first)
)
);
commit;
Works for me.
update t1 set third = (select third from t2 where t2.first = t1.first);
Fiddle (switched to MySql DB since I can't make Oracle work in it, but tested against my local Oracle as well).
The problem is not in the approach, but in your query. To fix that, you'll need to provide a Minimal, Complete, and Verifiable example.
A simplified test case:
SQL> create table yourTable (first, second, third) as (
2 select 1, 1, cast (null as number) from dual union all
3 select 5, 2, cast (null as number) from dual union all
4 select 3, 6, cast (null as number) from dual union all
5 select 2, 4, cast (null as number) from dual
6 );
Table created.
SQL> update yourTable t
2 set third = (select t.first * 2 from dual);
4 rows updated.
SQL> select * from yourTable;
FIRST SECOND THIRD
---------- ---------- ----------
1 1 2
5 2 10
3 6 6
2 4 4
SQL>
To make it a slightly more interesting/illustrative example I'm updating the third column with the value from a mapping table and have included duplicate values.
You can use MERGE and match on the pseudocolumn ROWID:
Oracle Setup:
CREATE TABLE table_name ( first, second, third ) AS
SELECT 1, 1, CAST( NULL AS NUMBER ) FROM DUAL UNION ALL
SELECT 5, 2, NULL FROM DUAL UNION ALL
SELECT 3, 6, NULL FROM DUAL UNION ALL
SELECT 2, 4, NULL FROM DUAL UNION ALL
SELECT 3, 4, NULL FROM DUAL;
CREATE TABLE table_name_map ( first, value ) AS
SELECT 1, 9 FROM DUAL UNION ALL
SELECT 2, 8 FROM DUAL UNION ALL
SELECT 3, 7 FROM DUAL UNION ALL
SELECT 4, 6 FROM DUAL UNION ALL
SELECT 5, 5 FROM DUAL;
Update:
MERGE INTO table_name dst
USING ( SELECT t.ROWID AS ri,
m.value
FROM table_name t
INNER JOIN table_name_map m
ON ( t.first = m.first )
) src
ON ( src.ri = dst.ROWID )
WHEN MATCHED THEN
UPDATE SET third = src.value;
Result:
FIRST SECOND THIRD
----- ------ -----
1 1 9
5 2 5
3 6 7
2 4 8
3 4 7

Oracle SQL (Toad): Expand table

Suppose I have an SQL (Oracle Toad) table named "test", which has the following fields and entries (dates are in dd/mm/yyyy format):
id ref_date value
---------------------
1 01/01/2014 20
1 01/02/2014 25
1 01/06/2014 3
1 01/09/2014 6
2 01/04/2015 7
2 01/08/2015 43
2 01/09/2015 85
2 01/12/2015 4
I know from how the table has been created that, since there are value entries for id = 1 for February 2014 and June 2014, the values for March through May 2014 must be 0. The same applies to July and August 2014 for id = 1, and for May through July 2015 and October through November 2015 for id = 2.
Now, if I want to calculate, say, the median of the value column for a given id, I will not arrive at the correct result using the table as it stands - as I'm missing 5 zero entries for each id.
I would therefore like to create/use the following (potentially just temporary table)...
id ref_date value
---------------------
1 01/01/2014 20
1 01/02/2014 25
1 01/03/2014 0
1 01/04/2014 0
1 01/05/2014 0
1 01/06/2014 3
1 01/07/2014 0
1 01/08/2014 0
1 01/09/2014 6
2 01/04/2015 7
2 01/05/2015 0
2 01/06/2015 0
2 01/07/2015 0
2 01/08/2015 43
2 01/09/2015 85
2 01/10/2015 0
2 01/11/2015 0
2 01/12/2015 4
...on which I could then compute the median by id:
select id, median(value) as med_value from test group by id
How do I do this? Or would there be an alternative way?
Many thanks,
Mr Clueless
In this solution, I build a table with all the "needed dates" and value of 0 for all of them. Then, instead of a join, I do a union all, group by id and ref_date and ADD the values in each group. If the date had a row with a value in the original table, then that's the resulting value; and if it didn't, the value will be 0. This avoids a join. In almost all cases a union all + aggregate will be faster (sometimes much faster) than a join.
I added more input data for more thorough testing. In your original question, you have two id's, and for both of them you have four positive values. You are missing five values in each case, so there will be five zeros (0) which means the median is 0 in both cases. For id=3 (which I added) I have three positive values and three zeros; the median is half of the smallest positive number. For id=4 I have just one value, which then should be the median as well.
The solution includes, in particular, an answer to your specific question - how to create the temporary table (which most likely doesn't need to be a temporary table at all, but an inline view). With factored subqueries (in the WITH clause), the optimizer decides if to treat them as temporary tables or inline views; you can see what the optimizer decided if you look at the Explain Plan.
with
inputs ( id, ref_date, value ) as (
select 1, to_date('01/01/2014', 'dd/mm/yyyy'), 20 from dual union all
select 1, to_date('01/02/2014', 'dd/mm/yyyy'), 25 from dual union all
select 1, to_date('01/06/2014', 'dd/mm/yyyy'), 3 from dual union all
select 1, to_date('01/09/2014', 'dd/mm/yyyy'), 6 from dual union all
select 2, to_date('01/04/2015', 'dd/mm/yyyy'), 7 from dual union all
select 2, to_date('01/08/2015', 'dd/mm/yyyy'), 43 from dual union all
select 2, to_date('01/09/2015', 'dd/mm/yyyy'), 85 from dual union all
select 2, to_date('01/12/2015', 'dd/mm/yyyy'), 4 from dual union all
select 3, to_date('01/01/2016', 'dd/mm/yyyy'), 12 from dual union all
select 3, to_date('01/03/2016', 'dd/mm/yyyy'), 23 from dual union all
select 3, to_date('01/06/2016', 'dd/mm/yyyy'), 2 from dual union all
select 4, to_date('01/11/2014', 'dd/mm/yyyy'), 9 from dual
),
-- the "inputs" table constructed above is for testing only,
-- it is not part of the solution.
ranges ( id, min_date, max_date ) as (
select id, min(ref_date), max(ref_date)
from inputs
group by id
),
prep ( id, ref_date, value ) as (
select id, add_months(min_date, level - 1), 0
from ranges
connect by level <= 1 + months_between( max_date, min_date )
and prior id = id
and prior sys_guid() is not null
),
v ( id, ref_date, value ) as (
select id, ref_date, sum(value)
from ( select id, ref_date, value from prep union all
select id, ref_date, value from inputs
)
group by id, ref_date
)
select id, median(value) as median_value
from v
group by id
order by id -- ORDER BY is optional
;
ID MEDIAN_VALUE
-- ------------
1 0
2 0
3 1
4 9
If ref_date is date and is second
with int1 as (select id
, max(ref_date) as max_date
, min(ref_date) as min_date from test group by id )
, s(n) as (select level -1 from dual connect by level <= (select max(months_between(max_date, min_date)) from int1 ) )
select i.id
, add_months(i.min_date,s.n) as ref_date
, nvl(value,0) as value
from int1 i
join s on add_months(i.min_date,s.n) <= i.max_date
LEFT join test t on t.id = i.id and add_months(i.min_date,s.n) = t.ref_date
And with median
with int1 as (select id
, max(ref_date) as max_date
, min(ref_date) as min_date from test group by id )
, s(n) as (select level -1 from dual connect by level <= (select max(months_between(max_date, min_date)) from int1 ) )
select i.id
, MEDIAN(nvl(value,0)) as value
from int1 i
join s on add_months(i.min_date,s.n) <= i.max_date
LEFT join test t on t.id = i.id and add_months(i.min_date,s.n) = t.ref_date
group by i.id

Oracle Hierarchical Query

Using Oracle 10g. I have two tables:
User Parent
-------------
1 (null)
2 1
3 1
4 3
Permission User_ID
-------------------
A 1
B 3
The values in the permissions table get inherited down to the children. I would like to write a single query that could return me something like this:
User Permission
------------------
1 A
2 A
3 A
3 A
3 B
4 A
4 B
Is it possible to formulate such a query using 10g connect .. by syntax to pull in rows from previous levels?
you can achieve the desired result with a connect by (and the function CONNECT_BY_ROOT that returns the column value of the root node):
SQL> WITH users AS (
2 SELECT 1 user_id, (null) PARENT FROM dual
3 UNION ALL SELECT 2, 1 FROM dual
4 UNION ALL SELECT 3, 1 FROM dual
5 UNION ALL SELECT 4, 3 FROM dual
6 ), permissions AS (
7 SELECT 'A' permission, 1 user_id FROM dual
8 UNION ALL SELECT 'B', 3 FROM dual
9 )
10 SELECT lpad('*', 2 * (LEVEL-1), '*')||u.user_id u,
11 u.user_id, connect_by_root(permission) permission
12 FROM users u
13 LEFT JOIN permissions p ON u.user_id = p.user_id
14 CONNECT BY u.PARENT = PRIOR u.user_id
15 START WITH p.permission IS NOT NULL
16 ORDER SIBLINGS BY user_id;
U USER_ID PERMISSION
--------- ------- ----------
3 3 B
**4 4 B
1 1 A
**2 2 A
**3 3 A
****4 4 A
You could take a look at http://www.adp-gmbh.ch/ora/sql/connect_by.html
Kind of black magic, but you can use table-cast-multiset to reference one table from another in WHERE clause:
create table t1(
usr number,
parent number
);
create table t2(
usr number,
perm char(1)
);
insert into t1 values (1,null);
insert into t1 values (2,1);
insert into t1 values (3,1);
insert into t1 values (4,3);
insert into t2 values (1,'A');
insert into t2 values (3,'B');
select t1.usr
, t2.perm
from t1
, table(cast(multiset(
select t.usr
from t1 t
connect by t.usr = prior t.parent
start with t.usr = t1.usr
) as sys.odcinumberlist)) x
, t2
where t2.usr = x.column_value
;
In the subquery x I construct a table of all parents for the given user from t1 (including itself), then join it with permissions for these parents.
Here is a example for just one user id. you can use proc to loop all.
CREATE TABLE a_lnk
(user_id VARCHAR2(5),
parent_id VARCHAR2(5));
CREATE TABLE b_perm
(perm VARCHAR2(5),
user_id VARCHAR2(5));
INSERT INTO a_lnk
SELECT 1, NULL
FROM DUAL;
INSERT INTO a_lnk
SELECT 2, 1
FROM DUAL;
INSERT INTO a_lnk
SELECT 3, 1
FROM DUAL;
INSERT INTO a_lnk
SELECT 4, 3
FROM DUAL;
INSERT INTO b_perm
SELECT 'A', 1
FROM DUAL;
INSERT INTO b_perm
SELECT 'B', 3
FROM DUAL;
-- example for just for user id = 1
--
SELECT c.user_id, c.perm
FROM b_perm c,
(SELECT parent_id, user_id
FROM a_lnk
START WITH parent_id = 1
CONNECT BY PRIOR user_id = parent_id
UNION
SELECT parent_id, user_id
FROM a_lnk
START WITH parent_id IS NULL
CONNECT BY PRIOR user_id = parent_id) d
WHERE c.user_id = d.user_id
UNION
SELECT d.user_id, c.perm
FROM b_perm c,
(SELECT parent_id, user_id
FROM a_lnk
START WITH parent_id = 1
CONNECT BY PRIOR user_id = parent_id
UNION
SELECT parent_id, user_id
FROM a_lnk
START WITH parent_id IS NULL
CONNECT BY PRIOR user_id = parent_id) d
WHERE c.user_id = d.parent_id;