Query for dates which are not present in a table - sql

Consider a table ABC which has a column of date type.
How can we get all the dates of a range (between start date and end date) which are not present in the table.
This can be done in PLSQL.I am searching a SQL query for it.

You need to generate the arbitrary list of dates that you want to check for:
http://hashfactor.wordpress.com/2009/04/08/sql-generating-series-of-numbers-in-oracle/
e.g.:
-- generate 1..20
SELECT ROWNUM N FROM dual
CONNECT BY LEVEL <= 20
Then left join with your table, or use a where not exists subquery (which will likely be faster) to fetch the dates amongst those you've generated that contains no matching record.

Assuming that your table's dates do not include a time element (ie. they are effectively recorded as at midnight), try:
select check_date
from (select :start_date + level - 1 check_date
from dual
connect by level <= 1 + :end_date - :start_date) d
where not exists
(select null from mytable where mydate = check_date)

Given a date column in order to do this you need to generate a list of all possible dates between the start and end date and then remove those dates that already exist. As Mark has already suggested the obvious way to generate the list of all dates is to use a hierarchical query. You can also do this without knowing the dates in advance though.
with the_dates as (
select date_col
from my_table
)
, date_range as (
select max(date_col) as maxdate, min(date_col) as mindate
from the_dates
)
select mindate + level
from date_range
connect by level <= maxdate - mindate
minus
select date_col
from the_dates
;
Here's a SQL Fiddle
The point of the second layer of the CTE is to have a "table" that has all the information you need but is only one row so that the hierarchical query will work correctly.

Related

Retrieve date from subtracting variable number of days from date on calendar table

In our system we have a created table that lists all the days that extends out to 20230 with a special field to specify a holiday/weekend.
SAMPLE BELOW:
DATE_FIELD HOLIDAY_FIELD
20200430 N
20200501 N
20200502 Y
20200503 Y
20200504 N
20200505 N
20200506 N
20200507 N
..............
My goal is to provide a date variable and subtract x number of days from the provided date.
The number of days is not a constant field, it can be different so FETCH and LIMIT wont work.
Ive already tried the code below and it works just as i want it if i always want to subtract 5 days from the given date:
select date_field
from table.calendar
where date_field <= '20200507' and holiday_field = 'N'
order by date_field desc
LIMIT 5,1
This will give me the result I want '20200430' because it skips the weekends.
However I want to be able to do something like below:
select date_field
from table.calendar
where date_field <= (variable date) and holiday_field = 'N'
order by date_field desc
LIMIT (variable n),1
But from what Ive read you cannot specify a variable for a fetch or limit.
Also to add this select statement will be used in a sub select.
So it most likely use as it is below:
SELECT table1.*,
( select date_field
from table.calendar
where date_field <= (table1.date) and holiday_field = 'N'
order by date_field desc
LIMIT (table1.days n),1 ) AS DATE
from table1
order by table1.date
Ive tried using row_number() but have no clue on how to pass the date and days variable.
This would start from the absolute top of the list and go down. I need it to start from a specific date.
with CALENDAR AS(
SELECT x.* FROM (
select date_field
row_number() over() as rownum
from table.calendar X
where holiday_field = 'N'
order by date_field
) AS t
)
select table1.*, A.date_field
from table1
left join CALENDAR A on A.date_field <= table1.date and A.rownum = 5
I also understand i could easily do this in a user created function but my ultimate goal is to produce a sql views to export to a 3rd party software. Their is severe performance slow down when using user functions in sql views.
Any suggestions?
The solution with global variables.
CREATE OR REPLACE VARIABLE GV_DATE_FIELD VARCHAR(8) DEFAULT (TO_CHAR(CURRENT DATE, 'YYYYMMDD'));
CREATE OR REPLACE VARIABLE GV_DAYS INT DEFAULT 5;
CREATE OR REPLACE VIEW CALENDAR_V AS
select date_field
from
(
select date_field, rownumber() over (order by date_field desc) rn
from calendar
where date_field <= GV_DATE_FIELD and holiday_field = 'N'
)
where rn = GV_DAYS;
-- GV_DATE_FIELD == TO_CHAR(CURRENT DATE, 'YYYYMMDD')
-- GV_DAYS == 5
select * from CALENDAR_V;
SET GV_DAYS = 4;
-- GV_DATE_FIELD == TO_CHAR(CURRENT DATE, 'YYYYMMDD')
-- GV_DAYS == 4
select * from CALENDAR_V;
This couple of global variables are set for every session to their default values and work as parameters.
You may set their values explicitly (with the SET statement as described) before running the statement using them (the CALENDAR_V view in this case) to get the corresponding result.

map/sample timeseries data to another timeserie db2

