I have a DB which looks like this :
FromDate ToDate ProfileUID
2017-02-10 07:00:00 2017-02-10 15:30:00 TB_D
2017-02-09 23:00:00 2017-02-10 07:00:00 ZK_D
2017-02-09 17:30:00 2017-02-09 23:00:00 DL_D
2017-02-09 07:00:00 2017-02-09 17:30:00 AM_D
2017-02-08 23:00:00 2017-02-09 07:00:00 CK_D
2017-02-08 17:30:00 2017-02-08 23:00:00 DJ_N
This DB is often modified manually and some errors can be done by user.
I'm trying to do an SQL query to check if the schedule is ok, which means I would like to know until when my records are (here we filled only for the two next days) and if there are no "black holes" in the schedule, meaning there is a period with no profileUID filled.
What I have so far :
SELECT *,LAG (ToDate) OVER (PARTITION BY FromDate ORDER BY FromDate)
FROM `dashboardcalendar`
WHERE FromDate>= NOW()
ORDER BY `dashboardcalendar`.`FromDate` ASC
EDIT: Given that you can't use window functions on your version of MariaDB, try this:
SELECT FromDate, ToDate, ProfileUID, next_FromDate, last_ToDate,
CASE WHEN next_record_uid is null then 'Warning - Gap after' end warn1,
CASE WHEN prev_record_uid is null then 'Warning - Gap before' end warn2
FROM (
SELECT t1.FromDate, t1.ToDate, t1.ProfileUID,
t2.ProfileUID next_record_uid,
t3.ProfileUID prev_record_uid
FROM table t1
LEFT JOIN table t2
ON t1.ToDate = t2.FromDate
LEFT JOIN table t3
ON t1.FromDate = t3.ToDate
)
This does not account for overlaps in time - it will give a false positive warning if two time periods overlap.
Original answer:
I'd use LEAD as well as LAG, and then wrap in another select to do the comparison:
SELECT FromDate, ToDate, ProfileUID, next_FromDate, last_ToDate,
CASE WHEN ToDate < next_FromDate then 'Warning - Gap after' end warn1,
CASE WHEN FromDate > last_ToDate then 'Warning - Gap before' end warn2
FROM (
SELECT FromDate, ToDate, ProfileUID,
LEAD(FromDate,1) OVER (ORDER BY FromDate) next_FromDate,
LAG(FromDate,1) OVER (ORDER BY FromDate) last_ToDate
FROM table
)
You should not need to partition, it sounds like from your description that each record covers a distinct period of time so there's nothing to group on.
Related
I have the following table.
id user time_stamp
1 Mike 2020-02-13 00:00:00 UTC
2 John 2020-02-13 00:00:00 UTC
3 Levy 2020-02-12 00:00:00 UTC
4 Sam 2020-02-12 00:00:00 UTC
5 Frodo 2020-02-11 00:00:00 UTC
Let's say 2020-02-13 00:00:00 UTC is the last day and I would like to query this table to only display last days results? I want to create a view in Bigquery so that I only and always get the last day's results?
So that in the end I get something like this (For last day which is 2020-02-13 00:00:00 UTC )
id user time_stamp
1 Mike 2020-02-13 00:00:00 UTC
2 John 2020-02-13 00:00:00 UTC
You can use window functions:
select t.* except (seqnum)
from (select t.*,
dense_rank() over (order by time_stamp) as seqnum
from t
) t
where seqnum = 1;
This may not work well on a large amount of data -- because of the way that BQ implements window functions with no partitioning. So, you might find that this works better (especially if the above runs out of resources):
select t.*
from t join
(select max(time_stamp) as max_time_stamp
from t
) tt
on t.time_stamp = max_time_stamp;
Also, if the timestamps actually have date components, then you will want to convert to a date or remove the time component somehow.
I have a table like this:
fld_id fld_DateTime fld_Index
2017-07-01 00:00:00.000 5
2017-07-01 01:00:00.000 10
2017-07-01 02:00:00.000 15
2017-07-01 03:00:00.000 40
...........
...........
2017-07-01 23:00:00.000 70
2017-07-02 00:00:00.000 110
2017-07-02 01:00:00.000 140
2017-07-02 02:00:00.000 190
...............
...............
2017-07-02 23:00:00.000 190
What I am trying to do is to group them and count sum of fld_index per day like so:
fld_id fld_DateTime SUM
2017-07-01 190
2017-07-02 400
Here's what I've tried:
SELECT fld_dateTime, SUM(fld_Index) AS Sum
FROM tbl_data
WHERE
AND fld_ConsDateTime BETWEEN '2017-07-01' AND '2017-08-02'
GROUP BY fld_dateTime
It calculates the sum but still in hourly format. How to achieve the daily format like above example?
UPDATE Monthly Part Output
2017 8 30630800.0000
2017 7 589076201.1800
Simply cast as DATE
SELECT CAST(fld_dateTime AS DATE) AS fld_Date, SUM(fld_Index) AS Sum
FROM tbl_data
WHERE
fld_ConsDateTime BETWEEN '2017-07-01' AND '2017-08-02'
GROUP BY CAST(fld_dateTime AS DATE);
EDIT:
What about month? Is it the same logic?
It depends on your RDBMS, but in SQL Server you could use:
SELECT YEAR(fld_dateTime), MONTH(fld_dateTime), SUM(fld_Index) AS Sum
FROM tbl_data
GROUP BY YEAR(fld_dateTime), MONTH(fld_dateTime);
It is important to add year part to avoid grouping records from different years.
You need to extract the date. In SQL Server, you would do:
SELECT CAST(fld_dateTime as DATE) as fld_date, SUM(fld_Index) AS Sum
FROM tbl_data
WHERE fld_ConsDateTime >= '2017-07-01' AND
fld_ConsDateTime < '2017-08-03'
GROUP BY CAST(fld_dateTime as DATE)
ORDER BY fld_date
In MySQL, the above would work, but I would do:
SELECT DATE(fld_dateTime) as fld_date, SUM(fld_Index) AS Sum
FROM tbl_data
WHERE fld_ConsDateTime >= '2017-07-01' AND
fld_ConsDateTime < '2017-08-03'
GROUP BY DATE(fld_dateTime)
ORDER BY fld_date;
In both cases, you should change the WHERE clause. Your version would keep flights where the date/time is exactly midnight on 2017-08-02. Using >= and < is more accurate -- taking all date/times on one day but not the next.
I have the following table:
oDate oTime oAct
--------------------------------------
2017-06-01 00:00:00 A
2017-06-01 01:00:00 B
2017-06-01 02:00:00 C
ff.
2017-06-02 00:00:00 B
ff.
I want to select a day before (only after 21:00:00) and after.
Let say, If I Select '2017-06-02' then the result should be:
oDate oTime oAct
--------------------------------------
2017-06-01 22:00:00 A
2017-06-02 00:00:00 B
2017-06-02 01:00:00 C
ff.
2017-06-03 00:00:00 C
ff.
Also, for the query. I only have one parameter, which is #oDate date.
Please advise.
Thank you.
If I follow your question correctly I think you are after a where clause such as this:
select
*
from YourTable
where (
oDate > '20170602'
OR
(oDate = '20170602' AND oTime >= '21:00:00')
)
This would give you all dates after 2017-06-02 as well as times on that date at and after 21:00
To make best use of indexes that may exist on those columns I suggest you do not try to combine the date with time such as this dateadd(day,datediff(day,0,oDate),oTime) and then try to filter >= '20170602 21:00:00' as that would produce table scanning.
perhaps this will help
select
*
from YourTable
where (
oDate > #dateparameterhere
OR
(oDate = #dateparemterhere AND oTime >= '21:00:00')
)
Try this
concatenating fields to make a datetime field
select
* from table
where
cast (cast(odate as varchar(max)) + ' ' + cast(otime as varchar(max)) as datetime) < cast('2017-06-02 21:00:00' as datetime)
I have a database table named availableTimeslot with fields pk, startDate, endDate, e.g.
PK startDate endDate
1. 2017-03-07 09:00:00 2017-03-07 18:00:00
2. 2017-03-07 18:00:00 2017-03-07 21:00:00
3. 2017-03-08 09:00:00 2017-03-08 18:00:00
records starting from 09:00:00 to 18:00:00 indicate it is a morning time slot, while 18:00:00 to 23:00:00 indicating it is a afternoon time slot
storing available timeslot dates (e.g. 2017-03-06, 2017-03-08) which are available for the customer to choose one.
Can I use one query to get exactly 10 available time slots dates starting on the day after the order date?
e.g. if I order a product on 2016-03-07, then the query returns
2017-03-08 09:00:00
2017-03-08 18:00:00
2017-03-09 09:00:00
2017-03-09 18:00:00
2017-03-10 ...
2017-03-11 ...
2017-03-13 ...
as 12 is a public holiday and not in the table.
In short, it returns 10 dates (5 days with each day having am and pm sessions)
remark: the available time slot dates are in order, but may not be consecutive
select available_date
from ( select available_date, row_number() over (order by available_date) as rn
from your_table
where available_date > :order_date
)
where rn <= 5;
:order_date is a bind variable - the date entered by the user/customer through the interface.
Do you want 5 for a single customer?
select ts.*
from (select ts.*
from customer c join
timeslots ts
on ts.date > c.orderdate
where c.customerid = v_customerid
order by ts.date asc
) ts
where rownum <= 5
I have a table that looks like
UserID LastDayofMonth Count
1234 2015-09-30 00:00:00 12
1237 2015-09-30 00:00:00 5
3233 2015-09-30 00:00:00 3
8336 2015-09-30 00:00:00 22
1234 2015-10-31 00:00:00 8
1237 2015-10-31 00:00:00 5
3233 2015-10-31 00:00:00 7
8336 2015-11-30 00:00:00 52
1234 2015-11-30 00:00:00 8
1237 2015-11-30 00:00:00 5
3233 2015-11-30 00:00:00 7
(with around ~10,000 rows). As you can see in the example, UserID 8336 has no record for October 31st (dates are monthly but always the last day of the month, which I want to keep). How do I return a table with a records that fills in records for a period of four months so that users like 8336 get records like
8336 2015-10-31 00:00:00 0
I do have a calendar table with all days that I can use.
If I understand correctly, you want a record for each user and for each end of month. And, if the record does not currently exist, then you want the value of 0.
This is two step process. Generate all the rows first, using cross join. Then use left join to get the values.
So:
select u.userId, l.LastDayofMonth, coalesce(t.cnt, 0) as cnt
from (select distinct userId from t) u cross join
(select distinct LastDayofMonth from t) l left join
t
on t.userId = u.userId and t.LastDayofMonth = l.LastDayofMonth;
This solution uses a couple of CTEs, not knowing your calendar table layout. The only advantage this solution has over Gordon Linoff's is it doesn't assume at least one user per possible month. I've provided test data per your example with an extra record for the month of July, skipping August entirely.
/************** TEST DATA ******************/
IF OBJECT_ID('MonthlyUserCount','U') IS NULL
BEGIN
CREATE TABLE MonthlyUserCount
(
UserID INT
, LastDayofMonth DATETIME
, [Count] INT
)
INSERT MonthlyUserCount
VALUES (1234,'2015-07-31 00:00:00',12),--extra record
(1234,'2015-09-30 00:00:00',12),
(1237,'2015-09-30 00:00:00',5),
(3233,'2015-09-30 00:00:00',3),
(8336,'2015-09-30 00:00:00',22),
(1234,'2015-10-31 00:00:00',8),
(1237,'2015-10-31 00:00:00',5),
(3233,'2015-10-31 00:00:00',7),
(8336,'2015-11-30 00:00:00',52),
(1234,'2015-11-30 00:00:00',8),
(1237,'2015-11-30 00:00:00',5),
(3233,'2015-11-30 00:00:00',7)
END
/************ END TEST DATA ***************/
DECLARE #Start DATETIME;
DECLARE #End DATETIME;
--establish a date range
SELECT #Start = MIN(LastDayofMonth) FROM MonthlyUserCount;
SELECT #End = MAX(LastDayofMonth) FROM MonthlyUserCount;
--create a custom calendar of days using the date range above and identify the last day of the month
--if your calendar table does this already, modify the next cte to mimic this functionality
WITH cteAllDays AS
(
SELECT #Start AS [Date], CASE WHEN DATEPART(mm, #Start) <> DATEPART(mm, #Start+1) THEN 1 ELSE 0 END [Last]
UNION ALL
SELECT [Date]+1, CASE WHEN DATEPART(mm,[Date]+1) <> DatePart(mm, [Date]+2) THEN 1 ELSE 0 END
FROM cteAllDays
WHERE [Date]< #End
),
--cte using calendar of days to associate every user with every end of month
cteUserAllDays AS
(
SELECT DISTINCT m.UserID, c.[Date] LastDayofMonth
FROM MonthlyUserCount m, cteAllDays c
WHERE [Last]=1
)
--left join the cte to evaluate the NULL and present a 0 count for that month
SELECT c.UserID, c.LastDayofMonth, ISNULL(m.[Count],0) [Count]
FROM cteUserAllDays c
LEFT JOIN MonthlyUserCount m ON m.UserID = c.UserID
AND m.LastDayofMonth =c.LastDayofMonth
ORDER BY c.LastDayofMonth, c.UserID
OPTION ( MAXRECURSION 0 )