Postgres query to fetch results between two dates - sql

This is example:
create table test (
id int,
name varchar(6),
start_date date,
end_date date
);
insert into test (id,name, start_date, end_date) values (1,'aaa', '2014-07-01', '2014-07-30');
insert into test (id,name, start_date, end_date) values (2,'bbb', '2014-07-01', '2014-08-30');
insert into test (id,name, start_date, end_date) values (3,'ccc', '2014-08-01', '2014-08-30');
insert into test (id,name, start_date, end_date) values (4,'ddd', '2014-08-16', '2014-08-30');
insert into test (id,name, start_date) values (5,'eee', '2014-07-01');
insert into test (id,name, start_date) values (6,'fff', '2014-08-16');
I need write query where result will be:
2;"bbb";"2014-07-01";"2014-08-30"
3;"ccc";"2014-08-01";"2014-08-30"
4;"ddd";"2014-08-16";"2014-08-30"
5;"eee";"2014-07-01";""
6;"fff";"2014-08-16";""
I've written this:
select * from test
where (start_date >= '2014-08-15' and (end_date <= current_Date or end_date is null)) or
(start_date <= '2014-08-15' and end_date is null) ;
But I see only ddd,eee and fff records.

Instead of the compare operators, you can actually use PostgreSQL's own detection of date (& time) overlaping. There are actually 2 constructs:
1) The SQL compatible OVERLAPS operator:
(start1, end1) OVERLAPS (start2, end2)
(start1, length1) OVERLAPS (start2, length2)
This one does not handle well an open-ended date-period, but you can use the the special infinity date for that.
select *
from test
where (date '2014-08-15', current_date) overlaps
(start_date, coalesce(end_date, date 'infinity'));
As far as I know, you can't use any index to speed up the query above.
Also:
Each time period is considered to represent the half-open interval start <= time < end, unless start and end are equal in which case it represents that single time instant.
2) Using the daterange type:
Ranges can be unbounded, and you can explicitly set how to handle range bounds (to include them or not).
select *
from test
where daterange(start_date, end_date, '[]') &&
daterange(date '2014-08-15', current_date, '[]');
Also, you can speed up this query, by applying a gist index on the daterange expression:
create index on test using gist ((daterange(start_date, end_date, '[]')));
SQLFiddle

I am guessing (intelligently) that you want any period that overlaps with 2014-08-15 to the present date. If so, this logic returns what you want:
select *
from test
where (start_date >= '2014-08-15' and (end_date <= current_Date or end_date is null)) or
(start_date <= '2014-08-15' and (end_date > '2014-08-15' or end_date is null));
Here is a SQL Fiddle.

Related

Problem with date matching between a variable and a date column Oracle SQL