I am trying to combine the results of two SQL (DB2 on IBM bluemix) queries:
The first query creates a timeserie from startdate to enddate:
with dummy(minute) as (
select TIMESTAMP('2017-01-01')
from SYSIBM.SYSDUMMY1 union all
select minute + 1 MINUTES
from dummy
where minute <= TIMESTAMP('2018-01-01')
)
select to_char(minute, 'DD.MM.YYYY HH24:MI') AS minute
from dummy;
The second query selects data from a table which have a timestamp. This data should be joined to the generated timeseries above. The standalone query is like:
SELECT DISTINCT
to_char(date_trunc('minute', TIMESTAMP), 'DD.MM.YYYY HH24:MI') AS minute,
VALUE AS running_ct
FROM TEST
WHERE ID = 'abc'
AND NAME = 'sensor'
ORDER BY minute ASC;
What I suppose to get is a query with one result with contains of two columns:
first column with the timestamp from startdate to enddate and
the second with values which are sorted by there own timestamps to the
first column (empty timestamps=null).
How could I do that?
A better solution, especially if your detail table is large, is to generate a range. This allows the optimizer to use indices to fulfill the bucketing, instead of calling a function on every row (which is expensive).
So something like this:
WITH dummy(temporaer, rangeEnd) AS (SELECT a, a + 1 MINUTE
FROM (VALUES(TIMESTAMP('2017-12-01'))) D(a)
UNION ALL
SELECT rangeEnd, rangeEnd + 1 MINUTE
FROM dummy
WHERE rangeEnd < TIMESTAMP('2018-01-31'))
SELECT Dummy.temporaer, AVG(Test.value) AS TEXT
FROM Dummy
LEFT OUTER JOIN Test
ON Test.timestamp >= Dummy.temporaer
AND Test.timestamp < Dummy.rangeEnd
AND Test.id = 'abc'
AND Test.name = 'text'
GROUP BY Dummy.temporaer
ORDER BY Dummy.temporaer ASC;
Note that the end of the range is now exclusive, not inclusive like you had it before: you were including the very first minute of '2018-01-31', which is probably not what you wanted. Of course, excluding just the last day of a month also strikes me as a little strange - you most likely really want < TIMESTAMP('2018-02-01').
found a working solution:
with dummy(temporaer) as (
select TIMESTAMP('2017-12-01') from SYSIBM.SYSDUMMY1
union all
select temporaer + 1 MINUTES from dummy where temporaer <= TIMESTAMP('2018-01-31'))
select temporaer, avg(VALUE) as text from dummy
LEFT OUTER JOIN TEST ON temporaer=date_trunc('minute', TIMESTAMP) and ID='abc' and NAME='text'
group by temporaer
ORDER BY temporaer ASC;
cheers

Find the date after a gap in date range in sql

I have these date ranges that represent start and end dates of subscription. There are no overlaps in date ranges.
Start Date End Date
1/5/2015 - 1/14/2015
1/15/2015 - 1/20/2015
1/24/2015 - 1/28/2015
1/29/2015 - 2/3/2015
I want to identify delays of more than 1 day between any subscription ending and a new one starting. e.g. for the data above, i want the output: 1/24/2015 - 1/28/2015.
How can I do this using a sql query?
Edit : Also there can be multiple gaps in the subscription date ranges but I want the date range after the latest one.
You do this using a left join or not exists:
select t.*
from t
where not exists (select 1
from t t2
where t2.enddate = dateadd(day, -1, t.startdate)
);
Note that this will also give you the first record in the sequence . . . which, strictly speaking, matches the conditions. Here is one solution to that problem:
select t.*
from t cross join
(select min(startdate) as minsd from t) as x
where not exists (select 1
from t t2
where t2.enddate = dateadd(day, -1, t.startdate)
) and
t.startdate <> minsd;
You can also approach this with window functions:
select t.*
from (select t.*,
lag(enddate) over (order by startdate) as prev_enddate,
min(startdate) over () as min_startdate
from t
) t
where minstartdate <> startdate and
enddate <> dateadd(day, -1, startdate);
Also note that this logic assumes that the time periods do not overlap. If they do, a clearer problem statement is needed to understand what you are really looking for.
You can achieve this using window function LAG() that would get value from previous row in ordered set for later comparison in WHERE clause. Then, in WHERE you just apply your "gapping definition" and discard the first row.
SQL FIDDLE - Test it!
Sample data:
create table dates(start_date date, end_date date);
insert into dates values
('2015-01-05','2015-01-14'),
('2015-01-15','2015-01-20'),
('2015-01-24','2015-01-28'), -- gap
('2015-01-29','2015-02-03'),
('2015-02-04','2015-02-07'),
('2015-02-09','2015-02-11'); -- gap
Query
SELECT
start_date,
end_date
FROM (
SELECT
start_date,
end_date,
LAG(end_date, 1) OVER (ORDER BY start_date) AS prev_end_date
FROM dates
) foo
WHERE
start_date IS DISTINCT FROM ( prev_end_date + 1 ) -- compare current row start_date with previous row end_date + 1 day
AND prev_end_date IS NOT NULL -- discard first row, which has null value in LAG() calculation
I assume that there are no overlaps in your data and that there are unique values for each pair. If that's not the case, you need to clarify this.

