SQL to identify missing dates in column - sql

Is there a way to generate list of missing dates in table in Oracle?
Input
name,my_date
A,04-JAN-2000
A,05-JAN-2000
A,08-JAN-2000
A,08-JAN-2000 -- duplicates possible
A,10-JAN-2000
B,09-FEB-2001
B,10-FEB-2001
B,05-FEB-2001
Result
A,06-JAN-2000
A,07-JAN-2000
A,09-JAN-2000
B,06-FEB-2001
B,07-FEB-2001
B,08-FEB-2001
After suggestion from #diiN__________ to see Oracle: select missing dates, I managed to get it working for a specific name as follows:
WITH all_dates_wo_boundary_values as
(SELECT oldest + level my_date
FROM (SELECT MIN(my_date) oldest
,MAX(my_date) recent
FROM mytable my
WHERE my.name = 'A'
)
connect by level <= recent - oldest - 1
)
SELECT my_date
FROM all_dates_wo_boundary_values
MINUS
SELECT my_date
FROM mytable my
WHERE my.name = 'A'
How could it be done for multiple names at once?

For multiple names, you can use the LEAD analytic function to find the next date and then CROSS JOIN LATERAL (available from Oracle 12) a row-generator to generate the missing values:
SELECT t.name,
m.missing
FROM (
SELECT name,
dt,
LEAD(dt) OVER (PARTITION BY name ORDER BY dt) AS next_dt
FROM table_name
) t
CROSS JOIN LATERAL (
SELECT dt + LEVEL AS missing
FROM DUAL
WHERE dt + 1 < next_dt
CONNECT BY dt + LEVEL < next_dt
) m
Which, for the sample data:
CREATE TABLE table_name (Name,dt) AS
SELECT 'A', DATE '2000-01-04' FROM DUAL UNION ALL
SELECT 'A', DATE '2000-01-05' FROM DUAL UNION ALL
SELECT 'A', DATE '2000-01-08' FROM DUAL UNION ALL
SELECT 'A', DATE '2000-01-08' FROM DUAL UNION ALL
SELECT 'A', DATE '2000-01-10' FROM DUAL UNION ALL
SELECT 'B', DATE '2001-02-05' FROM DUAL UNION ALL
SELECT 'B', DATE '2001-02-09' FROM DUAL UNION ALL
SELECT 'B', DATE '2001-02-10' FROM DUAL;
Outputs:
NAME
MISSING
A
06-JAN-00
A
07-JAN-00
A
09-JAN-00
B
06-FEB-01
B
07-FEB-01
B
08-FEB-01
db<>fiddle here

Related

Find min date from a column which has values DD-MMM-YY format stored as string

One of the column(file_date) in a table is of type string and has values of dates in DD-MMM-YY format. How do I find min(date) from the column.
I wrote simple query
Select min(file_date) from tablename
It gives output as:
01-Dec-22
But there are dates from earlier months present in the table example 28-Aug-22,31-Oct-22,14-Nov-22
The expected output is
28-Aug-22 as this is the earliest date from which data is present in the table.
Oracle:
WITH -- sample data
tbl (ID, FILE_DATE) AS
(
Select 1, '01-Dec-22' From Dual Union All
Select 1, '21-Jan-22' From Dual Union All
Select 1, '01-Dec-21' From Dual Union All
Select 1, '01-Jan-23' From Dual Union All
Select 1, '10-Dec-20' From Dual Union All
Select 1, '11-Jan-23' From Dual
)
-- your SQL
Select Min(To_Date(FILE_DATE, 'dd-Mon-yy')) "MIN_DATE" From tbl;
MIN_DATE
---------
10-DEC-20
More about here

How can I find the record with id where the previous status Oracle SQL