The problem is that this query is working fine:
CREATE TABLE PROCESGEN_TEST
(PROCESENDTIME TIMESTAMP);
INSERT INTO PROCESGEN_TEST
(SELECT DISTINCT PROCESENDTIME FROM dwh_procesgeneriek#xob10
WHERE PROCESENDTIME IS NOT NULL
AND PROCESENDTIME > '10-09-2020 01:00:00');
Def TIME2 = (SELECT MAX_EXEC_TIME FROM EXEC_TIME);
SELECT PROCESENDTIME
FROM PROCESGEN_TEST
WHERE PROCESENDTIME < &TIME2
AND PROCESEINDTIJD IS NOT NULL
In the above situation we first put the data into a table created in de database management system we use (named xor01 and not the xob10). In the query beneath we extract the data directly from xob10. This isn’t working when we want to select a date (greater or lower then..) and we don’t know why.
CREATE TABLE EXEC_TIME
(MAX_EXEC_TIME DATE);
INSERT INTO EXEC_TIME
(
SELECT TO_DATE(
TO_CHAR(
MAX(EXEC_DATE),
'DD-MM-YYYY HH24:MI:SS'
),
'DD-MM-YYYY HH24:MI:SS'
) - 1.1666
from L3DD_MIN_ACTIVITIES_BRD_BAK_Will
);
Def TIME3 = (SELECT MAX_EXEC_TIME FROM EXEC_TIME);
SELECT PROCESENDTIME
FROM dwh_procesgeneriek#xob10
WHERE PROCESENDTIME IS NOT NULL
AND TO_DATE(PROCESENDTIME,'DD-MM-YY HH24:MI:SS')
> TO_DATE(&TIME3, 'DD-MM-YY HH24:MI:SS');
The problem is that the query is not finding a single date in the last query and keeps on executing. If we replace TO_DATE(&TIME3, 'DD-MM-YY HH24:MI:SS') with a certain date like '10-08-2020 20:00:00' the query will find the right dates. We have tried all kinds of things, like working with TIMESTAMP format and TO_TIMESTAMP. Nothing works. It looks like a rather simple problem.
Does anyone know what’s causing the problem the query can’t find any dates in the second query?
You don't need:
the EXEC_TIME table;
to convert a timestamp to a string and then back to a date;
to use a variable; or
to filter on PROCESENDTIME IS NOT NULL (since the > filter only works on non-NULL values).
Then you can use:
SELECT PROCESENDTIME
FROM dwh_procesgeneriek#xob10
WHERE PROCESENDTIME
> (
SELECT MAX(EXEC_DATE) - INTERVAL '1 4' DAY TO HOUR
FROM L3DD_MIN_ACTIVITIES_BRD_BAK_Will
);
If you do want the EXEC_TIME table then:
CREATE TABLE EXEC_TIME( MAX_EXEC_TIME DATE );
INSERT INTO EXEC_TIME
SELECT MAX(EXEC_DATE) - INTERVAL '1 4' DAY TO HOUR
FROM L3DD_MIN_ACTIVITIES_BRD_BAK_Will;
SELECT PROCESENDTIME
FROM dwh_procesgeneriek#xob10
WHERE PROCESENDTIME > ( SELECT MAX_EXEC_TIME FROM EXEC_TIME );

fails in user acceptance

