Oracle SQL - update 2 columns in row with the oldest date - sql

I am attempting to update 2 columns in a row. The row that should be updated is the row that has the oldest duedate
The table chorecompletion is described as:
Name Null? Type
----------------------------------------- -------- ----------------------------
CHOREID NOT NULL NUMBER(38)
GROUPID NOT NULL NUMBER(38)
DUEDATE NOT NULL DATE
COMPLETEDDATE DATE
COMPLETEDBY NUMBER(38)
This query returns the row that I want to update
select *
from
(
select choreid, duedate, row_number()
over (partition by choreid order by duedate) as rn
from chorecompletion where choreid = 12 and groupid = 6
)
where rn = 1;
Where I could use some help is how to use this query in my update statement, specifically my where clause
my current attempt:
update chorecompletion
set completeddate = sysdate, completedby=1
where --How to get the result of the previous query here?
Any help on my logic would be hugely appreciated.
Example desired result:
Before Update:
CHOREID GROUPID DUEDATE COMPLETEDDATE COMPLETEDBY
-------------------------------------------------------------------
12 6 2018-11-1
12 6 2018-10-1
After Update
CHOREID GROUPID DUEDATE COMPLETEDDATE COMPLETEDBY
-------------------------------------------------------------------
12 6 2018-11-1
12 6 2018-10-1 2018-09-30 1

Something like this?
SQL> create table test
2 (choreid number,
3 groupid number,
4 duedate date,
5 completeddate date,
6 completedby number
7 );
Table created.
SQL> insert into test
2 select 12, 6, date '2018-01-11', null, null from dual union all
3 select 12, 6, date '2018-01-10', null, null from dual;
2 rows created.
SQL> update test t set
2 t.completeddate = sysdate,
3 t.completedby = 1
4 where t.duedate = (select min(t1.duedate)
5 from test t1
6 where t1.choreid = t.choreid
7 and t1.groupid = t.groupid)
8 and t.choreid = 12
9 and t.groupid = 6;
1 row updated.
SQL> select * From test;
CHOREID GROUPID DUEDATE COMPLETEDD COMPLETEDBY
---------- ---------- ---------- ---------- -----------
12 6 2018-01-11
12 6 2018-01-10 2018-09-30 1
SQL>

You can use a MERGE statement and can join on the ROWID pseudo-column so that you can correlated directly to the matched row:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE chorecompletion ( choreid, groupid, duedate, completeddate, completedby ) AS
SELECT 12, 6, DATE '2018-09-29', CAST( null AS DATE ), CAST( null AS NUMBER ) FROM DUAL UNION ALL
SELECT 12, 6, DATE '2018-09-30', null, null FROM DUAL;
Query 1:
MERGE INTO chorecompletion dst
USING (
SELECT ROWID AS rid
FROM (
SELECT *
FROM chorecompletion
WHERE choreid = 12
AND groupid = 6
ORDER BY duedate ASC
)
WHERE ROWNUM = 1
) src
ON ( src.RID = dst.ROWID )
WHEN MATCHED THEN
UPDATE
SET completeddate = sysdate,
completedby = 1
Results:
1 Row Updated.
Query 2:
SELECT * FROM chorecompletion
Results:
| CHOREID | GROUPID | DUEDATE | COMPLETEDDATE | COMPLETEDBY |
|---------|---------|----------------------|----------------------|-------------|
| 12 | 6 | 2018-09-29T00:00:00Z | 2018-09-30T18:42:45Z | 1 |
| 12 | 6 | 2018-09-30T00:00:00Z | (null) | (null) |
Query 3: You could also use an UPDATE statement with the ROWID pseudo-column:
UPDATE chorecompletion dst
SET completeddate = sysdate,
completedby = 2
WHERE ROWID = (
SELECT ROWID
FROM (
SELECT ROW_NUMBER() OVER ( PARTITION BY choreid ORDER BY duedate ) rn
FROM chorecompletion
WHERE choreid = 12
AND groupid = 6
ORDER BY duedate ASC
)
WHERE rn = 1
)
Results:
1 Row Updated.
Query 4:
SELECT * FROM chorecompletion
Results:
| CHOREID | GROUPID | DUEDATE | COMPLETEDDATE | COMPLETEDBY |
|---------|---------|----------------------|----------------------|-------------|
| 12 | 6 | 2018-09-29T00:00:00Z | 2018-09-30T18:42:45Z | 2 |
| 12 | 6 | 2018-09-30T00:00:00Z | (null) | (null) |

You can use a correlated subquery. If I understand correctly:
update chorecompletion
set completeddate = (select min(duedate)
from chorecompletion cc
where cc.choreid = chorecompletion.coreid
)
where choreid = 12 and groupid = 6

