SQL Server - WHERE Date Range & GROUP BY MonthName - date

I have 2 same queries (to return "MonthName Year" and count) as below, but only the date range in the WHERE condition is different. Query 1 gets only the June month count, while Query 2 gets count from Apr to Jul, where the Jun month count (in Query 2) is not same as June month count from Query 1. Please advise.
Query 1:
SELECT DATENAME(MONTH, SubmissionDate) + ' ' + DateName(Year, SubmissionDate) AS MonthNumber, COUNT(1) AS InquiryCount
, Cast(Datename(MONTH,SubmissionDate) + ' ' + Datename(YEAR,SubmissionDate) AS DATETIME) AS tmp
FROM [dbo].[InvestigationDetails] (nolock)
WHERE SubmissionDate>= '06/01/2016'
AND SubmissionDate <= '06/30/2016'
GROUP BY DATENAME(MONTH, SubmissionDate) + ' ' + DateName(Year, SubmissionDate), DateName(Year, SubmissionDate)
ORDER BY tmp ASC
Query 2:
SELECT DATENAME(MONTH, SubmissionDate) + ' ' + DateName(Year, SubmissionDate) AS MonthNumber, DateName(Year, SubmissionDate), COUNT(1) AS InquiryCount
, Cast(Datename(MONTH,SubmissionDate) + ' ' + Datename(YEAR,SubmissionDate) AS DATETIME) AS tmp
FROM [dbo].[InvestigationDetails] (nolock)
WHERE SubmissionDate>= '04/01/2016'
AND SubmissionDate <= '07/31/2016'
GROUP BY DATENAME(MONTH, SubmissionDate) + ' ' + DateName(Year, SubmissionDate), DateName(Year, SubmissionDate)
ORDER BY tmp ASC
Thanks,
Jay

SubmissionDate must be of type DATETIMEand thus, you are missing all values for your last day, 06/30/2016, since this equates to 06/30/2016 00:00:00. This means any records that have SubmissionDate with a time > 00:00:00 on 6/30/2016 will be excluded. For example, 6/30/2016 12:44:22 wouldn't be included in your results with your current logic.
Use one of these instead:
AND SubmissionDate < '07/01/2016'
AND SubmissionDate <= '06/30/2016 23:59:59.999'
The first method is preferred since you will get all records before 7/1/2016, which includes 6/30/2016 23:59:59.999. Of course, you should be aware of how precise DATETIME can be in SQL Server. Run the code below to see what I mean.
declare #dt datetime2 = getdate()
select #dt --more precise with datetime2
select getdate() --not as precise

Related

How can i get a week range for a given month in Postgress