create table Minutes(Minute varchar2(5));
create table orders(OrderID varchar(54), Orderplaced TIMESTAMP ,
Ordercompleted TIMESTAMP);
insert into orders
VALUES
('#1',TO_TIMESTAMP('2018-01-15 00:12:20', 'YYYY-MM-DD HH24:MI:SS'),
TO_TIMESTAMP( '2018-01-15 00:12:42', 'YYYY-MM-DD HH24:MI:SS'));
insert into orders
VALUES
('#2',TO_TIMESTAMP('2018-01-15 01:15:20', 'YYYY-MM-DD HH24:MI:SS'),
TO_TIMESTAMP( '2018-01-15 02:56:20', 'YYYY-MM-DD HH24:MI:SS'));
insert into orders
VALUES
('#3',TO_TIMESTAMP('2018-01-15 01:20:20', 'YYYY-MM-DD HH24:MI:SS'),
TO_TIMESTAMP( '2018-01-15 03:00:20', 'YYYY-MM-DD HH24:MI:SS'));
insert into Minutes (Minute)
select to_char(trunc(sysdate) + interval '1' minute * (level - 1),
'HH24:MI') as minute
from dual
connect by level <= 1440;
select a.Minute, nvl(count(b.OrderID),0) as orders
from Minutes a
left join orders b
on a.Minute between to_char(cast( b.Orderplaced as date),'hh24:mi:ss') and
to_char(cast( b.Ordercompleted as date),'hh24:mi:ss')
where
a.Minute <= (select to_char(cast (sysdate as date),'hh24:mi:ss') from dual)
group by a.Minute
order by 1;
The processing time is too long and the result is undelivered as well.
It works fine with Integration testing. Please have a look once.
I ran your code, works OK for those test tables. However, I'd suggest a slight modification.
you don't have to CAST values from ORDERS table
Even worse is to CAST SYSDATE AS DATE - SYSDATE is a function that
returns DATE data type anyway
there's no need to select it from DUAL - you can use it "as is"
COUNT will return 0 even if there's nothing to return, so you can
omit NVL function
Here's the modified SELECT statement:
SELECT a.minute, COUNT (b.orderid) AS orders
FROM minutes a
LEFT JOIN orders b
ON a.minute BETWEEN TO_CHAR (b.orderplaced, 'hh24:mi:ss')
AND TO_CHAR (b.ordercompleted, 'hh24:mi:ss')
WHERE a.minute <= TO_CHAR (SYSDATE, 'hh24:mi:ss')
GROUP BY a.minute
ORDER BY 1;
What does it mean for you? I don't know. As I said, it works OK. Explain plan says that it performs full table scan of both MINUTES and ORDERS tables, so - if there's zillion rows in those tables, it might make a difference.
Consider creating indexes on columns you use; as you extract only time from the ORDERS table, those two would be function-based ones.
CREATE INDEX i1_min
ON minutes (minute);
CREATE INDEX i2_plac
ON orders (TO_CHAR (orderplaced, 'hh24:mi:ss'));
CREATE INDEX i3_compl
ON orders (TO_CHAR (ordercompleted, 'hh24:mi:ss'));
Then try again; hopefully, you'll see some improvement.
You said you're trying to get "the number of orders count per minute on a particular day", and later clarified that should be the current day. Your query is only looking at times - converted to strings - so it's looking at the same time slot across all records in your orders table. Really you want to restrict the found orders to the day you're interested in. Presumably your UAT environment just has much more data, across more days, than you created in IT.
You could just add a filter to restrict it to orders placed today:
select a.Minute, nvl(count(b.OrderID),0) as orders
from Minutes a
left join orders b
on a.Minute between to_char(cast( b.Orderplaced as date),'hh24:mi:ss') and
to_char(cast( b.Ordercompleted as date),'hh24:mi:ss')
and b.Orderplaced > trunc(sysdate) -- added this filter
where
a.Minute <= (select to_char(cast (sysdate as date),'hh24:mi:ss') from dual)
group by a.Minute
order by 1;
though you don't need any of the casting or subquery or nvl(), as #Littlefoot mentioned, so can simplify that a bit to:
select a.Minute, count(b.OrderID) as orders
from Minutes a
left join orders b
on a.Minute between to_char(b.Orderplaced,'hh24:mi:ss') and
to_char(b.Ordercompleted,'hh24:mi:ss')
and b.Orderplaced > trunc(sysdate)
where a.Minute <= to_char(sysdate,'hh24:mi:ss')
group by a.Minute
order by 1;
You're still doing a lot of conversions and comparing strings rather than dates/timestamps. It might be simpler to generate the minutes for that specific day in a CTE instead of a permanent table, and join using those values as well, without doing any further data conversions
with minutes (minute) as (
select cast(trunc(sysdate) as timestamp) + interval '1' minute * (level - 1)
from dual
connect by level <= (sysdate - trunc(sysdate)) * 1440
)
select to_char(m.minute, 'HH24:MI') as minute, count(o.orderid) as orders
from minutes m
left join orders o
on o.orderplaced >= cast(trunc(sysdate) as timestamp)
and o.orderplaced <= m.minute
and (o.ordercompleted is null or o.ordercompleted >= m.minute)
group by m.minute
order by m.minute;
I've included rows with no ordercompleted date, though it isn't clear if you want to count those.
You could also join on just the orderplaced date being today, which looks a bit odd, and do a conditional count:
with minutes (minute) as (
select cast(trunc(sysdate) as timestamp) + interval '1' minute * (level - 1)
from dual
connect by level <= (sysdate - trunc(sysdate)) * 1440
)
select to_char(m.minute, 'HH24:MI') as minute,
count(case when o.orderplaced <= m.minute
and (o.ordercompleted is null or o.ordercompleted >= m.minute)
then o.orderid end) as orders
from minutes m
left join orders o
on o.orderplaced >= cast(trunc(sysdate) as timestamp)
group by m.minute
order by m.minute;
Either way this assumes you have an index on orderplaced.
Look at the execution plans for your original query and these options and any others suggested, and test with realistic data, to see which is the best approach for your actual data and requirements.
To look for records for a different, full, day, change the sysdate references to a date/timestamp literal like timestamp '2018-01-15 00:00:00' or something relative like trunc(sysdate-1), and include an end-date on the orderplaced; and remove the end-time filter in the CTE; e.g.:
with minutes (minute) as (
select cast(trunc(sysdate - 1) as timestamp) + interval '1' minute * (level - 1)
from dual
connect by level <= 1440
)
select to_char(m.minute, 'HH24:MI') as minute, count(o.orderid) as orders
from minutes m
left join orders o
on o.orderplaced >= cast(trunc(sysdate - 1) as timestamp)
and o.orderplaced < cast(trunc(sysdate - 1) as timestamp) + interval '1' day
and o.orderplaced <= m.minute
and (o.ordercompleted is null or o.ordercompleted >= m.minute)
group by m.minute
order by m.minute;
or
with minutes (minute) as (
select timestamp '2018-01-15 00:00:00' + interval '1' minute * (level - 1)
from dual
connect by level <= 1440
)
select to_char(m.minute, 'HH24:MI') as minute, count(o.orderid) as orders
from minutes m
left join orders o
on o.orderplaced >= timestamp '2018-01-15 00:00:00'
and o.orderplaced < timestamp '2018-01-16 00:00:00'
and o.orderplaced <= m.minute
and (o.ordercompleted is null or o.ordercompleted >= m.minute)
group by m.minute
order by m.minute;
If you want to include rows where the placed and completed times are in the same minute, but still otherwise want to exclude rows from the minute they were placed, you'll need a bit more logic; maybe something like:
with minutes (minute) as (
select timestamp '2018-01-15 00:00:00' + interval '1' minute * (level - 1)
from dual
connect by level <= 1440
)
select to_char(m.minute, 'HH24:MI') as minute, count(o.orderid) as orders
from minutes m
left join orders o
on o.orderplaced >= timestamp '2018-01-15 00:00:00'
and o.orderplaced < timestamp '2018-01-16 00:00:00'
and ((trunc(o.ordercompleted, 'MI') > trunc(o.orderplaced, 'MI')
and o.orderplaced <= m.minute)
or (trunc(o.ordercompleted, 'MI') = trunc(o.orderplaced, 'MI')
and o.orderplaced < m.minute + interval '1' minute))
and (o.ordercompleted is null or o.ordercompleted >= m.minute)
group by m.minute
order by m.minute;
If you need further refinements you'll need to modify the clauses to suit, which might need a bit of experimentation.

