Oracle subselect case - sql

So, there are 2 tables
Table1 (Contains all articles,center,date 00000)
Tabla2 (Contains articles handwritten (that are also in Table1),center, date)
We have a procedure that every day compares Table1 and Table2 articles and center, and if they match, an update changes th Table1 date for that article and center.
Now, we also want to add something, we want that in case center is ' ' (empty) on Tabla2, it updates every center that has that article in Table1.
Here is the OracleSQL:
update Table1 r
set date1= (SELECT max(date2) FROM Tabla2 t
where t.articulo = r.articulo
and t.center = to_char(center) //It gets the center from a select behind
and t.date2 >= to_char(sysdate,'yyyymmdd')
group by t.center);
We want both cases to work
If center has a real center like 20, it only updates center 20.
If center has a empty '' then it updates every center with that article.

You can use:
UPDATE Table1 r
SET date1 = ( SELECT MAX(date2)
KEEP (DENSE_RANK FIRST ORDER BY t.center NULLS LAST)
FROM Tabla2 t
WHERE t.articulo = r.articulo
AND (t.center = r.center OR t.center IS NULL)
AND t.date2 >= TRUNC(sysdate)
);
Note: KEEP (DENSE_RANK LAST... is used to prefer dates from a row with a non-NULL center over rows with a NULL center.
Which, if you have the sample data:
CREATE TABLE table1 (articulo, center, date1) AS
SELECT 1, 1, CAST(NULL AS DATE) FROM DUAL UNION ALL
SELECT 2, 2, NULL FROM DUAL UNION ALL
SELECT 3, 3, NULL FROM DUAL UNION ALL
SELECT 4, 4, NULL FROM DUAL;
CREATE TABLE tabla2 (articulo, center, date2) AS
SELECT 1, 1, DATE '2023-05-19' FROM DUAL UNION ALL
SELECT 2, 2, DATE '2023-01-01' FROM DUAL UNION ALL
SELECT 2, 2, DATE '2023-05-19' FROM DUAL UNION ALL
SELECT 3, 3, DATE '2023-01-01' FROM DUAL UNION ALL
SELECT 3, NULL, DATE '2023-05-19' FROM DUAL UNION ALL
SELECT 4, NULL, DATE '2023-01-01' FROM DUAL UNION ALL
SELECT 4, NULL, DATE '2023-05-19' FROM DUAL;
Then, after the update Table1 contains:
ARTICULO
CENTER
DATE1
1
1
2023-05-19 00:00:00
2
2
2023-05-19 00:00:00
3
3
2023-01-01 00:00:00
4
4
2023-05-19 00:00:00
db<>fiddle here

I would use something like this:
and t.center = nvl(to_char(center),t.center)
If the center is populated, it will use that value. If center is null, the nvl will instead result in the value of t.center. Basically resulting in t.center=t.center (which again means always true).

Related

Find a row that has passed different steps in the same table

Hi to all here it is my problem:
I have an history table where a record register different steps:
Id
Step
Item Code
1
Created
112345
2
Approved
112345
3
Completed
112345
4
Closed
112345
5
Created
112346
6
Approved
112346
8
Closed
112346
What i want to find inside this table is:
All the item codes that have done one step (for example the Approved one) and where the next one is not the "natural one" (for expample the Completed one). In the example table the time code 112346 item has done the Approved step but has skipped the Completed step).
Is there anyway to do a query like this? I've used the PARTITION BY to make a cluster of Item ,for each step, but i am unable to continue the query.
Thanks in advance for any help or suggestion
You can use the LEAD analytic function to check if the next step is not the expected one:
SELECT id, step, item_code
FROM (
SELECT t.*,
LEAD(step) OVER (PARTITION BY item_code ORDER BY id) AS next_step
FROM table_name t
)
WHERE step = 'Approved'
AND (next_step IS NULL OR next_step != 'Completed')
Or, from Oracle 12, you can use MATCH_RECOGNIZE to perform row-by-row processing:
SELECT id, step, item_code
FROM table_name
MATCH_RECOGNIZE(
PARTITION BY item_code
ORDER BY id
ALL ROWS PER MATCH
PATTERN ( approved {- (not_completed|$) -} )
DEFINE
approved AS step = 'Approved',
not_completed AS step <> 'Completed'
)
Which, for the sample data:
CREATE TABLE table_name (Id, Step, Item_Code) AS
SELECT 1, 'Created', 112345 FROM DUAL UNION ALL
SELECT 2, 'Approved', 112345 FROM DUAL UNION ALL
SELECT 3, 'Completed', 112345 FROM DUAL UNION ALL
SELECT 4, 'Closed', 112345 FROM DUAL UNION ALL
SELECT 5, 'Created', 112346 FROM DUAL UNION ALL
SELECT 6, 'Approved', 112346 FROM DUAL UNION ALL
SELECT 8, 'Closed', 112346 FROM DUAL;
Both output:
ID
STEP
ITEM_CODE
6
Approved
112346
db<>fiddle here
Provided you have a table of step names with a proper ordering int column (seqno)
with
/* Sample data */
steps(seqno, Step) as (
select 1, 'Created' from dual union all
select 2, 'Approved' from dual union all
select 3, 'Completed' from dual union all
select 4, 'Closed' from dual
),
tbl(Id, Step, ItemCode) as (
select 1, 'Created' , 112345 from dual union all
select 2, 'Approved' , 112345 from dual union all
select 3, 'Completed', 112345 from dual union all
select 4, 'Closed' , 112345 from dual union all
select 5, 'Created' , 112346 from dual union all
select 6, 'Approved' , 112346 from dual union all
select 8, 'Closed' , 112346 from dual
),
/* Find steps with the next step being out of order */
m as (
select max(seqno)+1 term
from steps
)
select Id, Step, ItemCode
from (
SELECT t.*, s.seqno, lead(s.seqno, 1, (select term from m)) over(partition by ItemCode order by Id) nextSeqno
FROM tbl t
JOIN steps s on s.Step = t.Step
) q
cross join m
where nextseqno != Seqno + 1 and nextseqno < m.term