This is my current implementation
SELECT
date_trunc('month', do_date::date)::date as starting_of_the_month,
(date_trunc('month', do_date::date) + interval '1 month' - interval '1 day')::date as ending_of_the_month,
case when 1 + FLOOR((EXTRACT(DAY FROM do_date) - 1) / 7) = 1
THEN date_trunc('week', do_date)::date || ' - ' ||
(date_trunc('week', do_date) + '6 days') ::date end as week1,
case when 1 + FLOOR((EXTRACT(DAY FROM do_date) - 1) / 7) = 2
THEN date_trunc('week', do_date)::date || ' - ' ||
(date_trunc('week', do_date) + '6 days') ::date end as week2,
case when 1 + FLOOR((EXTRACT(DAY FROM do_date) - 1) / 7) = 3
THEN date_trunc('week', do_date)::date || ' - ' ||
(date_trunc('week', do_date) + '6 days') ::date end as week3,
case when 1 + FLOOR((EXTRACT(DAY FROM do_date) - 1) / 7) = 4
THEN date_trunc('week', do_date)::date || ' - ' ||
(date_trunc('week', do_date) + '6 days') ::date end as week4,
case when 1 + FLOOR((EXTRACT(DAY FROM do_date) - 1) / 7) = 5
THEN date_trunc('week', do_date)::date || ' - ' ||
(date_trunc('week', do_date) + '6 days') ::date end as week5
FROM sales_dos
WHERE date_trunc('month', do_date::date)::date >= '2021-02-01' AND date_trunc('month', do_date::date)::date < '2021-02-28'
This is my output for now :
I want the output to display as below :
Week 1 : 2021-02-01 - 2021-02-07
Week 2 : 2021-02-08 - 2021-02-14
Week 3 : 2021-02-15 - 2021-02-21
Week 4 : 2021-02-22 - 2021-02-28
Week 5 : -
Here is another way to do it (example for January 2021).
with
t as (select date_trunc('month', '2021-03-11'::date) as aday), -- any date in Jan-2021
s as
(
select d::date, d::date + 6 ed, extract('isodow' from d) wd
from t, generate_series (aday, aday + interval '1 month - 1 day', interval '1 day') d
)
select format ('Week %s', extract(day from d)::integer / 7 + 1) as weekname, d, ed
from s
where wd = 1;
So what you are looking for is a hybrid ISO with standard Calendar. You are taking the ISO week starting and ending period, but instead of all weeks being exactly 7 days you potentially truncate the 1st and/or last weeks.
The change to need for this is not actually extensive. For initial query returns the in the ISO week begin date instead of the 1st of the month. Then the main query then checks for week 1 and if so produces the 1st of the month. The only twist is determining the ISO week begin date. For this I've just included a function I have had for some time specifically for that. The change to the week_days function are marked --<<<.
create or replace function iso_first_of_week(date_in date)
returns date
language sql
immutable strict
/*
Given a date return the 1st day of the week according to ISO-8601.
I.e. Return the Date if it is Monday otherwise return the preceding Monday
*/
AS $$
with wk_adj(l_days) as (values (array[0,1,2,3,4,5,6]))
select date_in - l_days[ extract (isodow from date_in)::integer ]
from wk_adj;
$$;
create or replace
function week_dates( do_date_in date)
returns table (week_num integer, first_date date, last_date date)
language sql
immutable strict
as $$
with recursive date_list(week_num,first_date,terminate_date) as
( select 1
, iso_first_of_week(do_date_in)::timestamp --<<<
, (date_trunc('month', do_date_in) + interval '1 month' - interval '1 day')::timestamp
union all
select week_num+1, (first_date+interval '7 day'), terminate_date
from date_list
where first_date+interval '6 day' < terminate_date::timestamp
)
select week_num
, case when week_num = 1 --<<<
then date_trunc('month', do_date_in)::date --<<<
else first_date::date --<<<
end --<<<
, case when (first_date+interval '6 day')::date > terminate_date
then terminate_date::date
else (first_date+interval '6 day')::date
end last_date
from date_list;
$$;
---------- Original Reply
You can use a recursive query CTE to get the week number and first date for each week of the month specified. The main query calculates the ending date, shorting the last if necessary. Then wrap that into a SQL function to return the week number and date range for each week. See example.
create or replace
function week_dates( do_date_in date)
returns table (ween_num integer, first_date date, last_date date)
language sql
immutable strict
as $$
with recursive date_list(week_num,first_date,terminate_date) as
( select 1
, date_trunc('month', do_date_in)::timestamp
, (date_trunc('month', do_date_in) + interval '1 month' - interval '1 day')::timestamp
union all
select week_num+1, (first_date+interval '7 day'), terminate_date
from date_list
where first_date+interval '6 day' < terminate_date::timestamp
)
select week_num
, first_date::date
, case when (first_date+interval '6 day')::date > terminate_date
then terminate_date::date
else (first_date+interval '6 day')::date
end last_date
from date_list;
$$;
Response to: "How can i put the output in a single row with week1, week2, week3, week4 and week5". This is essentially the initial output that did not satisfy what you wanted. The term for this type action is PIVOT and is generally understood. It stems from transforming row orientation to column orientation. It is not overly difficult but it is messy.
IMHO this is something that belongs in the presentation layer and is not suitable for SQL. After all you are rearranging the data structure for presentation purposes. Let the database server use its natural format, use the presentation layer to reformat. This allows reuse of the queries instead of rewriting when the presentation is changed or another view of the same data is required.
If you actually want this then just use your initial query, or see the answer from
#Bohemian. However the below shows how this issue can be handled with just SQL (assuming the function week_dates was created).
select week1s
, case when week5e is null
then week4e
else week5e
end "end of month"
, week1s || ' - ' || week1e
, week2s || ' - ' || week2e
, week3s || ' - ' || week3e
, week4s || ' - ' || week4e
, week5s || ' - ' || week5e
from ( select max(case when (week_num=1) then first_date else NULL end) as week1s
, max(case when (week_num=1) then last_date else NULL end) as week1e
, max(case when (week_num=2) then first_date else NULL end) as week2s
, max(case when (week_num=2) then last_date else NULL end) as week2e
, max(case when (week_num=3) then first_date else NULL end) as week3s
, max(case when (week_num=3) then last_date else NULL end) as week3e
, max(case when (week_num=4) then first_date else NULL end) as week4s
, max(case when (week_num=4) then last_date else NULL end) as week4e
, max(case when (week_num=5) then first_date else NULL end) as week5s
, max(case when (week_num=5) then last_date else NULL end) as week5e
from week_dates(current_date)
) w ;
As before I have wrapped the above in a SQL function and provide an example here.
I would first simplify to:
extract(day from do_date)::int / 7 + 1 as week_in_month
then pivot on that using crosstab().

DATE ADD function in PostgreSQL

