How to search sequence of Date Ranges in SQL - sql-server-2008

When trying to check date ranges it does not return values. Here st_date and end_date for usr_id = 1 is sequence so it should return only usr_id = 1 values and should not return usr_id = 3 since it is not sequence . If the date range is not in sequence, it should not return any value.
CREATE TABLE #temp(st_date DATE,end_date DATE,usr_id INT)
INSERT #temp
VALUES ('2007-03-01 ','2015-01-31 ',1),
('2015-02-01 ','2017-04-01 ',1),
('2007-03-01 ','2014-01-31 ',2),
('2007-03-01 ','2015-01-31 ',3),
('2015-03-02 ','2017-04-01 ',3)
DECLARE #st_dt DATE = '2009-02-01 00:00:00',#end_dt DATE = '2017-01-01 00:00:00'
SELECT * FROM #temp WHERE #st_dt BETWEEN st_date AND end_date
AND #end_dt BETWEEN st_date AND end_date
DROP TABLE #temp

SELECT *
FROM #temp t
INNER JOIN #temp t2 ON t.usr_id = t2.usr_id AND t.end_date = DATEADD(dd, -1, t2.st_date)
WHERE #st_dt BETWEEN t.st_date AND t.end_date
AND #end_dt BETWEEN t2.st_date AND t2.end_date

Related

Cursor Pagination (prev / next) with null values