How to query data which is not unique up to a certain point?

Basically the current conditions of the query are
WHERE data_payload_uri BETWEEN
'/organization/team/folder/2021'
AND
'/organization/team/folder/2022'
And this gets all data for the year of 2021.
A sample of the data_payload_uri data looks like this:
/organization/team/folder/20210101/orig
/organization/team/folder/20210102/orig
/organization/team/folder/20210102/orig_v1
/organization/team/folder/20210103/orig
/organization/team/folder/20210104/orig
/organization/team/folder/20210105/orig
/organization/team/folder/20210105/orig_v1
/organization/team/folder/20210105/orig_v2
What I would like to do is only query the rows where up until the last forward-slash, the row is NOT unique.
What this means, is I want to NOT query the rows which ONLY have one orig
/organization/team/folder/20210101/orig
/organization/team/folder/20210103/orig
/organization/team/folder/20210104/orig
but I DO want to query all the other rows
/organization/team/folder/20210105/orig
/organization/team/folder/20210105/orig_v1
/organization/team/folder/20210105/orig_v2
/organization/team/folder/20210102/orig
/organization/team/folder/20210102/orig_v1
What is the best way to do this? Pls let me know if anything is unclear and thank you for any help
You can use the analytic COUNT function:
SELECT *
FROM (
SELECT t.*,
COUNT(DISTINCT data_payload_uri) OVER (
PARTITION BY SUBSTR(data_payload_uri, 1, INSTR(data_payload_uri, '/', -1))
) AS cnt
FROM table_name t
WHERE data_payload_uri >= '/organization/team/folder/2021'
AND data_payload_uri < '/organization/team/folder/2022'
)
WHERE cnt > 1
Which, for the sample data:
CREATE TABLE table_name (id, data_payload_uri) AS
SELECT 1, '/organization/team/folder/20210101/orig' FROM DUAL UNION ALL
SELECT 2, '/organization/team/folder/20210102/orig' FROM DUAL UNION ALL
SELECT 3, '/organization/team/folder/20210102/orig_v1' FROM DUAL UNION ALL
SELECT 4, '/organization/team/folder/20210103/orig' FROM DUAL UNION ALL
SELECT 5, '/organization/team/folder/20210104/orig' FROM DUAL UNION ALL
SELECT 6, '/organization/team/folder/20210105/orig' FROM DUAL UNION ALL
SELECT 7, '/organization/team/folder/20210105/orig_v1' FROM DUAL UNION ALL
SELECT 8, '/organization/team/folder/20210105/orig_v2' FROM DUAL;
Outputs:
ID
DATA_PAYLOAD_URI
CNT
2
/organization/team/folder/20210102/orig
2
3
/organization/team/folder/20210102/orig_v1
2
6
/organization/team/folder/20210105/orig
3
7
/organization/team/folder/20210105/orig_v1
3
8
/organization/team/folder/20210105/orig_v2
3
db<>fiddle here