Related

SQL return second max date for each id, date and channel

I have the following table:
id channel_id date
1 | 1 | 2017-01-10
1 | 2 | 2018-02-05
1 | 1 | 2019-03-07
1 | 2 | 2020-03-15
2 | 1 | 2018-01-17
2 | 1 | 2019-07-20
2 | 1 | 2020-01-10
I want to return for previous maximum date for each date and id but two separate columns for both channel_id. So, one column for previous max date for channel_id is equal to 1 and another for previous max date for channel_id is equal to 2. What I want to get can be found below:
id channel_id date prev_date_channel_id1 prev_date_channel_id2
1 | 1 | 2017-01-10 | NULL | NULL |
1 | 2 | 2018-02-05 | 2017-01-10 | NULL |
1 | 1 | 2019-03-07 | 2017-01-10 | 2018-02-05 |
1 | 2 | 2020-03-15 | 2019-03-07 | 2018-02-05 |
2 | 1 | 2018-01-17 | NULL | NULL |
2 | 1 | 2019-07-20 | 2018-01-17 | NULL |
2 | 1 | 2020-01-10 | 2019-07-20 | NULL |
I made a query as below and returns what I want but takes too much time. I'd appreciate any optimization suggestions!
SELECT
a.id,
a.date,
MAX(c.date) AS prev_date_channel_id1,
MAX(d.date) AS prev_date_channel_id2
FROM
table a
LEFT JOIN
table c ON a.id=c.id AND a.date>c.date AND c.channel_id=1
LEFT JOIN
table d ON a.id=d.id AND a.date>d.date AND d.channel_id=2
GROUP BY a.id, a.date
Use lag() for the previous date and a cumulative conditional max for the channel 2 date:
select t.*, lag(date) over (partition by id order by date) as prev_date,
max(case when channel = 2 then date end) over
(partition by id
order by date
rows between unbounded preceding and 1 row preceding
) as prev_date_channel2
from t;
I think there's an error in your "expected output" for the value of prev_date_channel_id1 on the last row (it should be 2019-07-20).
In any case, with appropriate indexing an outer apply top 1 construct might serve you better:
create table t
(
id int,
channel_id int,
[date] date
constraint pk_t primary key clustered (id, channel_id, [date])
);
insert t values
(1, 1, '2017-01-10'),
(1, 2, '2018-02-05'),
(1, 1, '2019-03-07'),
(1, 2, '2020-03-15'),
(2, 1, '2018-01-17'),
(2, 1, '2019-07-20'),
(2, 1, '2020-01-10');
select t1.id,
t1.channel_id,
t1.[date],
prev_date_channel_id1 = c1.dt,
prev_date_channel_id2 = c2.dt
from t t1
outer apply (
select top 1 [date]
from t
where id = t1.id
and channel_id = 1
and [date] < t1.[date]
order by date desc
) c1(dt)
outer apply (
select top 1 [date]
from t
where id = t1.id
and channel_id = 2
and [date] < t1.[date]
order by date desc
) c2(dt)
order by t1.id, t1.[date];
Or possibly faster still, especially with the key changed to constraint pk_t primary key clustered (id, [date], [channel_id]))
select t1.id,
t1.channel_id,
t1.[date],
prev_date_channel_id1 = prev.c1,
prev_date_channel_id2 = prev.c2
from t t1
outer apply (
select c1 = max(iif(channel_id = 1, [date], null)),
c2 = max(iif(channel_id = 2, [date], null))
from t
where id = t1.id
and [date] < t1.[date]
) prev
Assuming you have an index on those three columns, you can use subqueries:
SELECT [T0].[id],
[T0].[channel_id],
[T0].[date],
[prev_date_channel_id1] = (
SELECT MAX([T1].[date])
FROM [t] [T1]
WHERE [T1].[id] = [T0].[id]
AND [T1].[date] < [T0].[date]
AND [T1].[channel_id] = 1
),
[prev_date_channel_id2] = (
SELECT MAX([T1].[date])
FROM [t] [T1]
WHERE [T1].[id] = [T0].[id]
AND [T1].[date] < [T0].[date]
AND [T1].[channel_id] = 2
)
FROM [t] [T0];

postgres: How to select that have every value in a range?

