Select dates in ranges list - sql

I have table with records, each row contains DATETIME column which describes when row was loaded into table. And I have CTE which creates ranges (count is vary) like one below.
first_day_of_month last_day_of_moth
-------------------------------------------------------
2013-12-01 00:00:00.000 2013-12-31 23:59:59.000
2013-11-01 00:00:00.000 2013-12-31 23:59:59.000
2013-10-01 00:00:00.000 2013-12-31 23:59:59.000
2013-09-01 00:00:00.000 2013-12-31 23:59:59.000
2013-08-01 00:00:00.000 2013-12-31 23:59:59.000
Question: Now I want to select minimal DATETIME value from first table for each range created in CTE. I am absolutely have no idea how to do it. Any ideas/links are appreciated.
For example, it should looks like:
2013-12-10
2013-11-20
2013-10-05
2013-09-13
2013-08-06
UPD: Date or datetime - it is no matter
UPD2: I found that I can join my tables using condition like:
INNER JOIN source_monthes_dates ON
(load_timestamp >= first_day_of_month AND load_timestamp <= last_day_of_moth)
but actually I do not know how to acquire only first date of period.

You can use this query which uses ROW_NUMBER() to get the minimum. ranges is the result of your CTE, table1 is the other table where you have dates.
select x.somedate
from
(select t.somedate,
ROW_NUMBER() OVER (PARTITION BY r.first_day_of_month, r.last_day_of_moth ORDER BY t.somedate) rownumber
from ranges r
inner join table1 t
on r.first_day_of_month <= t.somedate and r.last_day_of_moth >= t.somedate) x
where x.rownumber = 1
SQL Fiddle demo
If you want to get all the ranges and include only those days that match ranges and display null for others, you can join ranges once more:
select ranges.first_day_of_month, ranges.last_day_of_moth, x.somedate
from
ranges
left join
(select t.somedate, r.first_day_of_month, r.last_day_of_moth,
ROW_NUMBER() OVER (PARTITION BY r.first_day_of_month, r.last_day_of_moth ORDER BY t.somedate) rownumber
from ranges r
inner join table1 t
on r.first_day_of_month <= t.somedate and r.last_day_of_moth >= t.somedate) x
on x.first_day_of_month = ranges.first_day_of_month and x.last_day_of_moth = ranges.last_day_of_moth
where isnull(x.rownumber, 1) = 1
SQL Fiddle demo

Related

INNER JOIN SQL with DateTime return multiple record

I have the following table:
Group RecDate oData
---------------------------------------
123 2022-03-20 02:00:00 F1xR
123 2022-03-21 02:30:00 F1xF
123 2022-03-22 05:00:00 F1xN
123 2022-03-15 04:00:00 F2xR
From the table above, I want to get the MAX date group by 2 char from oData field. Then I wrote a query like this:
SELECT a.Group, MAX(a.RecDate) RecDate, LEFT(a.oData, 2) oDataNo
INTO #t1
FROM TableData a
GROUP BY a.Group, LEFT(a.oData, 2)
SELECT * FROM #t1
Then, the result should be:
Group RecDate oDataNo
--------------------------------------------
123 2022-03-22 05:00:00 F1
123 2022-03-15 04:00:00 F2
From the result above (#t1), I want to join with the TableData to get the RIGHT character (1 digit) from oData field. So I INNER JOIN the #t1 with TableData. The JOIN field is RecDate. But it is strange that the result isn't what I want.
The query like:
SELECT RIGHT(a.oData,1) oDataStat, b.*
FROM TableData a
INNER JOIN #t1 b ON a.RecDate = b.RecDate
The wrong result like:
The result should be:
Group RecDate oDataNo oDataStat
-----------------------------------------------------------
123 2022-03-22 05:00:00 F1 N
123 2022-03-15 04:00:00 F2 R
Am I doing wrong approach?
Please advise. Really appreciated.
Thank you.
The query you provided returns the data you desire. However its cleaner to do it in a single query e.g.
WITH cte AS (
SELECT *
, RIGHT(a.oData,1) oDataStat
, ROW_NUMBER() OVER (PARTITION BY LEFT(a.oData, 2) ORDER BY RecDate DESC) rn
FROM TableData a
)
SELECT [Group], RecDate, oData, oDataStat
FROM cte
WHERE rn = 1
ORDER BY RecDate;
returns:
Group
RecDate
oData
oDataStat
123
2022-03-15 04:00:00
F2xR
R
123
2022-03-22 05:00:00
F1xN
N
Note: Your query as posted doesn't actually run due to not escaping [Group] - you should ensure everything you post has any errors removed first.

How to select Maximum and minimum date values and pass those as a query

I have a table with the following entries
CustomeID
TransDate
WorkID
1
2012-12-01
12
1
2012-12-03
45
1
2013-01-21
3
2
2012-12-23
11
3
2013-01-04
13
3
2013-12-24
16
4
2014-01-02
2
I am trying get the data between two dates and the required date values are minimum and maximum values of the column. I am able to get the desired output when I hard code the values.
SELECT *
FROM dbo.MyTable
WHERE TransDate >= '2012-12-01' AND TransDate <= '2014-01-02'
I am aware the if I remove the where clause it will solve all the issues, But my actual query is much complex and has other conditions. The only way is to get maximum date values and minimum date value from the table and pass that reference to it.
I tried the below step but that does not work and throws the below error.
SELECT *
FROM dbo.MyTable
WHERE TransDate >= '2012-12-01' AND TransDate <= MAX(TransDate)
Error
An aggregate may not appear in the WHERE clause unless it is in a subquery contained in a HAVING clause or a select list, and the column being aggregated is an outer reference.
Expected Output:
CustomeID
TransDate
WorkID
1
2012-12-01
12
1
2012-12-03
45
1
2013-01-21
3
2
2012-12-23
11
3
2013-01-04
13
3
2013-12-24
16
4
2014-01-02
2
Use a scalar subquery to find the maximum date across the whole table:
SELECT *
FROM dbo.MyTable
WHERE TransDate >= '2012-12-01' AND
TransDate < (SELECT DATEADD(DAY, 1, MAX(TransDate)) FROM dbo.MyTable);
Note that I am using a strict inequality (less than) in the WHERE clause against one day later than the max date. This will include all days which fall on or earlier than the maximum date.
You can also declare variables and then use them as given below:
DECLARE #minDate DATE = (SELECT MIN(TransDate) FROM Customer);
DECLARE #maxDate DATE = (SELECT MAX(TransDate) FROM Customer);
SELECT * FROM dbo.MyTable WHERE TransDate >= #minDate AND
TransDate <= #maxDate

Is there an alternative to using a cross join in SQL query here?

I have two tables Person_Gaps and MissingDates where I need to generate a new table Output which is all rows combined with person ID and the individual days that they are missing from the MissingDates table based on their gap ranges in Person_Gaps. Table examples as such:
Person_Gaps
PersonId StartDate EndDate
1 2011-01-01 2011-04-01
1 2014-12-31 2015-05-03
2 2011-01-01 2011-02-01
3 2015-01-01 2015-05-03
.
.
MissingDates
DateVal
2011-01-01
2011-01-02
2011-01-03
.
.
.
2016-01-03
Desired Output Table
PersonID DateVal
1 2011-01-01
1 2011-01-02
. .
. .
2 2011-01-01
2 2011-01-02
3 2015-01-01
3 2015-01-02
. .
. .
3 2015-05-03
I'm able to get the output I want currently with the following SELECT statement using CROSS JOIN but I am wondering if there's a better way to do this without the cross join in T-SQL. Perhaps using BETWEEN or the date ranges somehow.
SELECT p.PersonID, md.DateVal
FROM Person p
CROSS JOIN MissingDates md
WHERE md.DateVal >= p.StartDate AND md.DateVal <= p.EndDate
Your query is fine. It can be written using a JOIN:
SELECT p.PersonID, md.DateVal
FROM Person p JOIN
MissingDates md
ON md.DateVal >= p.StartDate AND md.DateVal <= p.EndDate;
Your results suggest that you want equality.
Note: The above is going to generate the same execution plan as your query. It gets rid of the CROSS JOIN, but that will probably not affect performance.

Fill rows for missing data by last day of month

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 )