I have a cursor pagination implemented with MySQL (version 8.0), which works fine as long as there are no null values involved.
Here's my example data (id is a random UUID, date is date, time is time):
id | date | time
--------------------------
68 | 2017-10-28 | 22:00:00
d3 | 2017-11-03 | null
dd | 2017-11-03 | 21:45:00
62 | 2017-11-04 | 14:00:00
a1 | 2017-11-04 | 19:40:00
The cursor I use always consists of all three columns.
I use this query to get the next results (after the cursor):
SELECT * FROM table
WHERE (date > cursor.date)
OR (date = cursor.date AND time > cursor.time)
OR (date = cursor.date AND time = cursor.time AND id > cursor.id)
ORDER BY date ASC, time ASC, id ASC
And this query for prev results (before the cursor):
SELECT * FROM table
WHERE (date < cursor.date)
OR (date = cursor.date AND time < cursor.time)
OR (date = cursor.date AND time = cursor.time AND id < cursor.id)
ORDER BY date DESC, time DESC, id DESC
When using the prev query with cursor [id = dd, date = 2017-11-03, time = 21:45:00] it won't return the row with id = d3, because time is null, and this won't get selected by time < cursor.time.
Though I tried using time < cursor.time OR time IS NULL instead of time < cursor.time to include rows with null values. Which seems to fix this particular problem, but then creates a new problem: When using the prev query with cursor [id = d3, date = 2017-11-03, time = null], because now the result contains the row of the provided cursor.
I hope there's an easy solution for this. There seems to be no examples or tutorials on the web that deal with null values in cursor pagination.
Note: For the solution it doesn't matter if null will be sorted before or after non-null values, as long as it's consistent. (MySQL's default ordering is null < non-null)
I am not going to touch the topic of using cursors for pagination. There are alternatives, such as limit/offset.
But my recommendation for your queries is to use coalesce(), assigning a fake time for the comparison. MySQL makes this somewhat simple, because it supports time values in excess of 24 hours. And those would not be valid values for a date/time combination.
So:
SELECT *
FROM table
WHERE (date > cursor.date) OR
(date = cursor.date AND COALESCE(time, '24:00:00') > COALESCE(cursor.time, '24:00:00')) OR
(date = cursor.date AND COALESCE(time, '24:00:00') = COALESCE(cursor.time, '24:00:00') AND id > cursor.id)
ORDER BY date ASC, time ASC, id ASC
A more concise WHERE clause would be:
WHERE (date, COALESCE(time, '24:00:00'), id) > (cursor.date, COALESCE(cursor.time, '24:00:00'), cursor.id)
I'm a bit late to the party but give the following a try. The cursor logic needs to be adjusted for nullable columns and when a cursor row returns a null value for the time column.
Both examples listed below:
declare #cursorID nvarchar(2)
declare #cursorDate date
declare #cursorTime time(0)
declare #table table(id nvarchar(2), date date, time time(0))
insert into #table
values
('68', '2017-10-28', '22:00:00'),
('d3', '2017-11-03', NULL),
('dd', '2017-11-03', '21:45:00'),
('62', '2017-11-04', '14:00:00'),
('a1', '2017-11-04', '19:40:00')
--IF SELECTING A ROW WITH A NON-NULL VALUE FOR TIME
set #cursorID = 'dd'
set #cursorDate = (select date from #table where id = #cursorID)
set #cursorTime = (select time from #table where id = #cursorID)
--ASCENDING CURSOR VALUES
select * from #table
where
date >= #cursorDate
and (date > #cursorDate or (time >= #cursorTime
and (time > #cursorTime or id > #cursorID)))
order by
date asc, time asc, id asc
--DESCENDING CURSOR VALUES
select * from #table
where
date <= #cursorDate
and (date < #cursorDate or ((time <= #cursorTime or time is null)
and ((time < #cursorTime or time is null) or id < #cursorID)))
order by
date desc, time desc, id desc
--IF SELECTING A ROW WITH A NON VALUE FOR TIME
set #cursorID = 'd3'
set #cursorDate = (select date from #table where id = #cursorID)
set #cursorTime = (select time from #table where id = #cursorID)
--ASCENDING CURSOR VALUES
select * from #table
where
date >= #cursorDate
and (date > #cursorDate or (time is not null
or (time is null and id > #cursorID)))
order by
date asc, time asc, id asc
--DESCENDING CURSOR VALUES
select * from #table
where
date <= #cursorDate
and (date < #cursorDate or (time is null
and (id < #cursorID)))
order by
date desc, time desc, id desc
Add another column to the table. Make it a DATETIME. Combine date and time into it when not NULL; combine date with some particular time when NULL. Then your cursor has two columns to work with and no nulls.
If you have a reasonably recent version of MySQL, you can use a "generated stored" column, thereby avoiding any code changes.
And be sure to have INDEX(datetime, id).
If you are using MySQL 8.0 then you can consider to use row_number() window funciton create an unique sequential id (rn) for each row. Then just pass the rn for current row to get the previous rows.
Schema and insert statements:
create table cursortable( id varchar(10), date date, time time);
insert into cursortable values('68' , '2017-10-28' , '22:00:00');
insert into cursortable values('d3' , '2017-11-03' , null);
insert into cursortable values('dd' , '2017-11-03' , '21:45:00');
insert into cursortable values('62' , '2017-11-04' , '14:00:00');
insert into cursortable values('a1' , '2017-11-04' , '19:40:00');
Query to get the result for fist time:
select *,row_number()over(order by date,time,id)rn from cursortable
Output:
id
date
time
rn
68
2017-10-28
22:00:00
1
d3
2017-11-03
null
2
dd
2017-11-03
21:45:00
3
62
2017-11-04
14:00:00
4
a1
2017-11-04
19:40:00
5
Query to get the previous rows for cursor [id = dd, date = 2017-11-03, time = 21:45:00, rn=3] with only cursor [rn=3]:
with cte as
(
select *,row_number()over(order by date,time,id)rn from cursortable
)
select * from cte where rn<3
Output:
id
date
time
rn
68
2017-10-28
22:00:00
1
d3
2017-11-03
null
2
db<>fiddle here
If you don't want to introduce an computed column into your code then please try the below solution considering all three columns cursor [id = dd, date = 2017-11-03, time = 21:45:00]
Query:
with cte as
(
select *,row_number()over(order by date,time,id)rn from cursortable
)
,cte2 as
(
select * from cte where id='dd' and date= '2017-11-03' and time= '21:45:00'
)
select cte.id,cte.date,cte.time from cte inner join cte2 on cte.rn<cte2.rn
Output:
id
date
time
68
2017-10-28
22:00:00
d3
2017-11-03
null
db<>fiddle here
Your code would be like below:
with cte as
(
select *,row_number()over(order by date,time,id)rn from cursortable
)
,cte2 as
(
select * from cte where id=cursor.id and date= cursor.date and time= cursor.time
)
select cte.id,cte.date,cte.time from cte inner join cte2 on cte.rn<cte2.rn

Time difference for a particular product

Essentially, I want to find out how much time is spent on a product.
Each product is identified by the model_id and different people may work on
each product. I want the time difference between when the product is being worked on and when there is more than a 5 minute gap.
example table
CREATE TABLE test (id INT, created_at DATETIME, model_id INT, TIMEDIFF DATETIME, TOTALTIME DATETIME)
SELECT '144111', '2019-03-30 11:14:26','301302','',''
UNION
SELECT '144112', '2019-03-30 11:14:33','301302','',''
UNION
SELECT '144113', '2019-03-30 11:14:33','301302','',''
UNION
SELECT '144114', '2019-03-30 11:14:54','301302','',''
UNION
SELECT '144115', '2019-03-30 11:15:35','301302','',''
UNION
SELECT '144116', '2019-03-30 11:22:21','301302','',''
UNION
SELECT '144117', '2019-03-30 11:23:14','301302','',''
UNION
SELECT '144118', '2019-03-30 11:24:24','301302','',''
UNION
SELECT '144119', '2019-03-30 11:25:35','301302','',''
my attempt
WHILE (SELECT COUNT(model_id) FROM portal3_projectsolar.`qat_` WHERE `timediff` IS NULL) > 0 DO #begining of while loop
#min modelid
SET #modelid = (SELECT MIN(model_id) FROM `portal3_projectsolar`.`qat_` WHERE `Timediff` IS NULL);
#set the times
SET #starttime = (SELECT MIN(created_at) FROM `portal3_projectsolar`.`qat_` a WHERE a.model_id = #modelid AND a.`Timediff` IS NULL ORDER BY a.model_id, a.created_at);
SET #endtime = (SELECT MIN(created_at) FROM `portal3_projectsolar`.`qat_` a WHERE a.model_id = #modelid AND a.created_at > #starttime AND a.`Timediff` IS NULL ORDER BY a.model_id, a.created_at);
SET #Nexttime = (SELECT MIN(created_at) FROM `portal3_projectsolar`.`qat_` a WHERE a.model_id = #modelid AND a.created_at > #endtime AND a.`Timediff` IS NULL ORDER BY a.model_id, a.created_at);
# compare the endtime and the nexttime. if nextime is 5 minutes more than endtime then endtime is endtime
WHILE (SELECT DATE_ADD(#endtime,INTERVAL 30 MINUTE) > #Nextdate) DO
SET #Endtime =(SELECT #Nexttime);
SET #Nexttime = (SELECT MIN(created_at) FROM `portal3_projectsolar`.`qat_` WHERE model_id = #modelid AND created_at > #endtime AND `Timediff` IS NULL ORDER BY model_id, created_at);
END WHILE;
#the time diffrence
SET #timediff = (SELECT TIMESTAMPDIFF(MINUTE, #starttime, #endtime));
SET #startid = (SELECT MIN(id) FROM qat_ WHERE created_at = #starttime);
SET #endid = (SELECT MIN(id) FROM qat_ WHERE created_at = #endtime);
#update the time diff for the id range
UPDATE portal3_projectsolar.`qat_`
SET `timediff` = #timediff
WHERE Created_at = BETWEEN #starttime AND #Endtime;
END WHILE;
The results should show the difference of the start and end in the timediff column and the total time spent for each model_id in the totaltime column

SQL: using group by concat()

I have a query which returns result of number of calls made by customers and some suggestions made for customers and etc.. for that particular date ie... grouping it by date
But now I want to find number of customers I tried grouping by lead_id and cuncat(lead_id,timecreatredFormat) still there is a data mismatch
Below is the query that I have tried
select
sum(t.enquiry_cnt),
sum(t.suggested_cnt),
sum(t.tot_cnt)
from
(select
case
when source = 1 then 1
else 0
end enquiry_cnt,
case
when source = 6 then 1
else 0
end suggested_cnt,
case
when (source = 1 || source = 6) then 1
else 0
end tot_cnt,
date_format(timecreated, '%d-%b-%Y') created_time,
lead_id,timecreated
from
mg_lead_suggested_listing group by concat(created_time,lead_id) ) t
group by t.created_time
order by t.timecreated desc
limit 10;
Thanks in advance
Check whether following query is correct or not. I have added COUNT(DISTINCT t.lead_id) to get customer count.
DECLARE #TEMP TABLE
(
[source] INT,
lead_id INT,
timecreated DATETIME
)
INSERT INTO #TEMP VALUES (1,1,GETDATE())
INSERT INTO #TEMP VALUES (6,1,GETDATE())
INSERT INTO #TEMP VALUES (6,1,GETDATE())
INSERT INTO #TEMP VALUES (1,2,DATEADD(d,-1,GETDATE()))
INSERT INTO #TEMP VALUES (1,1,DATEADD(d,-1,GETDATE()))
INSERT INTO #TEMP VALUES (1,1,DATEADD(d,-1,GETDATE()))
SELECT
CAST(t.timecreated AS DATE) [date],
SUM(t.enquiry_cnt) enquiry_cnt,
(SELECT COUNT(DISTINCT lead_id) FROM #TEMP WHERE CAST(timecreated AS DATE) = CAST(t.timecreated AS DATE) AND [source] = 1) as lead_enquiry_cnt,
SUM(t.suggested_cnt) suggested_cnt,
(SELECT COUNT(DISTINCT lead_id) FROM #TEMP WHERE CAST(timecreated AS DATE) = CAST(t.timecreated AS DATE) AND [source] = 6) as lead_suggested_cnt,
SUM(t.tot_cnt) tot_cnt,
COUNT(t.lead_id) as lead_cnt
FROM
(
SELECT
CASE
WHEN [source] = 1 THEN 1
ELSE 0
END enquiry_cnt,
CASE
WHEN [source] = 6 THEN 1
ELSE 0
END suggested_cnt,
CASE
WHEN ([source] = 1 OR [source] = 6) THEN 1
ELSE 0
END tot_cnt,
lead_id,
timecreated
FROM
#TEMP
) AS t
group by
CAST(t.timecreated AS DATE)
order by
[date] desc

List dates between two date range

I have table 1 with 3 columns id, startdate and enddate. With order id being the primary key how do I list the dates between the date range Startdate and Enddate?
What I have:
id Startdate EndDate
1 2/11/2014 2/13/2014
2 2/15/2014 2/17/2014
What I need:
id Date
1 2/11/2014
1 2/12/2014
1 2/13/2014
2 2/15/2014
2 2/16/2014
2 2/17/2014
How do I do this?
Use recursive CTE:
WITH tmp AS (
SELECT id, StartDate AS [Date], EndDate
FROM MyTable
UNION ALL
SELECT tmp.id, DATEADD(DAY,1,tmp.[Date]), tmp.EndDate
FROM tmp
WHERE tmp.[Date] < tmp.EndDate
)
SELECT tmp.ID, tmp.[Date]
FROM tmp
ORDER BY tmp.id, tmp.[Date]
OPTION (MAXRECURSION 0) -- For long intervals
If you have to use cursor/loop, most times you are doing it wrong.
If you do a one-off setup of an auxiliary calendar table as shown at Why should I consider using an auxiliary calendar table?, possibly omitting a lot of the columns if you don't need them, like this:
CREATE TABLE dbo.Calendar
(
dt SMALLDATETIME NOT NULL
PRIMARY KEY CLUSTERED,
Y SMALLINT,
M TINYINT,
D TINYINT
)
GO
SET NOCOUNT ON
DECLARE #dt SMALLDATETIME
SET #dt = '20000101'
WHILE #dt < '20300101'
BEGIN
INSERT dbo.Calendar(dt) SELECT #dt
SET #dt = #dt + 1
END;
UPDATE dbo.Calendar SET
Y = YEAR(dt),
M = MONTH(dt),
D = DAY(dt);
(You may well not need the Y, M, D columns at all, but I left those in to show that more data can be stored for fast access - the article I linked to shows how that could be used.)
Then if your table is named "so", your code would simply be
SELECT A.id, C.dt
FROM so AS A
JOIN Calendar AS C
ON C.dt >= A.StartDate AND C.dt<= A.EndDate
An advantage of using an auxiliary table like that is that your queries can be faster: the work done in setting one up is a one-time cost which doesn't happen during usage..
Instead of using CTE (to over come recursive and performance when date range is large) below query can be used to get the list of dates between two date range.
DECLARE #StartDateSTR AS VARCHAR(32); DECLARE #EndDateSTR AS
VARCHAR(32); DECLARE #EndDate AS DATE; DECLARE #StartDate AS DATE;
SET #StartDateSTR = '01/01/1990'; SET #EndDateSTR = '03/31/2025'; SET
#StartDate = CAST(#StartDateSTR AS date); SET #EndDate =
cast(#EndDateSTR AS date); SELECT
DATEADD(DAY, n1.rn - 1, #StartDate) AS dt FROM (SELECT rn=Row_number() OVER( ORDER BY (SELECT NULL)) FROM sys.objects a
CROSS JOIN sys.objects b CROSS JOIN sys.objects c CROSS JOIN
sys.objects d) as n1 WHERE n1.[rn] <= Datediff(dd, #StartDate,
#EndDate)+1;

select in check constraint

I want to create a check constraint for my table, where you can't add new line, if the new booking range(start_date, end_date) have intersect with an already submitted row.
But i can't place query in a check constraint. Do you have an idea how to do this?
The tables:
APARTMAN
id INT
price INT
BOOKINGS
id INT
start_date DATE
end_date DATE
apartman_id INT
[apartman_id] IN (SELECT [id] FROM [dbo].[APARTMAN]
WHERE [id] NOT IN (
SELECT [apartman_id] FROM
[dbo].[BOOKINGS]
WHERE
([start_date] <= "requested end_date" AND
[end_date] >= "requested start_date" )
OR
([start_date] <= "requested start_date" AND
[end_date] >= "requested end_date" )
OR
(([start_date] <= "requested end_date" AND [end_date] >= "requested start_date" )
OR
([end_date] <= "requested start_date" AND [end_date] >= "requested end_date" ))
)
)
Here's an instead of trigger that I think handles all scenarios.
CREATE TRIGGER dbo.PreventOverlappingBookings
ON dbo.BOOKINGS INSTEAD OF INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON;
IF EXISTS (
SELECT 1 FROM inserted AS i
INNER JOIN dbo.BOOKINGS AS b
ON (b.id <> i.id OR i.id = 0) -- 0 for insert
AND b.apartman_id = i.apartman_id
AND ((b.start_date <= i.end_date AND b.end_date >= i.start_date)
OR (b.start_date <= i.start_date AND b.end_date >= i.end_date)
OR (b.end_date <= i.start_date AND b.end_date >= i.end_date))
) OR EXISTS (
-- also make sure there are no overlaps in a set-based insert/update
SELECT 1 FROM inserted AS i
INNER JOIN inserted AS b
ON (b.id <> i.id OR i.id = 0) -- 0 for insert
AND b.apartman_id = i.apartman_id
AND ((b.start_date <= i.end_date AND b.end_date >= i.start_date)
OR (b.start_date <= i.start_date AND b.end_date >= i.end_date)
OR (b.end_date <= i.start_date AND b.end_date >= i.end_date))
)
BEGIN
RAISERROR('Overlapping date range.', 11, 1);
END
ELSE
BEGIN
UPDATE b SET start_date = i.start_date, end_date = i.end_date
FROM dbo.BOOKINGS AS b
INNER JOIN inserted AS i
ON b.id = i.id;
IF ##ROWCOUNT > 0
BEGIN
INSERT dbo.BOOKINGS(start_date, end_date, apartman_id)
SELECT start_date, end_date, apartman_id FROM inserted AS i;
END
END
END
GO
Some answers will suggest a function in a UDF, but I don't trust them (and neither should you, IMHO).