I have a table, test_table that looks like this:
analysis_date | test_num
------------------------+--------------------
2001-01-01 | 1
2001-01-01 | 2
2001-01-01 | 3
2001-01-02 | 1
2001-01-02 | 2
2001-01-02 | 3
2001-01-03 | 1
2001-01-03 | 2
2001-01-03 | 8
I only want to select rows in which the analysis date has a value for test_num 1, 2, AND 3. The query should not return rows for anlysis_date 2001-01-03as row for test_num = 3 is missing
analysis_date | test_num
------------------------+--------------------
2001-01-01 | 1
2001-01-01 | 2
2001-01-01 | 3
2001-01-02 | 1
2001-01-02 | 2
2001-01-02 | 3
I am aware of the BETWEEN query, but that doesn't guarantee that all the values within the range exist.
You may try
SELECT * FROM test_table
WHERE analysis_date
IN ( SELECT analysis_date
FROM test_table where test_num IN (1,2,3)
group by analysis_date having
count(DISTINCT test_num) = 3
)
DEMO
You can use exists also
select *
from test_table t
where exists ( select 1
from test_table
where test_num in (1,2,3)
and analysis_date = t.analysis_date
group by analysis_date
having count(distinct test_num) = 3
)
as an alternative to #Kaushik's case.
The fastest method if you want the original rows might be three exists:
select t.*
from test_table t
where exists (select 1
from test_table tt
where tt.analysis_date = t.analysis_date and
tt.test_num = 1
) and
exists (select 1
from test_table tt
where tt.analysis_date = t.analysis_date and
tt.test_num = 2
) and
exists (select 1
from test_table tt
where tt.analysis_date = t.analysis_date and
tt.test_num = 3
);
In particular, this can make use of an index on (analysis_date, test_num).
That said, I think I prefer window functions. Assuming you have no duplicate tests on the same date:
select tt.*
from (select tt.*,
count(*) filter (where test_num in (1, 2, 3)) over (partition by analysis_date) as cnt
from test_table tt
) t
where cnt = 3;

SQL Order By On two columns but same prority

I'm stuck on this simple select and don't know what to do.
I Have this:
ID | Group
===========
1 | NULL
2 | 100
3 | 100
4 | 100
5 | 200
6 | 200
7 | 100
8 | NULL
and want this:
ID | Group
===========
1 | NULL
2 | 100
3 | 100
4 | 100
7 | 100
5 | 200
6 | 200
8 | NULL
all group members keep together, but others order by ID.
I can not write this script because of that NULL records. NULL means that there is not any group for this record.
First you want to order your rows by the minimum ID of their group - or their own ID in case they belong to no group.Then you want to order by ID. That is:
order by min(id) over (partition by case when grp is null then id else grp end), id
If IDs and groups can overlap (i.e. the same number can be used for an ID and for a group, e.g. add a record for ID 9 / group 1 to your sample data) you should change the partition clause to something like
order by min(id) over (partition by case when grp is null
then 'ID' + cast(id as varchar)
else 'GRP' + cast(grp as varchar) end),
id;
Rextester demo: http://rextester.com/GPHBW5600
What about data after a null? In a comment you said don't sort the null.
declare #T table (ID int primary key, grp int);
insert into #T values
(1, NULL)
, (3, 100)
, (5, 200)
, (6, 200)
, (7, 100)
, (8, NULL)
, (9, 200)
, (10, 100)
, (11, NULL)
, (12, 150);
select ttt.*
from ( select tt.*
, sum(ff) over (order by tt.ID) as sGrp
from ( select t.*
, iif(grp is null or lag(grp) over (order by id) is null, 1, 0) as ff
from #T t
) tt
) ttt
order by ttt.sGrp, ttt.grp, ttt.id
ID grp ff sGrp
----------- ----------- ----------- -----------
1 NULL 1 1
3 100 1 2
7 100 0 2
5 200 0 2
6 200 0 2
8 NULL 1 3
10 100 0 4
9 200 1 4
11 NULL 1 5
12 150 1 6

SQL check if group containes certain values of given column (ORACLE)