Select one column value for other columns based on a condition

I have a table like given below structure, I need to write a query to fetch result like achived month comnet retaine for all remaining months from the table based on a condition if the actual value is greater than or equal to target then select that particular month comments value for remaining months too.My table structure is
I am Expecting the result like the below structure.
Here in June actual value is 100 and comment 'Closed' after thet user will not enter anything(Actual or comments) since actual meets target. so i need to display the commment 'Closed' in all remaining months(July-Dec)
Your expected output is not clear, Please add clarity.
How about this? -
SELECT *
FROM YOUR_TABLE
WHERE MONTHS_ID IN (SELECT MONTHS_ID FROM YOUR_TABLE WHERE ACTUAL_VALUE >= TARGET)
Do you want to aggregate comments by Months_ID?
SELECT MONTHS_ID,
LISTAGG(COMMENTS, ',') WITHIN GROUP(ORDER BY COMMENTS) AS COMMENTS
FROM YOUR_TABLE
WHERE MONTHS_ID IN (SELECT MONTHS_ID FROM YOUR_TABLE WHERE ACTUAL_VALUE >= TARGET)
GROUP BY MONTHS_ID
you can use a where based on you filter condition
select a.comment
from your_table_with_commen a
where a.comment is not null
and a.target is not null
and a.target <= a.actual
Your expected output isn't very clear. But if I got it correctly, then you can achieve the desired result using below query(may not be best performing):
with commentValue as (
select month_id, comments from your_table where actual_value = ( select max(target)
from your_table)
)
select yt.target,yt.month_id,
case when yt.month_id >= cv.month_id then cv.comments else yt.comments end as
comments,
yt.actual_value
from your_table yt
join commentValue cv on 1 = 1
From the explanation, seems you need nothing more than this :
Select month, nvl(comment,'Closed') as comment, target, actual
From tableDemands;
I need to write a query to fetch result like achived month comnet retaine for all remaining months from the table based on a condition if the actual value is greater than or equal to target then select that particular month comments value for remaining months too.
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE table_name ( month, "COMMENT", target, actual ) AS
SELECT 1, 'initiated', NULL, 5 FROM DUAL UNION ALL
SELECT 2, 'feb', NULL, 6 FROM DUAL UNION ALL
SELECT 3, 'Mar- On going', NULL, 10 FROM DUAL UNION ALL
SELECT 4, 'Apr- work On going', NULL, 20 FROM DUAL UNION ALL
SELECT 5, 'May- Ongoing', NULL, 50 FROM DUAL UNION ALL
SELECT 6, 'closed', NULL, 100 FROM DUAL UNION ALL
SELECT 7, NULL, NULL, NULL FROM DUAL UNION ALL
SELECT 8, NULL, NULL, NULL FROM DUAL UNION ALL
SELECT 9, NULL, 100, NULL FROM DUAL UNION ALL
SELECT 10, NULL, NULL, NULL FROM DUAL UNION ALL
SELECT 11, NULL, NULL, NULL FROM DUAL UNION ALL
SELECT 12, NULL, NULL, NULL FROM DUAL;
Query 1:
SELECT month,
"COMMENT",
max_target As target,
actual
FROM (
SELECT t.*,
MAX( target ) OVER () AS max_target
FROM table_name t
)
WHERE actual >= max_target
Results:
| MONTH | COMMENT | TARGET | ACTUAL |
|-------|---------|--------|--------|
| 6 | closed | 100 | 100 |
It seems you want to replace a null comment with the last non-null comment. Use LAST_VALUE for this:
select
month,
last_value(comment ignore nulls) over (order by month) as comment,
target,
actual
from mytable
order by month;

Sql query to only select rows if last entry of item meets condition