please help me
How can I find this records with id and status, e.g. "d", but only one that has passed through the previous status in the past, e.g. "b", but not other way
From Oracle 12, you can use MATCH_RECOGNIZE to perform row-by-row comparisons:
SELECT id, status, timestamp
FROM table_name
MATCH_RECOGNIZE(
PARTITION BY id
ORDER BY timestamp
ALL ROWS PER MATCH
PATTERN ( {- b_status any_row*? -} d_status )
DEFINE
b_status AS status = 'b',
d_status AS status = 'd'
)
You can also, in earlier versions, use analytic functions:
SELECT id, status, timestamp
FROM (
SELECT t.*,
COUNT(CASE WHEN status = 'b' THEN 1 END)
OVER (PARTITION BY id ORDER BY timestamp) AS has_b
FROM table_name t
)
WHERE status = 'd'
AND has_b > 0;
Which, for the sample data:
CREATE TABLE table_name (id, status, timestamp) AS
SELECT 100, 'a', DATE '2022-02-01' FROM DUAL UNION ALL
SELECT 100, 'b', DATE '2022-02-02' FROM DUAL UNION ALL
SELECT 100, 'c', DATE '2022-02-03' FROM DUAL UNION ALL
SELECT 100, 'd', DATE '2022-02-04' FROM DUAL UNION ALL
SELECT 200, 'g', DATE '2022-02-05' FROM DUAL UNION ALL
SELECT 200, 's', DATE '2022-02-06' FROM DUAL UNION ALL
SELECT 200, 'd', DATE '2022-02-07' FROM DUAL UNION ALL
SELECT 200, 'a', DATE '2022-02-08' FROM DUAL;
Both output:
ID
STATUS
TIMESTAMP
100
d
2022-02-04 00:00:00
db<>fiddle here
You could also use this solution using EXISTS clause.
select t1.ID, t1.status, t1.timestamp
from Your_Table t1
where t1.status = 'd'
and exists (
select null
from Your_Table t2
where t1.id = t2.id
and t1.timestamp > t2.timestamp
and t2.status in ('b')
)
;
demo on db<>fiddle

Oracle sql: Find min max consecutive dates within same group

I know the question is probably badly explained, but I don't know how else to explain this. I have the following data: (ordered by date)
DATE GROUP
11-Oct-16 A
12-Oct-16 A
13-Oct-16 A
14-Oct-16 B
15-Oct-16 B
16-Oct-16 A
17-Oct-16 A
18-Oct-16 C
19-Oct-16 C
20-Oct-16 C
21-Oct-16 C
22-Oct-16 A
23-Oct-16 A
24-Oct-16 A
I want to find consecutive usage for groups. The results I want will explain this better than me:
GROUP MIN(DATE) MAX(DATE)
A 11-Oct-16 13-Oct-16
B 14-Oct-16 15-Oct-16
A 16-Oct-16 17-Oct-16
C 18-Oct-16 21-Oct-16
A 22-Oct-16 24-Oct-16
Any idea how to do this in oracle sql?
Thank you.
This can be a way:
with test("DATE","GROUP") as
(
select to_date('11-10-16', 'dd-mm-rr'),'A' from dual union all
select to_date('12-10-16', 'dd-mm-rr'),'A' from dual union all
select to_date('13-10-16', 'dd-mm-rr'),'A' from dual union all
select to_date('14-10-16', 'dd-mm-rr'),'B' from dual union all
select to_date('15-10-16', 'dd-mm-rr'),'B' from dual union all
select to_date('16-10-16', 'dd-mm-rr'),'A' from dual union all
select to_date('17-10-16', 'dd-mm-rr'),'A' from dual union all
select to_date('18-10-16', 'dd-mm-rr'),'C' from dual union all
select to_date('19-10-16', 'dd-mm-rr'),'C' from dual union all
select to_date('20-10-16', 'dd-mm-rr'),'C' from dual union all
select to_date('21-10-16', 'dd-mm-rr'),'C' from dual union all
select to_date('22-10-16', 'dd-mm-rr'),'A' from dual union all
select to_date('23-10-16', 'dd-mm-rr'),'A' from dual union all
select to_date('24-10-16', 'dd-mm-rr'),'A' from dual
)
select min("DATE"), max("DATE"), "GROUP"
from (
select "DATE",
"DATE" - row_number() over (partition by "GROUP" order by "DATE") as minDate,
"GROUP"
from test
)
group by "GROUP", minDate
order by "GROUP", minDate
The inner query builds the minimum date for a group of consecutive dates, while the external one simply aggregates by this minimum date, thus building a row for every group of consecutive dates.
As an aside, it's better to avoid using reserved words as column names.
So basically I think that the proper answer was in #mathguy comment.
Here I am just expanding and giving the proper full query.
select GROUP_NAME, grp_id, min(date_field) as starting_date, max(date_field) as ending_date
from (
select DATE_FIELD, GROUP_NAME,
row_number() over ( order by date_field) - row_number() over ( partition by group_name order by date_field ) as grp_id
from darber.my_table) innested
group by GROUP_NAME, grp_id
order by min(date_field);
Please note: I changed the field name because they were giving conflicts on my database. In this solution DATE is now DATE_FIELD and GROUP is now GROUP_NAME.
I found this to be a really smart and elegant solution.
I hope will help others.
I had the same issue with multiple members in the group. In case you are running into the same issue, replace the GROUP_NAME with "groupMember1, groupMember2, ..."