SQL join two record into one row with multiple column

i want to join two record (from same table) into one row with multiple column.
employment history structure as follows:
StaffID StartDate EndDate DeptID
==================================================
1 2010-10-01 2011-01-19 1
1 2011-01-20 2012-12-31 2
1 2013-01-01 2013-05-29 4
how can i join the two rows into one row if same StaffID and the 2nd record startdate is 1 day after the enddate of 1st record (continuous employment)
the output should like this
StaffID EffectiveDate New_DeptID Prev_DeptID
==================================================
1 2011-01-20 2 1
1 2013-01-01 4 2
the following is my sql statement but it doesn't work
select distinct
ca1.StaffID,
ca1.ProjectDepartment as Prev_DeptID, ca1.StartDate, ca1.EndDate,
ca2.ProjectDepartment as New_DeptID, ca2.StartDate, ca2.EndDate
from
emp_hist as ca1,
emp_hist as ca2
where
(ca1.StaffID = ca2.StaffID)
and ca1.StartDate<>ca2.StartDate
and ca1.EndDate <>ca2.EndDate
and ca2.startdate= DATEADD(day, 1, ca1.enddate)
for example,
two records (true data) in the table:
StaffID StartDate EndDate DeptID
===========================================================================
1 2010-04-12 12:00:00.000 2013-02-28 00:00:00.000 1
1 2013-03-01 12:00:00.000 2013-08-29 11:02:59.877 2
i cannot retrieve this record by using my sql statement
Your problem is that the dates have a time component. You appear to be using SQL Server. You can fix your query by doing this:
select ca1.StaffID,
ca1.ProjectDepartment as Prev_DeptID, ca1.StartDate, ca1.EndDate,
ca2.ProjectDepartment as New_DeptID, ca2.StartDate, ca2.EndDate
from emp_hist as ca1 join
emp_hist as ca2
on ca1.StaffID = ca2.StaffID and
cast(ca1.StartDate as date) <> cast(ca2.StartDate as date) and
cast(ca1.EndDate as date) <> cast(ca2.EndDate as date) and
cast(ca2.startdate as date) = DATEADD(day, 1, cast(ca1.enddate as date));
I also replaced the implicit join with improved join syntax.
If you're using SQL 2012 try the lag functions.
select distinct
ca1.StaffID,
ca1.EndDate,
ca1.ProjectDepartment as New_DeptID,
LAG(ca1.ProjectDepartment) OVER (PARTITION BY ca1.StaffId ORDER BY ca1.EndDate) as Prev_DeptID
from
emp_hist as ca1
If you're not, use the RANK function and a subquery
select
eh.StaffID,
eh.EndDate,
eh.ProjectDepartment as New_DeptID,
eh1.ProjectDepartment as Prev_DeptID
from
(select *, RANK(EndDate) OVER (PARTITION BY StaffId ORDER BY EndDate) as Rank
from emp_hist) eh left join (
select distinct
StaffID,
EndDate,
ProjectDepartment,
RANK(EndDate) OVER (PARTITION BY StaffId ORDER BY EndDate) as Rank
from
emp_hist) eh1 on eh1.staffid=a.staffid and eh1.rank=eh.rank-1