Get data from the past "x" hours excluding the weekend - sql

I'm using Oracle 11g.
What I would like to do is select login data from the past (let's say) 10 hours. I would like to exclude the weekend from this, and let my query wrap around to the previous Friday. For example, if it's Monday at 8:00 AM, I want to be able to find the logins that occurred from 12-8 AM Monday, as well as data from 10-11:59 PM on Friday.
My current statement looks like this:
select * from logins where end_time >= sysdate - (10) / (24)
But I'm not sure how to exclude Saturday and Sunday.

I didn't see what Database as the Question was only tagged as SQL. Regardless the answer below is SQL Server but the concept will work in any database. I've used the method shown below in SQLite and Oracle.
Get a list of dates between two dates using a function
There is an excellent answer contained in the above SO Post I provided above. However this question has so many answers and the excepted answer is actually not the answer that solved my issue when I originally had a similar calendar table problem on my end.
I've pasted below the answer that works without having to create a calendar table and works within any standard query which can then be turned into a View/Stored Proc/Function etc.... And since the SQL below does not use a recursive CTE you can use this code in Views, Stored Procedures, Functions... etc... The dates table is still populated using a CTE but it does this using an exponential method of joining in several fake tables. I think it is brilliant and it performs well.
DECLARE #StartDate DATE = '08/01/2021'
, #EndDate Date = '08/01/2022'
;
-- From http://stackoverflow.com/questions/1378593/get-a-list-of-dates-between-two-dates-using-a-function
-- it basically just selects a whole bunch of 1s, gets the ROW_NUMBER() for each and filters.
-- This seems to perform better than inserting a bunch of rows in a WHILE loop or a recursive CTE
-- just UNIONing to the next date.
WITH
N0 AS (SELECT 1 AS n UNION ALL SELECT 1)
,N1 AS (SELECT 1 AS n FROM N0 t1, N0 t2)
,N2 AS (SELECT 1 AS n FROM N1 t1, N1 t2)
,N3 AS (SELECT 1 AS n FROM N2 t1, N2 t2)
,N4 AS (SELECT 1 AS n FROM N3 t1, N3 t2)
,N5 AS (SELECT 1 AS n FROM N4 t1, N4 t2)
,N6 AS (SELECT 1 AS n FROM N5 t1, N5 t2)
,nums AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS num FROM N6)
SELECT DATEADD(day, num - 1, #StartDate) AS TheDate
, DATENAME(WEEKDAY, (DATEADD(day, num - 1, #StartDate))) AS TheDayName
, num - 1 AS TheOffset
FROM nums
WHERE num <= DATEDIFF(day, #StartDate, #EndDate) + 1
In the above SQL I added the field for TheDayName so that you can filter out the weekends however you can add so many more fields that pull in more details of the dates being pulled in your query. Refer to the below SQL as it shows how to add all of the unique date parts into the CTE as needed. Again I added one of the below fields to the above CTE to pull TheDayName.
/* The below shows how to obtain additional information a date */
-- https://www.mssqltips.com/sqlservertip/4054/creating-a-date-dimension-or-calendar-table-in-sql-server/
DECLARE #d DATE = '07/26/21';
SELECT
TheDate = CONVERT(date, #d),
TheDay = DATEPART(DAY, #d),
TheDayName = DATENAME(WEEKDAY, #d),
TheWeek = DATEPART(WEEK, #d),
TheISOWeek = DATEPART(ISO_WEEK, #d),
TheDayOfWeek = DATEPART(WEEKDAY, #d),
TheMonth = DATEPART(MONTH, #d),
TheMonthName = DATENAME(MONTH, #d),
TheQuarter = DATEPART(Quarter, #d),
TheYear = DATEPART(YEAR, #d),
TheFirstOfMonth = DATEFROMPARTS(YEAR( #d), MONTH( #d ), 1),
TheLastOfYear = DATEFROMPARTS(YEAR( #d), 12, 31),
TheDayOfYear = DATEPART(DAYOFYEAR, #d)
Last if you are needing an example of how you JOIN back to a calendar table see the accepted answer in this SO Post: How to Calculate the Total Unique Days Employed for All Jobs - No overlap days counted twice

Related

Trying to convert a 7 digit julian date into MMDDYY format

I'm trying to convert a 7 digit julian/mainframe date into a calendar date of format mmddyy in SQL Server.
An example of this being julian date 2005020, where the first 4 digits are the year, 2005, and the last three are the day number in a calendar, so 020 = january 20. So I'd need to get this date as 012005(MMDDYY) in SQL Server.
I've been using the following query but keep getting an error after it loads a few records:
SELECT DATEADD(day,CAST(RIGHT([julianDateColumn],3) as int)-,LEFT([julianDateColumn],4))
The error I've been getting:
Conversion failed when converting date and/or time from character string.
Originally I was doing this in an Access DB using the "DATESERIAL" function but from what I've seen the closest thing to that in SQL Server was "DATEFROMPARTS", I tried using the following formula but it also didn't work:
DATEFROMPARTS([julianDateColumn]/1000,1,[julianDateColumn] % 1000)
Thanks in advance!
The simplest would seem to be to take the left as the year, and the add the days (-1) to make a date. Also, rather than using a format of MMDDYY I'm going to go straight a date datatype. If you want it in a specific format, that's for your presentation layer.
SELECT JulianDate,
CONVERT(date,DATEADD(DAY,RIGHT(JulianDate,3)-1,CONVERT(datetime,LEFT(JulianDate,4)))) AS ActualDate --4 int strings are iterpreted as the year, so I'm going to take advantage of that
FROM (VALUES('2005020'))V(JulianDate);
Based on the comments on the answer, it appears that the OP has some dates that don't conform to the format that stated (yyyyddd). Therefore what we could use here is a calendar table, here, and then LEFT JOIN to it and see what bad rows you get (and INNER JOIN to get the dates).
You can create the table with something like this:
CREATE TABLE dbo.CalendarTable (CalendarDate date NOT NULL PRIMARY KEY,
CalenderYear AS DATEPART(YEAR, CalendarDate) PERSISTED,
CalenderMonth AS DATEPART(MONTH, CalendarDate) PERSISTED,
CalenderDay AS DATEPART(DAY, CalendarDate) PERSISTED,
CalenderMonthName AS DATENAME(MONTH, CalendarDate),
JulianDate AS DATEPART(YEAR,CalendarDate) * 1000 + DATEDIFF(DAY,DATEFROMPARTS(DATEPART(YEAR, CalendarDate),1,1),CalendarDate) + 1 PERSISTED); --Some example columns
WITH N AS(
SELECT N
FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL))N(N)),
Tally AS(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) -1 AS I
FROM N N1, N N2, N N3, N N4, N N5, N N6),
Dates AS(
SELECT CONVERT(date,DATEADD(DAY, T.I, '19000101')) AS CalendarDate
FROM Tally T)
INSERT INTO dbo.CalendarTable(CalendarDate)
SELECT CalendarDate
FROM Dates
WHERE CalendarDate < '21000101';
GO
Then we can do something like this to get the bad rows:
SELECT YT.JulianDate
FROM dbo.YourTable YT
LEFT JOIN dbo.CalendarTable CT ON YT.JulianDate = CT.JulianDate
WHERE CT.JulianDate IS NULL;
I think I would use datefromparts() and dateadd():
select dateadd(day,
right(juliandate, 3) - 1),
datefromparts(left(juliandate, 4), 1, 1)
)
select Format(cast(concat(substring('2005020', 1, 4), '-', Month(cast(substring('2005020', 5, len('2005020')) as int)) ,'-', day(dateadd(day,-1,cast(substring('2005020', 5, len('2005020')) as int ) ) )) as date), 'MMddyy')

Join Generated Date Sequence

Currently I'm trying to join a date table to a ledger table so I can fill the gaps of the ledger table whenever there are no transactions in certain instances (e.g. there are transactions on March 1st and in March 3rd, but no transaction in March 2nd. And by joining both tables March 2nd would appear in the ledger table but with 0 for the variable we're analyzing.)
The challenge is that I can't create a Date object/table/dimension because I don't have permissions to create tables in the database. Therefore I've been generating a date sequence with this code:
DECLARE #startDate date = CAST('2016-01-01' AS date),
#endDate date = CAST(GETDATE() AS date);
SELECT DATEADD(day, number - 1, #startDate) AS [Date]
FROM (
SELECT ROW_NUMBER() OVER (
ORDER BY n.object_id
)
FROM sys.all_objects n
) S(number)
WHERE number <= DATEDIFF(day, #startDate, #endDate) + 1;
So, is there the possibility to join both tables into the same statement? Let's say the ledger table looks like this:
SELECT
date,cost
FROM ledger
I'd assume it can be done by using a subquery but I don't know how.
Thank you.
There is a very good article by Aaron Bertrand showing several methods for generating a sequence of numbers (or dates) in SQL Server: Generate a set or sequence without loops – part 1.
Try them out and see for yourself which is faster or more convenient to you. (spoiler - Recursive CTE is rather slow)
Once you've picked your preferred method you can wrap it in a CTE (common-table expression).
Here I'll use your method from the question
WITH
CTE_Dates
AS
(
SELECT
DATEADD(day, number - 1, #startDate) AS dt
FROM (
SELECT ROW_NUMBER() OVER (
ORDER BY n.object_id
)
FROM sys.all_objects n
) S(number)
WHERE number <= DATEDIFF(day, #startDate, #endDate) + 1
)
SELECT
...
FROM
CTE_Dates
LEFT JOIN Ledger ON Ledger.dt = CTE_Dates.dt
;
You can use your generated date sequence as a CTE and LEFT JOIN that to your ledger table. For example:
DECLARE #startDate date = CAST('2020-02-01' AS date);
DECLARE #endDate date = CAST(GETDATE() AS date);
WITH dates AS (
SELECT DATEADD(day, number - 1, #startDate) AS [Date]
FROM (
SELECT ROW_NUMBER() OVER (
ORDER BY n.object_id
)
FROM sys.all_objects n
) S(number)
WHERE number <= DATEDIFF(day, #startDate, #endDate) + 1
)
SELECT dates.Date, COALESCE(ledger.cost, 0)
FROM dates
LEFT JOIN (VALUES ('2020-02-02', 14), ('2020-02-05', 10)) AS ledger([Date], [cost]) ON dates.Date = ledger.Date
Output:
Date cost
2020-02-01 0
2020-02-02 14
2020-02-03 0
2020-02-04 0
2020-02-05 10
2020-02-06 0
Demo on dbfiddle

Query to check number of records created in a month.

My table creates a new record with timestamp daily when an integration is successful. I am trying to create a query that would check (preferably automated) the number of days in a month vs number of records in the table within a time frame.
For example, January has 31 days, so i would like to know how many days in january my process was not successful. If the number of records is less than 31, than i know the job failed 31 - x times.
I tried the following but was not getting very far:
SELECT COUNT (DISTINCT CompleteDate)
FROM table
WHERE CompleteDate BETWEEN '01/01/2015' AND '01/31/2015'
Every 7 days the system executes the job twice, so i get two records on the same day, but i am trying to determine the number of days that nothing happened (failures), so i assume some truncation of the date field is needed?!
One way to do this is to use a calendar/date table as the main source of dates in the range and left join with that and count the number of null values.
In absence of a proper date table you can generate a range of dates using a number sequence like the one found in the master..spt_values table:
select count(*) failed
from (
select dateadd(day, number, '2015-01-01') date
from master..spt_values where type='P' and number < 365
) a
left join your_table b on a.date = b.CompleteDate
where b.CompleteDate is null
and a.date BETWEEN '01/01/2015' AND '01/31/2015'
Sample SQL Fiddle (with count grouped by month)
Assuming you have an Integers table*. This query will pull all dates where no record is found in the target table:
declare #StartDate datetime = '01/01/2013',
#EndDate datetime = '12/31/2013'
;with d as (
select *, date = dateadd(d, i - 1 , #StartDate)
from dbo.Integers
where i <= datediff(d, #StartDate, #EndDate) + 1
)
select d.date
from d
where not exists (
select 1 from <target> t
where DATEADD(dd, DATEDIFF(dd, 0, t.<timestamp>), 0) = DATEADD(dd, DATEDIFF(dd, 0, d.date), 0)
)
Between is not safe here
SELECT 31 - count(distinct(convert(date, CompleteDate)))
FROM table
WHERE CompleteDate >= '01/01/2015' AND CompleteDate < '02/01/2015'
You can use the following query:
SELECT DATEDIFF(day, t.d, dateadd(month, 1, t.d)) - COUNT(DISTINCT CompleteDate)
FROM mytable
CROSS APPLY (SELECT CAST(YEAR(CompleteDate) AS VARCHAR(4)) +
RIGHT('0' + CAST(MONTH(CompleteDate) AS VARCHAR(2)), 2) +
'01') t(d)
GROUP BY t.d
SQL Fiddle Demo
Explanation:
The value CROSS APPLY-ied, i.e. t.d, is the ANSI string of the first day of the month of CompleteDate, e.g. '20150101' for 12/01/2015, or 18/01/2015.
DATEDIFF uses the above mentioned value, i.e. t.d, in order to calculate the number of days of the month that CompleteDate belongs to.
GROUP BY essentially groups by (Year, Month), hence COUNT(DISTINCT CompleteDate) returns the number of distinct records per month.
The values returned by the query are the differences of [2] - 1, i.e. the number of failures per month, for each (Year, Month) of your initial data.
If you want to query a specific Year, Month then just simply add a WHERE clause to the above:
WHERE YEAR(CompleteDate) = 2015 AND MONTH(CompleteDate) = 1

SQL that lists x records with the Weeknumber and Monday's date for each week

I'm looking for an SQL query that would provide me a list of the Weeknumber and the Monday's date for that particular week.
For example:
WeekNumber DateMonday
39 2013-09-23
40 2013-09-30
... ...
The following justs produces one week
select
(DATEPART(ISO_WEEK,(CAST(getdate() as DATETIME)))) as WeekNumber,
DATEADD(wk, DATEDIFF(d, 0, CAST(getdate() as DATETIME)) / 7, 0) AS DateMonday
If you don't have a numbers table you can generate a list of sequential numbers on the fly using system tables:
e.g
SELECT Number = ROW_NUMBER() OVER(ORDER BY object_id)
FROM sys.all_objects;
If you need to extend this for more numbers you can CROSS JOIN tables:
SELECT Number = ROW_NUMBER() OVER(ORDER BY a.object_id)
FROM sys.all_objects a
CROSS JOIN sys.all_objects b;
Then you just need to add/subtract these number of weeks from your starting date:
DECLARE #Monday DATE = DATEADD(WEEK, DATEDIFF(WEEK, 0, GETDATE()), 0);
WITH Numbers AS
( SELECT Number = ROW_NUMBER() OVER(ORDER BY object_id)
FROM sys.all_objects
)
SELECT WeekNumber = DATEPART(ISO_WEEK, w.DateMonday),
w.DateMonday
FROM ( SELECT DateMonday = DATEADD(WEEK, - n.Number, #Monday)
FROM Numbers n
) w;
This is a verbose way of doing this for step by step clarity, it can be condensed to:
SELECT WeekNumber = DATEPART(ISO_WEEK, w.DateMonday),
w.DateMonday
FROM ( SELECT DateMonday = DATEADD(WEEK, DATEDIFF(WEEK, 0, GETDATE()) - ROW_NUMBER() OVER(ORDER BY object_id), 0)
FROM sys.all_objects
) w;
Example on SQL Fiddle
Aaron Bertrand has done some in depth comparisons ways of generating sequential lists of numbers:
Generate a set or sequence without loops – part
1
Generate a set or sequence without loops – part
2
Generate a set or sequence without loops – part
3
Of course the easiest way to do this would be to create a calendar table

How to count databases elements in a range of date?

In an SQL Server procedure, I need to get all rows matching some constraints(simple where conditions), and then group them by month.
The goal is to create a graph(in Sql server reporting services), which display all data.
I've already something like this:
Select Count(*) AS Count, Month(a.issueDate) AS Month, Year(a.issueDate) AS Year
FROM MyTable a
WHERE
....
GROUP BY YEAR(a.issueDate), MONTH(a.issueDate)
I got my data, I got my graph, but the problem is that if I've NOT any rows in "MyTable", which match my Where conditions, I won't have any rows.
The result is that I've a graph Starting with january, skipping february, and then displaying march.
I cannot post-process data since it's directly connected to the SQL Server Reporting Services report.
Since I have this problem for ~20 stored procedure, I will appreciate to have the simpliest way of doing it.
Thank you very much for your advices
Let's say you want a specific year:
DECLARE #year INT;
SET #year = 2012;
DECLARE #start SMALLDATETIME;
SET #start = DATEADD(YEAR, #year-1900, 0);
;WITH y AS (SELECT TOP (12) rn = ROW_NUMBER() OVER (ORDER BY [object_id])-1
FROM sys.all_objects ORDER BY [object_id])
SELECT DATEADD(MONTH, y.rn, #start), COUNT(t.issueDate)
FROM y
LEFT OUTER JOIN dbo.MyTable AS t
ON t.issueDate >= DATEADD(MONTH, y.rn, #start)
AND t.issueDate < DATEADD(MONTH, y.rn + 1, #start)
GROUP BY DATEADD(MONTH, y.rn, #start);
If it's not a specific year, then you can do it slightly differently to cover any date range, as long as you provide the 1st day of the 1st month and the 1st day of the last month (or pass 4 integers and construct the dates manually):
DECLARE #startdate SMALLDATETIME, #enddate SMALLDATETIME;
SELECT #startdate = '20111201', #enddate = '20120201';
;WITH y AS (SELECT TOP (DATEDIFF(MONTH, #startdate, #enddate)+1)
rn = ROW_NUMBER() OVER (ORDER BY [object_id])-1
FROM sys.all_objects ORDER BY [object_id]
)
SELECT DATEADD(MONTH, y.rn, #startdate), COUNT(t.issueDate)
FROM y
LEFT OUTER JOIN dbo.MyTable AS t
ON t.issueDate >= DATEADD(MONTH, y.rn, #startdate)
AND t.issueDate < DATEADD(MONTH, y.rn + 1, #startdate)
GROUP BY DATEADD(MONTH, y.rn, #startdate);
In report builder, right click on the date axis, select properties, and then set the axis up as a date range, it will add the empty columns for you, and you won't have to change your SQL
You need to build a table (a Table variable would work best here) that contains all year/month combinations from your minimum to maximum.
You then need to cross join this with your main query to get results for all year/months ready for the graph.