Select unique id with a given code, but not if it has a another row with a different code with a later timestamp

I have table that basically consists of a user id, a code(A or B) and a timestamp.
I need to get a list of unique ids that have a code of A, but only if that same user id does not also have a row with code B with a later timestamp.
Hope that makes sense.
This English query translates into SQL almost verbatim:
get a list of unique ids that have a code of A
SELECT DISTINCT user_id FROM user u WHERE code='A' <...>
but only if that same user id does not also have a row with code B with a later timestamp
<...> AND NOT EXISTS (
SELECT *
FROM user ou
WHERE ou.user_id=u.user_id AND ou.code='B' AND ou.time_stamp > u.time_stamp
)
The trick to the translation us the use of aliases: u stands for "User", while ou stands for "Other user". Hence ou.user_id=u.user_id AND ou.code='B' AND ou.time_stamp > u.time_stamp means "another user with the same id, code of 'B', and a later timestamp".
This will get the result with only a single table scan:
Oracle Setup:
CREATE TABLE table_name ( user_id, code, time ) AS
SELECT 1, 'A', TIMESTAMP '2016-02-01 00:00:00' FROM DUAL UNION ALL
SELECT 1, 'A', TIMESTAMP '2016-02-02 00:00:00' FROM DUAL UNION ALL
SELECT 2, 'A', TIMESTAMP '2016-02-01 00:00:00' FROM DUAL UNION ALL
SELECT 2, 'B', TIMESTAMP '2016-02-02 00:00:00' FROM DUAL UNION ALL
SELECT 3, 'B', TIMESTAMP '2016-02-01 00:00:00' FROM DUAL UNION ALL
SELECT 3, 'A', TIMESTAMP '2016-02-02 00:00:00' FROM DUAL;
Query:
SELECT DISTINCT user_id
FROM (
SELECT user_id,
code,
LEAD( CASE code WHEN 'B' THEN 1 END )
IGNORE NULLS
OVER ( PARTITION BY user_id ORDER BY time ASC )
AS has_b
FROM TABLE_NAME
)
WHERE code = 'A'
AND has_b IS NULL;
Output:
USER_ID
----------
1
3
SELECT userId FROM tblName WHERE code = 'A' AND WHERE userId =
(SELECT userId FROM tblName WHERE code != 'B')

How to get query to return rows where first three characters of one row match another row?

Here's my data:
with first_three as
(
select 'AAAA' as code from dual union all
select 'BBBA' as code from dual union all
select 'BBBB' as code from dual union all
select 'BBBC' as code from dual union all
select 'CCCC' as code from dual union all
select 'CCCD' as code from dual union all
select 'FFFF' as code from dual union all
select 'GFFF' as code from dual )
select substr(code,1,3) as r1
from first_three
group by substr(code,1,3)
having count(*) >1
This query returns the characters that meet the cirteria. Now, how do I select from this to get desired results? Or, is there another way?
Desired Results
BBBA
BBBB
BBBC
CCCC
CCCD
WITH code_frequency AS (
SELECT code,
COUNT(1) OVER ( PARTITION BY SUBSTR( code, 1, 3 ) ) AS frequency
FROM table_name
)
SELECT code
FROM code_frequency
WHERE frequency > 1
WITH first_three AS (
...
)
SELECT *
FROM first_three f1
WHERE EXISTS (
SELECT 1 FROM first_three f2
WHERE f1.code != f2.code
AND substr(f1.code, 1, 3) = substr(f2.code, 1, 3)
)
select res from (select res,count(*) over
(partition by substr(res,1,3) order by null) cn from table_name) where cn>1;