I'm looking for the following query in SQL - i.e. select ID from table where entry is within 'last hour' and the last check-in value was 'false'.
Sample Data 'Table1':
ID(int), Check-In(boolean), Name(nvarchar), Entry(DateTime)*, PersonID(int)
*DateTime Format: DD/MM/YYYY HH:MM:SS
1, true, Klaus, 14/05/2015 15:45:21, 100
2, true, Klaus, 14/05/2015 16:05:22, 100
3, false, Klaus, 14/05/2015 16:06:04, 100
4, true, Pete, 14/05/2015 16:20:33, 101
5, false, Michelle, 14/05/2015 16:24:22, 105
6, true, Pete, 14/05/2015 16:25:55, 101
7, false, Pete, 14/05/2015 16:28:44, 101
8, true, Pete, 14/05/2015 16:29:36, 101
Result of Query:
Select ID from Table1 where time = last_hour and (LAST) Check-In was false'
= 3 and 5 (do not select 7)
In the above example, I don't want to select ID 7 as the last check-in of Pete was true (ID 8).
Any ideas how I can achieve that with a SQL query? Is this possible with a simple query?
To make sure the last checkin is false, you must check that there is not a newer row in the table.
You can do that with a "not exists" clause.
Try this one:
select * from table1 t1
where entry > DATE_SUB(NOW(), INTERVAL 1 HOUR)
and checkin = false
and not exists (
select * from table1 t2
where t2.name = t1.name
and t2.entry > t1.entry)
This is other way using partition. I can't run the explain now. But my guess is this version have to do less scans than a exists for each row.
SQL Fiddle Demo
Take note fiddle doesn't have the 1 hour validation
Also sql server bit field is (0,1) not (false, true)
row = 1: Select the last entry for each user
CheckIn = 0 is the CheckIn = False
.
WITH last_entry as (
SELECT *, ROW_NUMBER() OVER(PARTITION BY Name ORDER BY Entry DESC) AS row
FROM table1
WHERE entry > DATE_SUB(NOW(), INTERVAL 1 HOUR)
)
SELECT *
FROM last_entry
WHERE
row = 1
and CheckIn = 0
Using LEAD if you are using SQL Server 2012+ to determine if it was last false attempt.
Demo SqlFiddle
CREATE TABLE tab(
Id INT
,Check_in BIT /* BIT 0 - false, 1 - true) */
,Name NVARCHAR(10)
,Entry DATETIME
,PersonId INT)
SET DATEFORMAT dmy;
INSERT INTO tab(Id, Check_in, Name, Entry, PersonId)
SELECT 1, 1, 'Klaus', '14/05/2015 15:45:21', 100 UNION ALL
SELECT 2, 1, 'Klaus', '14/05/2015 16:05:22', 100 UNION ALL
SELECT 3, 0, 'Klaus', '14/05/2015 16:06:04', 100 UNION ALL
SELECT 4, 1, 'Pete', '14/05/2015 16:20:33', 101 UNION ALL
SELECT 5, 0, 'Michelle', '14/05/2015 16:24:22',105 UNION ALL
SELECT 6, 1, 'Pete', '14/05/2015 16:25:55', 101 UNION ALL
SELECT 7, 0, 'Pete', '14/05/2015 16:28:44', 101 UNION ALL
SELECT 8, 1, 'Pete', '14/05/2015 16:29:36', 101
WITH cte(Id, Next_id, Entry, Name, Check_in, PersonId)
AS
(
SELECT
Id
,LEAD(id, 1, NULL) OVER ( PARTITION BY Name ORDER BY Entry )
,Entry
,Name
,Check_in
,PersonId
FROM tab
WHERE DATEDIFF(minute, Entry, GETDATE()) < 60
)
SELECT Id, Entry, Name, Check_in, PersonId
FROM cte
WHERE check_in = 0
AND [Next_id] IS NULL;

oracle sql - Select ONLY if there are NO null values in that column