SQL date range query from two columns(start_date and end_date)

I have two columns in my table start_date and end_date and my query inputs two values input_start_date and input_end_date.
I want the data from input_start_date to input_end_date by querying start_date and end_date.
I am able to achieve this by following query:
SELECT * FROM table-name where (
(start_date >= input_start_date and end_date <= input_end_date) or
(start_date <= input_start_date and end_date >= input_end_date) or
(start_date >= input_start_date and start_date <=input_end_date) or
(end_date >= input_start_date and end_date <= input_end_date)
);
Is there any better way of doing it ?
SELECT column-names
FROM table-name
WHERE column-name BETWEEN value1 AND value2
This one is for between one date
SELECT column-names
FROM table-name
WHERE start_date = input_start_date AND end_date = input_end_date

Calculate time gaps in SQL vertica

I have the following data set:
(StartTime,EndTime)
(2,2.30)
(3,4.30)
(5,6)
I need to consider the time gaps in between
What I need:
(2,2.30)
(2.30,3)
(3,4.30)
(4.30,5)
(5,6)
How can I achieve this in SQL.
I am using vertica database.
You'll basically need to use LEAD to generate the rows in between. There is also time series for GFI but it doesn't really fit this problem too well honestly because you are dealing with ranges of time, not static slices.
CREATE TABLE mytable (StartTime interval,EndTime interval)
INSERT into mytable VALUES ( INTERVAL '2 minutes', INTERVAL '2.5 minutes');
INSERT into mytable VALUES ( INTERVAL '3 minutes', INTERVAL '4.5 minutes');
INSERT into mytable VALUES ( INTERVAL '5 minutes', INTERVAL '6 minutes');
SELECT StartTime, EndTime
from mytable
union all
SELECT EndTime, lead (StartTime, 1) OVER (order by StartTime)
from mytable
order by 1
You'll end up with a record at the end indicating an open range which you can filter out if you don't want it. You can also add in another union to get a similar before time record as well if you want that.

