MySQL Weekday/Weekend count - Part II - sql

I have posted about this before, which helped to give me the following SQL:
SELECT fname, MONTH( eventDate ) , IF( WEEKDAY( eventDate ) <5, 'weekday', 'weekend' ) AS
DAY , COUNT( * )
FROM eventcal AS e
LEFT JOIN users AS u ON e.primary = u.username
GROUP BY fname, MONTH( eventDate ) , IF( WEEKDAY( eventDate ) <5, 'weekday', 'weekend' ) ;
And that gives me the following results:
fname MONTH( eventDate ) DAY COUNT( * )
Kevin 7 weekday 3
Kevin 7 weekend 1
Missy 7 weekday 3
Missy 7 weekend 1
I'm having some trouble trying to achieve the following format:
fname MONTH( eventDate ) Weekday COUNT WEEKEND COUNT
Kevin 7 3 1
Missy 7 3 1
Can anyone offer some help? I would greatly appreciate it...
You can see my schemas for 'user' and 'eventcal' at: MySQL/PHP Algorithm for Weekday/Weekend count (per month)

SELECT
fname,
MONTH(eventDate),
SUM(IF(WEEKDAY(eventDate) < 5,1,0)) AS WeekdayCount,
SUM(IF(WEEKDAY(eventDate) >= 5,1,0)) AS WeekendCount
FROM eventcal AS e
LEFT JOIN users AS u ON e.primary = u.username
GROUP BY fname, MONTH(eventDate);
You want to do your aggregations (SUM in this case) in the SELECT, and GROUP BY how you want them totaled (by fname, by MONTH).

Related

SQL Join two tables by unrelated date

I’m looking to join two tables that do not have a common data point, but common value (date). I want a table that lists the date and total number of hired/terminated employees on that day. Example is below:
Table 1
Hire Date Employee Number Employee Name
--------------------------------------------
5/5/2018 10078 Joe
5/5/2018 10077 Adam
5/5/2018 10078 Steve
5/8/2018 10079 Jane
5/8/2018 10080 Mary
Table 2
Termination Date Employee Number Employee Name
----------------------------------------------------
5/5/2018 10010 Tony
5/6/2018 10025 Jonathan
5/6/2018 10035 Mark
5/8/2018 10052 Chris
5/9/2018 10037 Sam
Desired result:
Date Total Hired Total Terminated
--------------------------------------
5/5/2018 3 1
5/6/2018 0 2
5/7/2018 0 0
5/8/2018 2 1
5/9/2018 0 1
Getting the total count is easy, just unsure as the best approach from the standpoint of "adding" a date column
If you need all dates within some window then you need to join the data to a calendar. You can then left join and sum flags for data points.
DECLARE #StartDate DATETIME = (SELECT MIN(ActionDate) FROM(SELECT ActionDate = MIN(HireDate) FROM Table1 UNION SELECT ActionDate = MIN(TerminationDate) FROM Table2)AS X)
DECLARE #EndDate DATETIME = (SELECT MAX(ActionDate) FROM(SELECT ActionDate = MAX(HireDate) FROM Table1 UNION SELECT ActionDate = MAX(TerminationDate) FROM Table2)AS X)
;WITH AllDates AS
(
SELECT CalendarDate=#StartDate
UNION ALL
SELECT DATEADD(DAY, 1, CalendarDate)
FROM AllDates
WHERE DATEADD(DAY, 1, CalendarDate) <= #EndDate
)
SELECT
CalendarDate,
TotalHired = SUM(CASE WHEN H.HireDate IS NULL THEN NULL ELSE 1 END),
TotalTerminated = SUM(CASE WHEN T.TerminationDate IS NULL THEN NULL ELSE 1 END)
FROM
AllDates D
LEFT OUTER JOIN Table1 H ON H.HireDate = D.CalendarDate
LEFT OUTER JOIN Table2 T ON T.TerminationDate = D.CalendarDate
/* If you only want dates with data points then uncomment out the where clause
WHERE
NOT (H.HireDate IS NULL AND T.TerminationDate IS NULL)
*/
GROUP BY
CalendarDate
I would do this with a union all and aggregations:
select dte, sum(is_hired) as num_hired, sum(is_termed) as num_termed
from (select hiredate as dte, 1 as is_hired, 0 as is_termed from table1
union all
select terminationdate, 0 as is_hired, 1 as is_termed from table2
) ht
group by dte
order by dte;
This does not include the "missing" dates. If you want those, a calendar or recursive CTE works. For instance:
with ht as (
select dte, sum(is_hired) as num_hired, sum(is_termed) as num_termed
from (select hiredate as dte, 1 as is_hired, 0 as is_termed from table1
union all
select terminationdate, 0 as is_hired, 1 as is_termed from table2
) ht
group by dte
),
d as (
select min(dte) as dte, max(dte) as max_dte)
from ht
union all
select dateadd(day, 1, dte), max_dte
from d
where dte < max_dte
)
select d.dte, coalesce(ht.num_hired, 0) as num_hired, coalesce(ht.num_termed) as num_termed
from d left join
ht
on d.dte = ht.dte
order by dte;
Try this one
SELECT ISNULL(a.THE_DATE, b.THE_DATE) as Date,
ISNULL(a.Total_Hire,0) as Total_Hire,
ISNULL (b.Total_Terminate,0) as Total_terminate
FROM (SELECT Hire_date as the_date, COUNT(1) as Total_Hire
FROM TABLE_HIRE GROUP BY HIRE_DATE) a
FULL OUTER JOIN (SELECT Termination_Date as the_date, COUNT(1) as Total_Terminate
FROM TABLE_TERMINATE GROUP BY HIRE_DATE) a
ON a.the_date = b.the_date