I currently have the following code in Microsoft SQL Server to get users that viewed on two days in a row.
WITH uservideoviewvideo (date, user_id) AS (
SELECT DISTINCT date, user_id
FROM clickstream_videos
WHERE event_name ='video_play'
and user_id IS NOT NULL
)
SELECT currentday.date AS date,
COUNT(currentday.user_id) AS users_view_videos,
COUNT(nextday.user_id) AS users_view_next_day
FROM userviewvideo currentday
LEFT JOIN userviewvideo nextday
ON currentday.user_id = nextday.user_id AND DATEADD(DAY, 1,
currentday.date) = nextday.date
GROUP BY currentday.date
I am trying to get the DATEADD function to work in PostgreSQL but I've been unable to figure out how to get this to work. Any suggestions?
I don't think PostgreSQL really has a DATEADD function. Instead, just do:
+ INTERVAL '1 day'
SQL Server:
Add 1 day to the current date November 21, 2012
SELECT DATEADD(day, 1, GETDATE()); # 2012-11-22 17:22:01.423
PostgreSQL:
Add 1 day to the current date November 21, 2012
SELECT CURRENT_DATE + INTERVAL '1 day'; # 2012-11-22 17:22:01
SELECT CURRENT_DATE + 1; # 2012-11-22 17:22:01
http://www.sqlines.com/postgresql/how-to/dateadd
EDIT:
It might be useful if you're using a dynamic length of time to create a string and then cast it as an interval like:
+ (col_days || ' days')::interval
You can use date + 1 to do the equivalent of dateadd(), but I do not think that your query does what you want to do.
You should use window functions, instead:
with plays as (
select distinct date, user_id
from clickstream_videos
where event_name = 'video_play'
and user_id is not null
), nextdaywatch as (
select date, user_id,
case
when lead(date) over (partition by user_id
order by date) = date + 1 then 1
else 0
end as user_view_next_day
from plays
)
select date,
count(*) as users_view_videos,
sum(user_view_next_day) as users_view_next_day
from nextdaywatch
group by date
order by date;

TSQL SELECT data within range of year and month