Finding Members between a Date Range

I need to find Membership between two dates ( 7-01-14 and 6-30-15) who have been members at least 4 months or more during that time frame.There is a START_DATE and an END_DATE column. Any advice would be appreciated. Thank you.
This will work for sqlserver 2012. If you are using an earlier version, you can replace the where clause:
DECLARE #t table(START_DATE date, END_DATE date)
INSERT #t values
('2015-01-01','2015-08-30'),('2015-01-01','2015-12-30'),
('2015-08-01','2015-12-30'),('2015-11-01','2017-12-30'),
('2016-01-01','2017-12-30'),('2017-01-01','2017-12-30')
DECLARE #from date='2015-7-01'
DECLARE #to date='2016-03-01'
SELECT *
FROM #t
WHERE
DATEADD(month,4, IIF(#from<START_DATE, START_DATE, #FROM)) <=
IIF(#to>END_DATE, END_DATE, #to)
/*
--this is for sqlserver 2008
DATEADD(month,4, CASE WHEN #from<START_DATE THEN START_DATE ELSE #FROM END) <=
CASE WHEN #to>END_DATE THEN END_DATE ELSE #to END
*/
You can use this code:
select *
from YourTable
where DateColumn between '2014/01/7' and '2006/05/30' and
start_date >= Dateadd(Month, Datediff(Month, 0, DATEADD(m, -4, current_timestamp)), 0)
Lets call your table Enrollment.
First.. we want all people who were enrolled between 7-01-14 and 6-30-15
Select *
From Enrollment
Where Start_Date Between '7-01-14' and '6-30-15'
And End_Date Between '7-01-14' and '6-30-15'
Also, the membership has to be at least found months long in that timespan.
Select *
From Enrollment
Where Start_Date Between '7-01-14' and '6-30-15'
And End_Date Between '7-01-14' and '6-30-15'
And DateDiff (month, Start_Date, End_Date) > 4
But consider these scenarios:
What happens if somebody signs up on '6-25-15' (5 days before end cutoff) and is active for 4 months?
What happens if somebody signs up on '4-01-14' (3 months before beginning cutoff) and is active for 4 months?
You will have to make appropriate changes to the query in that case to take care of these scenarios if you need them.
For example, if the Start_Date OR End_Date can be between the cutoff dates specified, but the membership still needs to be 4 months long
Select *
From Enrollment
Where
(
Start_Date Between '7-01-14' and '6-30-15'
OR End_Date Between '7-01-14' and '6-30-15'
)
And DateDiff (month, Start_Date, End_Date) >= 4
But what if the Start_Date is smaller than beginning of the cutff, and the End_Date is larger than the end of cutoff, and the membership still needs to be 4 months long
Select *
From Enrollment
Where
(
(Start_Date Between '7-01-14' and '6-30-15' OR End_Date Between '7-01-14' and '6-30-15')
OR
(Start_Date < '7-01-14' and End_Date > '6-30-15')
)
And DateDiff (month, Start_Date, End_Date) >= 4