Spread a table in a date time interval

Hello everyone it's been some days that I use sql to make analysis and I meet all kinds of problems that I solves thanks to your forum.
Now I'd like to create a view that recuperates the interval of time and shows in detail the dates in this interval.
I have the following table:
And I want to create the view that displays the result:
For example in the player1 MyTable to play five days from 01/01/2012
to 05/01/2012. So the view displays 5 lines for player1 with the date 01/01/2012, 02/01/2012, 03/01/2012, 04/01/2012, 05/01/2012.
Thank you in advance for your help.
You have to create a common table expression that give you the date range ( i have created a date range of the current month but you can choice another range) :
WITH DateRange(dt) AS
(
SELECT CONVERT(datetime, '2012-01-01') dt
UNION ALL
SELECT DATEADD(dd,1,dt) dt FROM DateRange WHERE dt < CONVERT(datetime, '2012-01-31')
)
SELECT dates.dt AS DatePlaying, PlayerName
FROM MyTable t
JOIN DateRange dates ON dt BETWEEN t.BeginDate AND t.DateEnd
ORDER BY PlayerName, DatePlaying
Another approach to this is simply to create an enumeration table to add values to dates:
with enumt as (select row_number() over (order by (select NULL)) as seqnum
from mytable
)
select dateadd(d, e.seqnum, mt.DateBegin) as DatePlaying, mt.PlayerName
from MyTable mt join
enum e
on enumt.seqnum <= e.NumberOfPlayingDay
The only purpose of the "with" clause is to generate a sequence of integers starting at 1.

SQL to identify missing week

I have a database table with the following structure -
Week_End Sales
2009-11-01 43223.43
2009-11-08 4324.23
2009-11-15 64343.23
...
Week_End is a datetime column, and the date increments by 7 days with each new entry.
What I want is a SQL statement that will identify if there is a week missing in the sequence. So, if the table contained the following data -
Week_End Sales
2009-11-01 43223.43
2009-11-08 4324.23
2009-11-22 64343.73
...
The query would return 2009-11-15.
Is this possible? I am using SQL Server 2008, btw.
You've already accepted an answer so I guess you don't need this, but I was almost finished with it anyway and it has one advantage that the selected solution doesn't have: it doesn't require updating every year. Here it is:
SELECT T1.*
FROM Table1 T1
LEFT JOIN Table1 T2
ON T2.Week_End = DATEADD(week, 1, T1.Week_End)
WHERE T2.Week_End IS NULL
AND T1.Week_End <> (SELECT MAX(Week_End) FROM Table1)
It is based on Andemar's solution, but handles the changing year too, and doesn't require the existence of the Sales column.
Join the table on itself to search for consecutive rows:
select a.*
from YourTable a
left join YourTable b
on datepart(wk,b.Week_End) = datepart(wk,a.Week_End) + 1
-- No next week
where b.sales is null
-- Not the last week
and datepart(wk,a.Week_End) <> (
select datepart(wk,max(Week_End)) from YourTable
)
This should return any weeks without a next week.
Assuming your "week_end" dates are always going to be the Sundays of the week, you could try a CTE - a common table expression that lists out all the Sundays for 2009, and then do an outer join against your table.
All those rows missing from your table will have a NULL value for their "week_end" in the select:
;WITH Sundays2009 AS
(
SELECT CAST('20090104' AS DATETIME) AS Sunday
UNION ALL
SELECT
DATEADD(DAY, 7, cte.Sunday)
FROM
Sundays2009 cte
WHERE
DATEADD(DAY, 7, cte.Sunday) < '20100101'
)
SELECT
sun.Sunday 'Missing week end date'
FROM
Sundays2009 sun
LEFT OUTER JOIN
dbo.YourTable tbl ON sun.Sunday = tbl.week_end
WHERE
tbl.week_end IS NULL
I know this has already been answered, but can I suggest something really simple?
/* First make a list of weeks using a table of numbers (mine is dbo.nums(num), starting with 1) */
WITH AllWeeks AS (
SELECT DATEADD(week,num-1,w.FirstWeek) AS eachWeek
FROM
dbo.nums
JOIN
(SELECT MIN(week_end) AS FirstWeek, MAX(week_end) as LastWeek FROM yourTable) w
ON num <= DATEDIFF(week,FirstWeek,LastWeek)
)
/* Now just look for ones that don't exist in your table */
SELECT w.eachWeek AS MissingWeek
FROM AllWeeks w
WHERE NOT EXISTS (SELECT * FROM yourTable t WHERE t.week_end = w.eachWeek)
;
If you know the range you want to look over, you don't need to use the MIN/MAX subquery in the CTE.