I'm trying to query data within a range of start year and month and end year and month. But SQL returnes onty the year and the month chosen. Can anyone identify the problem with my approach.
Thanks!
ALTER PROCEDURE xxx
(#JaarBegin AS int
, #JaarEind AS int
, #MaandBegin AS int
, #MaandEind AS int)
AS
BEGIN
WITH
CTE AS
(
SELECT [D_Medewerker_ID]
,[Gebruikersnaam]
,[Naam]
,[Afdelingscode]
,CONVERT(date, [Datum_uit_dienst]) AS DatumIn
,CONVERT(date, [Datum_in_dienst]) AS DatumUit
FROM [DM].[dm].[D_Medewerker] AS M
),
CTE2 AS(
SELECT F.[D_Functie_ID]
,[Generieke_Functie]
,[Specifieke_Functie]
,Fo.[D_Medewerker_ID]
FROM [DM].[dm].[D_Functie] AS F
JOIN dm.dm.F_FormatieBezetting AS Fo
ON F.D_Functie_ID = Fo.D_Functie_ID
)
SELECT DISTINCT CTE.[Gebruikersnaam]
, CTE.Naam
, CTE.Afdelingscode
, CTE.DatumIn
, CTE.DatumUit
, CTE2.Generieke_Functie
, CTE2.Specifieke_Functie
FROM CTE
JOIN CTE2
ON CTE.D_Medewerker_ID = CTE2.D_Medewerker_ID
WHERE DATEPART(year,CTE.DatumUit) BETWEEN #JaarBegin AND #JaarEind
AND DATEPART(MONTH, CTE.DatumUit) >= #MaandBegin AND DATEPART(MONTH, CTE.DatumUit) <= #MaandEind
ORDER BY CTE.DatumUit DESC;
END
You need to convert the int values you get to a date value.
In Sql server 2012 or later, you can use the built-in function DATEFROMPARTS to do this:
WHERE CTE.DatumUit >= DATEFROMPARTS ( #JaarBegin , #MaandBegin , 1 )
AND CTE.DatumUit < DATEADD(MONTH, 1, DATEFROMPARTS ( #JaarEind , #MaandBegin , 1 ))
If you are working with an earlier version of sql server, you need to build a string that represents the date (using iso format yyyy-mm-dd) and then cast it to date:
WHERE CTE.DatumUit >= CAST(RIGHT('0000' + CAST(#JaarBegin as varchar(4)), 4) + '-' + RIGHT('00' + CAST(#MaandBegin as varchar(2)), 2) +'-01' as datetime)
AND CTE.DatumUit < DATEADD(MONTH, 1, CAST(RIGHT('0000' +CAST(#JaarEind as varchar(4)), 4) + '-' + RIGHT('00' + CAST(#MaandBegin as varchar(2)), 2) +'-01' as datetime))

How to calculate next birthday given a date of birth?

Given this schema in a Postgres database:
CREATE TABLE person (
id serial PRIMARY KEY,
name text,
birth_date date,
);
How would I query the table to get the date of each person's next birthday after today?
For example if Bob's birth_date is 2000-06-01 then his next birthday would be 2016-06-01.
Note: I'm not looking for the birth_date + a pre-defined interval but rather the next anniversary of a person's birth.
I've written the equivalent in Python:
def next_birthday(self):
today = datetime.date.today()
next_birthday = self.birth_date.replace(year=today.year)
if next_birthday < today:
next_birthday = next_birthday.replace(year=today.year + 1)
return next_birthday
However I'd like to see if Postgres can do this in a more performant way.
select birth_date,
cast(birth_date + ((extract(year from age(birth_date)) + 1) * interval '1' year) as date) as next_birthday
from person
where name = 'Bob'
The expression (extract(year from age(birth_date)) + 1) * interval '1' year calculates the age at the next birthday in (complete) years. When adding that to the date of birth, this gives the next birthday.
The cast is necessary to get a real date back, because date + interval returns a timestamp (including a time).
If you remove the where condition, you'll get all "next" birthdays.
You can also get a list of the upcoming birthdays in e.g. the next 30 days using something like this:
select next_birthday,
next_birthday - current_date as days_until_next
from (
select birth_date,
cast(birth_date + ((extract(year from age(birth_date)) + 1) * interval '1' year) as date) as next_birthday
from person
) as upcoming
where upcoming.next_birthday <= current_date + 30
order by next_birthday;
SELECT
birth_date
+
cast(
date_part('year', current_date)
-
date_part('year', birth_date)
+
CASE
WHEN
date_part('month', birth_date) <
date_part('month', current_date)
OR (
date_part('month', birth_date) =
date_part('month', current_date)
AND
date_part('day', birth_date) <
date_part('day', current_date)
)
THEN 1
ELSE 0
END || ' year' as interval)
FROM person;
SELECT date birth_date + interval '1 year' as next_birthday
from person;

How do you get positive and negative rows based on date

I'm trying to get ordinance by the current date from a table. this query does what I want but it seems overkill:
WITH dates
AS (SELECT Month,
FQ,
FY,
MonthDisplay,
CAST (datepart(yyyy, [Month]) AS VARCHAR) + '-' + RIGHT(CAST ((datepart(MM, [Month]) + 100) AS VARCHAR), 2) AS YM,
fh,
LEFT(CONVERT (VARCHAR, [Month], 100), 3) + ' ' + RIGHT(fy, 4) AS MY,
LEFT(CONVERT (VARCHAR, [Month], 100), 3) AS ShortMonthName
FROM Pipeline.DimTime AS dt),
datesafter
AS (SELECT dt.FH,
dt.FQ,
dt.FY,
dt.MY,
dt.Month,
dt.MonthDisplay,
dt.ShortMonthName,
dt.YM,
ROW_NUMBER() OVER ( ORDER BY [Month]) AS RowNum
FROM dates AS dt
WHERE dt.[Month] >= (SELECT TOP 1 DATEADD(MONTH, DATEDIFF(MONTH, 0, ds.SnapshotDate), 0)
FROM dbo.vw_DimSnapshot AS ds
WHERE ds.SnapshotWeek = 'Current')),
datesbefore
AS (SELECT dt.FH,
dt.FQ,
dt.FY,
dt.MY,
dt.Month,
dt.MonthDisplay,
dt.ShortMonthName,
dt.YM,
(ROW_NUMBER() OVER ( ORDER BY [Month] DESC)) * -1 AS RowNum
FROM dates AS dt
WHERE dt.[Month] < (SELECT TOP 1 DATEADD(MONTH, DATEDIFF(MONTH, 0, ds.SnapshotDate), 0)
FROM dbo.vw_DimSnapshot AS ds
WHERE ds.SnapshotWeek = 'Current'))
SELECT *
FROM datesafter
UNION ALL
SELECT *
FROM datesbefore
ORDER BY [month];
I think you can do it in a single query by using datediff. Pass current date as one parameter and the table date as another. This will work if you are ok with skipping missing dates. For example if current date is Nov 21, then Nov 20 will show up as -1 and Nov 18 will show up as -3 even if Nov 19 is missing in the data. I am not sure what your business requirement is, so cannot comment beyond that.
And by the way if you are looking for ordinance based on months instead of day, you can use still use datediff but use the correct datepart (See: http://msdn.microsoft.com/en-us/library/ms189794.aspx).
HTH.
-Tabrez