How to duplicate data in sql with conditions

I havea table as table_A . table_A includes these columns
-CountryName
-Min_Date
-Max_Date
-Number
I want to duplicate data with seperating by months. For example
Argentina | 2015-01-04 | 2015-04-07 | 100
England | 2015-02-08 | 2015-03-11 | 90
I want to see a table as this (Monthly seperated)
Argentina | 01-2015 | 27 //(days to end of the min_date's month)
Argentina | 02-2015 | 29 //(days full month)
Argentina | 03-2015 | 31 //(days full month)
Argentina | 04-2015 | 7 //(days from start of the max_date's month)
England | 02-2015 | 21 //(days)
England | 03-2015 | 11 //(days)
I tried too much thing to made this for each records. But now my brain is so confusing and my project is delaying.
Does anybody know how can i solve this. I tried to duplicate each rows with datediff count but it is not working
WITH cte AS (
SELECT CountryName, ISNULL(DATEDIFF(M,Min_Date ,Max_Date )+1,1) as count FROM table_A
UNION ALL
SELECT CountryName, count-1 FROM cte WHERE count>1
)
SELECT CountryName,count FROM cte
-Generate all the dates between min and max dates for each country.
-Then get the month start and month end dates for each country,year,month.
-Finally get the date differences of the month start and month end.
WITH cte AS (
SELECT Country, min_date dt,min_date,max_date FROM t
UNION ALL
SELECT Country, dateadd(dd,1,dt),min_date,max_date FROM cte WHERE dt < max_date
)
,monthends as (
SELECT country,year(dt) yr,month(dt) mth,max(dt) monthend,min(dt) monthstart
FROM cte
GROUP BY country,year(dt),month(dt))
select country
,cast(mth as varchar(2))+'-'+cast(yr as varchar(4)) yr_month
,datediff(dd,monthstart,monthend)+1 days_diff
from monthends
Sample Demo
EDIT: Another option would be to generate all the dates once (the example shown here generates 51 years of dates from 2000 to 2050) and then joining it to the table to get the days by month.
WITH cte AS (
SELECT cast('2000-01-01' as date) dt,cast('2050-12-31' as date) maxdt
UNION ALL
SELECT dateadd(dd,1,dt),maxdt FROM cte WHERE dt < maxdt
)
SELECT country,year(dt) yr,month(dt) mth, datediff(dd,min(dt),max(dt))+1 days_diff
FROM cte c
JOIN t on c.dt BETWEEN t.min_date and t.max_date
GROUP BY country,year(dt),month(dt)
OPTION (MAXRECURSION 0)
I think you have the right idea. But you need to construct the months:
WITH cte AS (
SELECT CountryName, Min_Date as dte, Min_Date, Max_Date
FROM table_A
UNION ALL
SELECT CountryName, DATEADD(month, 1, dte), Min_Date, Max_Date
FROM cte
WHERE dte < Max_date
)
SELECT CountryName, dte
FROM cte;
Getting the number of days in the month is a bit more complicated. That requires some thought.
Oh, I forgot about EOMONTH():
select countryName, dte,
(case when dte = min_date
then datediff(day, min_date, eomonth(dte)) + 1
when dte = max_date
then day(dte)
else day(eomonth(dte))
end) as days
from cte;
Using a Calendar Table makes this stuff pretty easy. RexTester: http://rextester.com/EBTIMG23993
begin
create table #enderaric (
CountryName varchar(16)
, Min_Date date
, Max_Date date
, Number int
)
insert into #enderaric values
('Argentina' ,'2015-01-04' ,'2015-04-07' ,'100')
, ('England' ,'2015-02-08' ,'2015-03-11' ,'90')
end;
-- select * from #enderaric
--*/"
declare #FromDate date;
declare #ThruDate date;
set #FromDate = '2015-01-01';
set #ThruDate = '2015-12-31';
with x as (
select top (cast(sqrt(datediff(day, #FromDate, #ThruDate)) as int) + 1)
[number]
from [master]..spt_values v
)
/* Date Range CTE */
,cal as (
select top (1+datediff(day, #FromDate, #ThruDate))
DateValue = convert(date,dateadd(day,
row_number() over (order by x.number)-1,#FromDate)
)
from x cross join x as y
order by DateValue
)
select
e.CountryName
, YearMonth = convert(char(7),left(convert(varchar(10),DateValue),7))
, [Days]=count(c.DateValue)
from #enderaric as e
inner join cal c on c.DateValue >= e.min_date
and c.DateValue <= e.max_date
group by
e.CountryName
, e.Min_Date
, e.Max_Date
, e.Number
, convert(char(7),left(convert(varchar(10),DateValue),7))
results in:
CountryName YearMonth Days
---------------- --------- -----------
Argentina 2015-01 28
Argentina 2015-02 28
Argentina 2015-03 31
Argentina 2015-04 7
England 2015-02 21
England 2015-03 11
More about calendar tables:
Aaron Bertrand - Generate a set or sequence without loops
generate-a-set-1
generate-a-set-2
generate-a-set-3
David Stein - Creating a Date Table/Dimension on SQL 2008
Michael Valentine Jones - F_TABLE_DATE

How to find contiguous dates in numerous rows in SQL Server

We have a table with service provisions for people. For example:
id people_id dateStart dateEnd
1 1 28.07.14 19.07.16
2 2 14.04.15 16.02.16
3 2 16.02.16 18.04.16
4 2 18.04.16 27.06.16
5 2 27.06.16 19.07.16
6 2 19.07.16 NULL
7 3 24.02.12 17.06.12
8 3 23.07.12 19.09.12
9 3 18.08.14 NULL
10 4 28.06.15 NULL
11 5 19.01.16 NULL
I need to extract distinct people_id's (clients) with real start date of unfinished uninterrupted service that lasts more than year and then count days pass. 'Start date' and 'End date' of two different rows should be the same to be count as contiguous. One client can only have one unfinished service.
So the perfect result for the table above would be:
people_id dateStart lasts(days)
2 14.04.15 472
3 18.08.14 711
4 28.06.15 397
I didn't have problem with a single service:
SELECT
--some other columns from PEOPLE,
p.PEOPLE_ID,
s.DATESTART,
DATEDIFF(DAY, s.DATESTART, GETDATE()) as lasts
FROM
PEOPLE p
INNER JOIN service s on s.ID =
(
SELECT TOP 1 s2.ID
FROM service s2
WHERE s2.PEOPLE_ID = p.PEOPLE_ID
AND s2.DATESTART IS NOT NULL
AND s2.DATEEND IS NULL
ORDER BY s2.DATESTART DESC
)
WHERE
DATEDIFF(DAY, s.DATESTART , GETDATE()) >= 365
But I can't figure out how to determine contiguous services.
You can find where periods of "continuous" service begin by using lag(). Then a cumulative sum of this flag provides a group, which can be used for aggregation:
select people_id, min(datestart) as datestart,
(case when count(dateend) = count(*) then max(dateend) end) as dateend
from (select t.*,
sum(case when prev_dateend = datestart then 0 else 1 end) over
(partition by people_id order by datestart) as grp
from (select t.*,
lag(dateend) over (partition by people_id order by date_start) as prev_dateend
from t
) t
) t
group by people_id, grp
having count(*) > count(dateend);
Try this query:
select PeopleId, min(dateStart) as dateStart, sum(diff) as [lasts(days)] from
(
select P.*, datediff(day,datestart, DateEnd) as diff from
(select peopleId, dateStart,
isnull(dateend, cast(getdate() as date)) as DateEnd
from People
) P
where Dateend in
(select DateStart from People
where PeopleId = P.PeopleId)
or DateEnd = cast(getdate() as date ) -- check for continuous dates
) P1 group by PeopleId having sum(diff)> 365 --check for > one year
The comments in the query should explain things

Select overlapping holidays per department

I'm trying to check if any of the employees at my company are requesting overlapping holidays. It's the policy that only 1 person per department is allowed to be off at once.
What query should I use? (I am a noob, so tell me if you need more information).
An Example of the table I want to use:
Request ID Employee ID Department ID Start Date End Date
1 10 1 2015-12-20 2016-12-27
2 10 1 2016-06-01 2015-06-14
3 11 1 2015-12-26 2015-12-27
4 11 1 2016-06-09 2016-06-23
5 12 2 2015-12-26 2015-12-26
6 12 2 2016-07-01 2016-07-14
Results:
Request ID Status
1 Not Approved, overlapping 26-27/12
2 Not Approved, overlapping 09-14/06
3 Not Approved, overlapping completely
4 Not Approved, overlapping 09-14/06
5 Approved, not overlapping in this department
6 Approved, not overlapping in this department
In the second phase, I want to compare if the holidays requested, are within a week, containing a bank holiday (I will have a different table with the bank holidays).
Thanks in advance!
One way is with exists:
select e.*,
(case when exists (select 1
from example e2
where e2.departmentid = e.departmentid and
e2.employeeid <> e.employeeid and
e2.startdate <= e.enddate and
e2.enddate >= e.startdate
)
then 'Overlapping'
else 'NotOverlapping'
end) as Status
from example e;
Getting your full message is trickier and depends on the database. The problem are multiple overlaps.
Actually, we can get more information without too much problem:
select e.RequestId,
(case when count(e2.RequestId) = 0
then 'Approved, not overlapping in this department'
when count(e2.RequestId) = 1
then (case when min(e2.startdate) <= e.startdate and
max(e2.enddate) >= e.enddate
then 'Not Approved, overlapping completely'
else 'Not Approved, overlapping partially'
end)
else 'Not Approved, multiple overlaps'
end) as Status
from example e left join
example e2
on e2.departmentid = e.departmentid and
e2.startdate <= e.enddate and
e2.enddate >= e.startdate and
e2.employeeid <> e.employeeid
group by e.RequestId, e.startdate, e.enddate;
Getting the actual dates is trickier, without knowing the database.
create table #holidays(RequestID int, EmployeeID int, DepartmentID int, StartDate date, EndDate date)
insert into #holidays
select 1, 10, 1, '2015-12-20', '2016-12-27'
union select 2, 10, 1, '2016-06-01', '2015-06-14'
union select 3, 11, 1, '2015-12-26', '2015-12-27'
union select 4, 11, 1, '2016-06-09', '2016-06-23'
union select 5, 12, 2, '2015-12-26', '2015-12-26'
union select 6, 12, 2, '2016-07-01', '2016-07-14'
with CTE
as (
select T1.RequestID,max(T2.RequestID) as T2RequestID, count(*) as cnt
from #holidays T1
join #holidays T2 on
T1.DepartmentID = T2.DepartmentID
and T1.StartDate < T2.EndDate and T1.EndDate > T2.StartDate
and T1.RequestID <> T2.RequestID
group by T1.RequestID
)
select H.*,
case
when isnull(CTE.cnt,0) = 0 then 'Approved, not overlapping in this department'
else 'Not Approved, overlapping ' + convert(nvarchar(50),H2.StartDate) + ' - ' + convert(nvarchar(50),H2.EndDate)
end as Res
from #holidays H
left join CTE on H.RequestID = CTE.RequestID
left join #holidays H2 on H2.RequestID = CTE.T2RequestID
Btw, first employer have too big holidays - more than 1 year long :)
Another StartDate > EndDate (what is it ?)
Result

Combining Union results

I have the below SQL Query
select Count(emailID) as ViewsThatMonth,
Day(entry_date) as day,
Month(entry_date) as month,
Year(entry_date) as year
from email_views
where emailID = 110197
Group By Day(entry_date), Month(entry_date), Year(entry_date)
UNION ALL
select Count(emailID) as ViewsThatMonth,
Day(Record_Entry) as day,
Month(Record_Entry) as month,
Year(Record_Entry) as year
from dbo.tblOnlineEmail_Views
where emailID = 110197
Group By Day(Record_Entry), Month(Record_Entry), Year(Record_Entry)
order by 4, 3, 2
The results are showing as below. I need the results on the same date to be combined. I.e. the total for the 23/8/2010 should be 800.
ViewsThatMonth day month year
---------------------------------
799 23 8 2010
1 23 8 2010
281 24 8 2010
88 25 8 2010
1 25 8 2010
You only need to group by once:
SELECT Count(emailID) as ViewsThatMonth,
Day(entry_date) as day,
Month(entry_date) as month,
Year(entry_date) as year
from(
select emailID, Record_Entry AS entry_date
from email_views
where emailID = 110197
UNION ALL
select emailID, entry_date
from dbo.tblOnlineEmail_Views
where emailID = 110197
) AS t
Group By Day(entry_date), Month(entry_date), Year(entry_date)
order by 4, 3, 2
Basically the easiest way is to make your union a derived table or CTE and then group them by date.
IE.
select
sum(dt.ViewsThatMonth) as ViewsThatMonth
,dt.[day]
,dt.[month]
,dt.[year]
from
(select Count(emailID) as ViewsThatMonth, Day(entry_date) as day, Month(entry_date) as month, Year(entry_date) as year from email_views
where emailID = 110197
Group By Day(entry_date), Month(entry_date), Year(entry_date)
UNION ALL
select Count(Record_Entry) as ViewsThatMonth, Day(Record_Entry) as day, Month(Record_Entry) as month, Year(Record_Entry) as year from dbo.tblOnlineEmail_Views
where emailID = 110197
Group By Day(Record_Entry), Month(Record_Entry), Year(Record_Entry)
) dt
group by [day], [month], [year]
order by dt.[year], dt.[month], dt.[day]
Keeping UNIONed code to a minimum:
select Count(emailID) as ViewsThatMonth,
Day(sort_date) as day,
Month(sort_date) as month,
Year(sort_date) as year
from (select v.*,
case c.caseid when 1 then entry_date else record_entry end sort_date
from email_views v
cross join (select 1 caseid union all select 2 caseid) c
where v.emailID = 110197) sq
Group By Day(sort_date), Month(sort_date), Year(sort_date)
EDIT: Added alias to subquery