I am looking at a checklist that contains several unique checklist items. I only want to select records (ID, name, etc) of those who have NO null values in the checklist date field.
Select distinct sp.id as "ID",
SP.LAST_NAME as "Last",
SP.FIRST_NAME as "First",
SA.TERM_CODE_ENTRY as "Term",
SA.APST_CODE as "Status"
FROM SPRITE SP
JOIN SARC CK
on sp.sprite_pidm = CK.SARC_pidm
JOIN ADAP SA
on sp.sprite_pidm = sa.adap_PIDM
WHERE
Sp.sprite_change_ind is null
and SA.ADAP_TERM_CODE_ENTRY = '201480'
and SA.ADAP_APST_CODE = 'I'
and SA.ADAP_APPL_NO = CK.SARC_APPL_NO
-- where there are no null records - all records should be not null
and CK.SARC_RECEIVE_DATE is not null
Currently, it is selecting those who have at least one not null checklist date. This means it is still selecting records of those who have null dates for some checklist items.
How do tell it to select where
CK.SARC_RECEIVE_DATE = (all checklist item receive_dates must be non-null values)?
Simplified Example:
ID Name Checklist Items DateReceived Other data...
01 Sherry missing item 1
01 Sherry missing item 2 02-02-14
05 Mike missing item 8 02-03-13
17 Carl missing item 2
17 Carl missing item 3
28 Luke missing item 3 04-03-13
28 Luke missing item 5 04-03-13
28 Luke missing item 8 04-03-13
The results should be
05 Mike (other data...)
28 Luke (other data...)
Instead, it is returning
01 Sherry (other data...)
05 Mike (other data...)
28 Luke (other data...)
WITH MYVIEW AS
(
Select sp.id as ID,
SP.LAST_NAME ,
SP.FIRST_NAME,
SA.TERM_CODE_ENTRY,
SA.APST_CODE,
CK.SARC_RECEIVE_DATE As RECEIVED_DATE
FROM SPRITE SP
JOIN SARC CK
on sp.sprite_pidm = CK.SARC_pidm
JOIN ADAP SA
on sp.sprite_pidm = sa.adap_PIDM
WHERE
Sp.sprite_change_ind is null
and SA.ADAP_TERM_CODE_ENTRY = '201480'
and SA.ADAP_APST_CODE = 'I'
and SA.ADAP_APPL_NO = CK.SARC_APPL_NO
)
SELECT ID as "ID",
MAX(LAST_NAME) as "Last",
MAX(FIRST_NAME) as "First",
MAX(TERM_CODE_ENTRY) as "Term",
MAX(APST_CODE) as "Status"
FROM MY_VIEW
GROUP BY id
HAVING SUM(NVL2(RECEIVED_DATE,0,1)) = 0;
You wouldn't do it that way. Instead, use an analytic function to count the number of NULL values and choose the ones that don't have any. Here is the idea:
with t as (
<your query here>
)
select *
from (select t.*, sum(case when SARC_RECEIVE_DATE is null then 1 else 0 end) as numNulls
from t
) t
where numNulls = 0;
I am answering based on the example you provided.
You can use NOT EXISTS also, as you are not selecting from the table SARC.
/*
WITH sprite AS (SELECT 1 sprite_id, 'Sherry' Name FROM dual
UNION ALL SELECT 5, 'Mike' FROM dual
UNION ALL SELECT 17, 'Carl' FROM dual
UNION ALL SELECT 28, 'Luke' FROM dual),
sarc AS (
SELECT 1 sprite_id, 'missing item' checklist, 1 items, null AS dateReceived FROM dual
UNION ALL SELECT 1, 'missing item', 2, to_date('02-02-2014', 'dd-mm-yyyy') FROM dual
UNION ALL SELECT 5, 'missing item', 8, to_date('02-03-2014', 'dd-mm-yyyy') FROM dual
UNION ALL SELECT 17, 'missing item', 2, null FROM dual
UNION ALL SELECT 17, 'missing item', 3, null FROM dual
UNION ALL SELECT 28, 'missing item', 3, to_date('04-03-2014', 'dd-mm-yyyy') FROM dual
UNION ALL SELECT 28, 'missing item', 5, to_date('04-03-2014', 'dd-mm-yyyy') FROM dual
UNION ALL SELECT 28, 'missing item', 8, to_date('04-03-2014', 'dd-mm-yyyy') FROM dual)
-- */
SELECT distinct sp.sprite_id, sp.name
FROM sprite sp
WHERE NOT EXISTS (SELECT 1
FROM sarc sa
WHERE sa.sprite_id = sp.sprite_id
AND dateReceived IS NULL);
The result is exactly the way you want.
I think the following modification to your query should work for you. I'm unsure about the exact results as I don't know your sample data but this should work with a slight modification:
SELECT DISTINCT sp.id AS "ID",
SP.LAST_NAME AS "Last",
SP.FIRST_NAME AS "First",
SA.TERM_CODE_ENTRY AS "Term",
SA.APST_CODE AS "Status"
FROM SPRITE SP
JOIN ADAP SA ON sp.sprite_pidm = sa.adap_PIDM
WHERE Sp.sprite_change_ind IS NULL
AND SA.ADAP_TERM_CODE_ENTRY = '201480'
AND SA.ADAP_APST_CODE = 'I'
AND NOT EXISTS (SELECT 1
FROM sarc ck
WHERE ck.sarc_appl_no = sa.adap_appl_no
AND ck.sarc_receive_date IS NULL);