I have table audit_log with these records:
log_id | request_id | status_id
1 | 2 | 5
2 | 2 | 10
3 | 2 | 20
4 | 3 | 10
5 | 3 | 20
I would like to know if there exists request_ids having status_id 5 and 10 at the same time. So this query should return request_id = 2 as its column status_id has values 5 and 10 (request_id 3 is omitted because status_id column has only value of 10 without 5).
How could I do this with SQL?
I think I should use group by request_id, but I don't know how to check if group has status_id with values 5 and 10?
Thanks,
mismas
This could be a way:
/* input data */
with yourTable(log_id , request_id , status_id) as (
select 1 , 2 , 5 from dual union all
select 2 , 2 , 10 from dual union all
select 3 , 2 , 20 from dual union all
select 4 , 3 , 10 from dual union all
select 5 , 3 , 20 from dual
)
/* query */
select request_id
from yourTable
group by request_id
having count( distinct case when status_id in (5,10) then status_id end) = 2
How it works:
select request_id,
case when status_id in (5,10) then status_id end as checkColumn
from yourTable
gives
REQUEST_ID CHECKCOLUMN
---------- -----------
2 5
2 10
2
3 10
3
So the condition count (distinct ...) = 2 does the work
SELECT request_id
FROM table_name
GROUP BY request_id
HAVING COUNT( CASE status_id WHEN 5 THEN 1 END ) > 0
AND COUNT( CASE status_id WHEN 10 THEN 1 END ) > 0
To check if both values exists (without regard to additional values) you can filter before aggregation:
select request_id
from yourTable
where status_id in (5,10)
group by request_id
having count(*) = 2 -- status_id is unique
-- or
having count(distinct status_id) = 2 -- status_id exists multiple times
This should do it:
select
log5.*, log10.status_id
from
audit_log log5
join audit_log log10 on log10.request_id = log5.request_id
where
log5.status_id = 5
and log10.status_id = 10
order by
log5.request_id
;
Here's the output:
+ ----------- + --------------- + -------------- + -------------- +
| log_id | request_id | status_id | status_id |
+ ----------- + --------------- + -------------- + -------------- +
| 1 | 2 | 5 | 10 |
+ ----------- + --------------- + -------------- + -------------- +
1 rows
And here's the sql to set up the example:
create table audit_log (
log_id int,
request_id int,
status_id int
);
insert into audit_log values (1,2,5);
insert into audit_log values (2,2,10);
insert into audit_log values (3,2,20);
insert into audit_log values (4,3,10);
insert into audit_log values (5,3,20);

PIVOT multiple rows into columns

I have table like below. Consider the query
select invoice_mth, inv_amt from table xdetails
where mobile_number=9080808080
data in the table
mobile_number invoice_mth inv_amt
9080808080 2010-10 20
9080808080 2010-11 30
9080808080 2010-12 40
I have to display the data from table like below.
I want invoice months to separate each month and amt separately.
MOBILE_NUMBER inv_m1 inv_m2 inv_m3 amt1 amt2 amt3
------- ----------------------------------------------------------
9080808080 2010-10 2010-11 2010-12 20 30 40
to display the data like above what I have to do?
You could play around with the standard PIVOT query:
SQL> SELECT * FROM t;
MOBILE_NUMBER INVOICE INV_AMT
------------- ------- ----------
9080808080 2010-10 20
9080808080 2010-11 30
9080808080 2010-12 40
SQL>
SQL> SELECT *
2 FROM
3 (SELECT mobile_number, invoice_mth, inv_amt FROM t
4 ) PIVOT (MIN(invoice_mth) AS inv_mth,
5 SUM(inv_amt) AS inv_amt
6 FOR (invoice_mth) IN ('2010-10' AS m1, '2010-11' AS m2, '2010-12' AS m3))
7 ORDER BY mobile_number;
MOBILE_NUMBER M1_INV_ M1_INV_AMT M2_INV_ M2_INV_AMT M3_INV_ M3_INV_AMT
------------- ------- ---------- ------- ---------- ------- ----------
9080808080 2010-10 20 2010-11 30 2010-12 40
SQL>
If you want a fixed number of columns in the output:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE TABLE_NAME ( mobile_number, invoice_mth, inv_amt ) AS
SELECT 9080808080, '2010-10', 20 FROM DUAL
UNION ALL SELECT 9080808080, '2010-11', 30 FROM DUAL
UNION ALL SELECT 9080808080, '2010-12', 40 FROM DUAL;
Query 1:
SELECT mobile_number,
MAX( CASE RN WHEN 1 THEN invoice_mth END ) AS inv_m1,
MAX( CASE RN WHEN 2 THEN invoice_mth END ) AS inv_m2,
MAX( CASE RN WHEN 3 THEN invoice_mth END ) AS inv_m3,
MAX( CASE RN WHEN 1 THEN inv_amt END ) AS amt1,
MAX( CASE RN WHEN 2 THEN inv_amt END ) AS amt2,
MAX( CASE RN WHEN 3 THEN inv_amt END ) AS amt3
FROM (
SELECT t.*,
ROW_NUMBER() OVER ( PARTITION BY mobile_number ORDER BY invoice_mth ASC ) AS rn
FROM TABLE_NAME t
)
GROUP BY mobile_number
Results:
| MOBILE_NUMBER | INV_M1 | INV_M2 | INV_M3 | AMT1 | AMT2 | AMT3 |
|---------------|---------|---------|---------|------|------|------|
| 9080808080 | 2010-10 | 2010-11 | 2010-12 | 20 | 30 | 40 |