I am designing a report where I need to display all days in a month(ex: 1- 30 for month of april) and display information associated to it. But my report displays only dates that have data associated to it and ignores all other days. Is there any way to display all the days in the report irrespective of the data?
As Wil hints at in his comment, it is easiest to generate these days in your dataset/query. This is a perfect place to use a CTE (Common Table Expression). These generate a table that you can use in your query, and they support recursion, so it is easy to generate a table with as many days as you want.
A Google of "cte days in a month" gave me this link as one of the top results: http://smehrozalam.wordpress.com/2009/06/09/t-sql-using-common-table-expressions-cte-to-generate-sequences/
Please leave a comment if this doesn't make sense.
OK, here's a sample using a CTE with days of the current month.
WITH DaysOfMonthCTE
AS ( SELECT DATEADD(month, DATEDIFF(MONTH, 0, GETDATE()), 0) AS StartDate ,
DATEADD(month, DATEDIFF(MONTH, 0, GETDATE()), 1) AS EndDate
UNION ALL
SELECT DATEADD(day, 1, StartDate) ,
DATEADD(DAY, 1, EndDate)
FROM DaysOfMonthCTE
WHERE EndDate < DATEADD(month,
DATEDIFF(MONTH, 0, GETDATE()) + 1, 0)
)
SELECT Student.Name ,
DaysOfMonthCTE.StartDate ,
AVG(Scores.TestScore) AS AverageScore
FROM myStudentsTable AS student
INNER JOIN myScoresTable scores
ON student.ID = scores.StudentID
RIGHT OUTER JOIN DaysOfMonthCTE
ON scores.TestDate >= DaysOfMonthCTE.StartDate
AND scores.TestDate < DaysOfMonthCTE.EndDate
GROUP BY Student.NAME ,
DaysOfMonthCTE.StartDate
Answer by #JamieF is spot-on but I was looking for a more general purpose date generator where I can get a list of dates between a start date and end date. The OP asked for all the days in a month, so this answer is not really for the OP. I added here in the event someone (like me) is looking for how to generate sequential data in a range on the fly in SSRS.
I merely added the following as the basis for my query in a dataset, and used two parameters - #StartDate and #EndDate.
;with DateGenerator
as (
select
CONVERT(datetime, #StartDate) GeneratedDate
union all select
DATEADD(day, 1, GeneratedDate)
from
DateGenerator
where GeneratedDate < #EndDate
)
select
GeneratedDate
from
DateGenerator
option (maxrecursion 32767)
Beware - limit is range of 32767 days. If the range is larger than that this solution will implode.
To use, merely join your query to the pseudotable DateGenerator (line above 'option' statement)
Related
Suppose you have a room which is 100sqft and you want to rent it from 1st Aug to 31st Aug.
Bookings Table schema
startdate|enddate|area|storageid
you have following bookings
06-Aug|25-Aug|50|'abc'
05-Aug|11-Aug|40|'xyz'
18-Aug|23-Aug|30|'pqr'
13-Aug|16-Aug|10|'qwe'
Now somebody requests for booking from 08-Aug to 20-Aug. For this date range the maximum area available is 10sqft (Since, for dates 8,9,10 and 11 Aug only 10sq ft is available.)
How would you create an efficient SQL query to get this? Right now I have very messy and inefficient query which gives wrong results for some cases. I am not posting the query because It is so messy that I can't explain it myself.
I don't necessarily want to solve it using SQL only. If there is an algorithm that can solve it efficiently I would extract all the data from database.
Someone removed SQL Server, but here is the algorithm:
DECLARE #startDate date = '2016-08-09';
DECLARE #endDate date = '2016-08-20';
DECLARE #totalArea decimal(19,2) = 100;
WITH Src AS --Your source table
(
SELECT * FROM (VALUES
('2016-08-06', '2016-08-25', 50, 'abc'),
('2016-08-05', '2016-08-11', 40, 'xyz'),
('2016-08-18','2016-08-23',30,'pqr'),
('2016-08-13','2016-08-16',10,'qwe')
)T(startdate, enddate, area, storageid)
), Nums AS --Numbers table 0..N, N must be greater than ranges calculated
(
SELECT 0 N
UNION ALL
SELECT N+1 N FROM Nums
WHERE N<DATEDIFF(DAY,#startDate,#endDate)
) --Query
--You can use total-maxUsed from range of days
SELECT #totalArea-MAX(Used) FROM
(
--Group by day, sum all used areas
SELECT MidDate, SUM(Used) Used FROM
(
--Join table with numbers, split every day, if room used, return area
SELECT DATEADD(DAY, N, #startDate) MidDate, CASE WHEN DATEADD(DAY, N, #startDate) BETWEEN startDate AND endDate THEN area END Used
FROM Src
CROSS APPLY Nums
) T
GROUP BY MidDate
) T
my sql knowledge is fairly basic and I would be grateful for some advice. I have a table with columns like:
date, time, readings, .... comments1, comments2
What I would like to do is filter the table to show the results when comments1 is equal to a string, which I can achieve. The tricky bit is I then want to find the readings when the time is between 5 and 7 hours after the times returned/identified by the initial query (comments1 = string"). Is there a way to do this with and what would be the best strategy?
Thank you.
You should really store date and time in a single column, otherwise midnight boundaries are extremely difficult to select across. My example assumes your "date" column is a datetime type that also stores the timestamp.
I believe something like this is what you're looking for:
WITH CommentTime AS (
SELECT TOP 1 date
FROM tblRecords
WHERE comments1 = 'The comment to find'
)
SELECT *
FROM tblRecords
WHERE date >= DATEADD(hour, 5, (SELECT date FROM CommentTime))
AND date < DATEADD(hour, 7, (SELECT date FROM CommentTime))
New Answer:
(Reworking Dans answer to instead use a variable)
DECLARE #CommentTime AS DateTime = (SELECT TOP 1 [date] FROM tblRecords WHERE comments1 = 'string')
SELECT * FROM tblRecords
WHERE [date] >= DATEADD(HOUR, 5, #CommentTime)
AND [date] < DATEADD(HOUR, 7, #CommentTime)
I have a neat table of holidays showing date and recurrence if such holiday is recurrent (i.e. New Year)
Now, I need to count the number of holidays between two dates. If it was a simple list of dates without info about recurrence (so i.e. it would show all New Years between 2000-01-01 and 2015-01-01) it would be quite easy, i.e. something like
declare #start_Date Date= '2013-01-02',
#end_date Date ='2014-01-02'
SELECT COUNT(CE.name) AS holidays_count
FROM dbo.argo_cal_event AS CE INNER JOIN dbo.argo_cal_event_type AS CET
ON CE.event_type_gkey = CET.gkey
WHERE (CET.name = 'EXEMPT_DAY') AND (CE.name <> 'Sundays')
AND (CE.occ_start BETWEEN #start_Date AND #end_date)
But now we have a neat recurrence, so the query above won't count all the New Years, Christmases etc that have been declared as happening "every year starting from".
I COULD create a table with such list, but I've been wondering, is there any other way?
EDIT: Let me precise what I had in mind: I'd like to count the event normally if it event occurs once (I will assume here that user will have to populate all the irregular holidays as i.e. Easter), but when the recurrence <> Once, then get the occurrence start and count the years between that date and final date.
EDIT2: I think I've got it - for the recurrent holidays I can use
SELECT sum (datediff (year, ce.occ_start, #end_date)) as recurrent_holidays
FROM dbo.argo_cal_event AS CE INNER JOIN dbo.argo_cal_event_type AS CET
ON CE.event_type_gkey = CET.gkey
WHERE (CET.name = 'EXEMPT_DAY') and (CE.repeat_interval ='ANNUALLY')
EDIT3: unfortunately this solution doesn't work (or at least getting quite complicated) if I'd like to count between TWO dates, in which one is taken from another table, i.e if I'd like to count recurring holidays between unit.time_in and getddate() :/
The most straightforward way would be to make them all one-time occurrences.
The table isn't going to be that big that you can't add in every Sunday. It's only 52 or 53 entries per year.
If you do it this way now you can do something like,
select count(*) from events where event_date between start_date and end_date
Done.
The main reason to do it this way, though, is that some of your holidays are tricky to calculate. You'll need to be calculating (or looking up) the dates for Good Friday, Easter Monday, and others. They're based on the phase of the moon.
Why not just calculate them all and make your query really easy?
In the end, it matters a lot less how "neat" your data structures are and a lot more on whether your code does what it's supposed to do, how long it takes to do it, and how much effort it takes to get it working.
You could create a function that searches your holiday table for holidays of each type (weekly, monthly, yearly, one-off, etcetera) or even "Nth/Last/Nth Last Whateverday of Whichevermonth" (which would be a variant on Yearly), and for each holiday, interval by interval, loops through and increments each holiday date and checks if it falls between the StartDate and EndDate dates that the function would need to take as parameters. You could even find an Easter Date calculation algorithm and insert that too.
However, such a function wouldn't be simple or as fast as a simple Select statement - you need to decide whether it is worth expending the time and effort to develop such a function and the performance penalty it could impose on your queries is worth the flexibility it would give you and your users.
Almost anything can be done; you should also ask if it should be done...
One terrible approach:
declare #Holidays as Table ( Name VarChar(16), OccurrenceStart Date, Recurrence VarChar(10) );
insert into #Holidays ( Name, OccurrenceStart, Recurrence ) values
( 'New Year Day', '20000101', 'Annually' ),
( 'Sundays', '20000202', 'Weekly' ),
( 'Labour Day 2014', '20141027', 'Once' );
select * from #Holidays;
declare #Start as Date = '20130102';
declare #End as Date = '20150101';
with DateRange as ( -- All dates from #Start to #End .
select #Start as ADate
union all
select DateAdd( day, 1, DR.ADate )
from DateRange as DR
where DR.ADate < #End ),
Once as ( -- Holidays that occur once within the date range.
select DR.ADate as Holiday
from DateRange as DR inner join
#Holidays as H on H.OccurrenceStart = DR.ADate and H.Recurrence = 'Once' ),
Weekly as ( -- Holidays that occur weekly within the date range, give or take.
select DateAdd( week, case when H.OccurrenceStart < #Start then DateDiff( week, H.OccurrenceStart, #Start ) else 0 end, H.OccurrenceStart ) as Holiday
from #Holidays as H
where H.OccurrenceStart <= #End and H.Recurrence = 'Weekly'
union all
select DateAdd( week, 1, W.Holiday )
from Weekly as W
where DateAdd( week, 1, W.Holiday ) <= #End ),
Annually as ( -- Holidays that occur annually within the date range, give or take.
select DateAdd( year, case when H.OccurrenceStart < #Start then DateDiff( year, H.OccurrenceStart, #Start ) else 0 end, H.OccurrenceStart ) as Holiday
from #Holidays as H
where H.OccurrenceStart <= #End and H.Recurrence = 'Annually'
union all
select DateAdd( year, 1, A.Holiday )
from Annually as A
where DateAdd( year, 1, A.Holiday ) <= #End )
select Count( 42 ) as 'Number of Holidays'
from DateRange as DR inner join
( select Holiday from Once union
select Holiday from Weekly union
select Holiday from Annually ) as H on H.Holiday = DR.ADate
option ( MaxRecursion 0 );
Note that multiple 'hits' on one day, e.g. New Years Day falling on a Sunday, are counted as a single holiday. There are more efficient ways to generate DateRange, e.g. with a numbers table. And the whole thing is hideous.
Ask and ye shall be deceived.
I am trying to nest a few queries but so far am getting back error 1242: Subquery returns more than 1 row. I want more than one row, as I am working on a number of records.
I have 2 tables. One has a commencement date stored in 3 columns; yr_comm, mth_comm, day_comm. The 2nd table has a period of service (in years) for a number of users which is expressed as an integer (2.71, 3.45, etc).
I need to take this start date (from table 1), and add on the period of service (from table 2) to obtain an end date, but I only need to display the year.
I have 2 queries which work just fine when seperate, they result in the required values, however I am having trouble combining the queries to get the desired end result.
Query 1: Concatenate the 3 commencement values into date format
SELECT concat_ws('-', yr_comm, mth_comm, day_comm) AS date_comm
FROM table 1
Query 2: Convert the integer yrs_service into days
SELECT format(yrs_served * 365, 0) AS days_served
FROM table 2
Query 3: Use date_add function to add the days service to the commencement date
SELECT date_add(date_comm, INTERVAL days_served DAY) AS date_left
Can anyone suggest how I can achieve the above? Many thanks in advance.
EDIT - Here is the full query I am working on:
SELECT prime_minister.pm_name, yr_comm, party, ADDDATE(
(SELECT CONCAT_WS('-', yr_comm, mth_comm, day_comm) FROM ministry), INTERVAL
(SELECT FORMAT(yrs_served * 365, 0) FROM prime_minister) YEAR) AS date_left
FROM ministry JOIN prime_minister USING (pm_name)
WHERE party NOT LIKE '%labor%'
AND prime_minister.pm_name = ministry.pm_name
ORDER BY pm_name;
you can use user variables
SET #date = CONCAT_WS('-', 2012,1,1); -- paste your query here
SET #toAdd = (SELECT MONTH(CURDATE())); -- paste your query here
SELECT DATE_ADD(#date, INTERVAL #toAdd DAY) AS date_left
SQLFiddle Demo
which is the same as
SET #date = CONCAT_WS('-', 2012,1,1); -- paste your query here
SET #toAdd = (SELECT MONTH(CURDATE())); -- paste your query here
SELECT #date + INTERVAL #toAdd DAY AS date_left
SQLFiddle Demo
or without using variable, which is more longer,
SELECT (CONCAT_WS('-', 2012,1,1)) + INTERVAL (SELECT MONTH(CURDATE())) DAY AS date_left
SQLFiddle Demo
Hello again SQL Server 2008 gurus.
I need to apply the following rules to the setting of a worker's start and end times for their work day (hourly employees) in a SELECT statement. I apologize in advance for my SQL ignorance.
The rule is to set their start time to a value stored in a table field for that worker, if they login on or before their start time (a time stored in the worker starttime column) and therefore get credit for starting at their start time.
If they log out within a 10 minute period before or anytime after their end time stored in a column for the worker, they get credit for their full day, another value stored in a column of the worker table, otherwise they are penalized some percentage of an hour, i.e. their log out time rounded to .25 of an hour less closest to the time they logged out. i.e. if they are set to log out at 4:30, and they log out at 4:18, their log out time is 4:15. If they log out at 4:20, and are set to log out at 4:30, their log out time is 4:30.
The first rule applies to all hourly employees where their workday hours is less than or equal to their expected workday value. The caveat is, for those where overtime is ok (a bit value set to 1). If overtime is permitted, the number of billable hours can exceed the full day value stored for them, and therefore the value of their logout - login time can exceed their fullday value.
The question is, can these rules be calculated in the SELECT statement and if so can I get some help with the code?
The columns containing the information are:
worker.startime (TIME)
worker.endtime (TIME)
worker.overtimeallowed (BIT)
worker.workdayhours (decimal (12,2))
worker.penaltyvalue (decimal (12,2))
If it requires a UDF or stored procedure (since I'm using the Telerik ReportViewer) I'm not sure it would be supported, but that's probably another question.
So far I've gotten some help with applying some CASE logic - calculating whether a worker get's credit for their 1/2 lunch. The code that was supplied works as promised. This, I believe may be an extension to that logic - so I'll provide the code I have here:
-- for testing purposes only.
DECLARE #StartDate AS DateTime
SET #StartDate = CAST('03/25/2012' AS DATE)
DECLARE #EndDate AS DateTime
SET #EndDate = CAST('04/10/2012' AS DATE)
SELECT
w.Firstname
,w.Lastname
,wf.Login
,wf.logout
,ROUND(CAST(DATEDIFF(MI, wf.Login, wf.Logout) AS DECIMAL)/60,2)
- CASE
WHEN DATEDIFF(hour, wf.Login, wf.Logout) < w.MinimumHours THEN
w.LunchDeduction
ELSE
0
END AS [Hours Credited]
FROM Workers AS w
JOIN Workflow AS wf
ON wf.LoggedInWorkerid = w.ID
JOIN Roles AS r
ON w.RoleID = r.RoleID
WHERE (r.Descript = 'Hourly')
AND wf.Login >= #StartDate AND wf.Logout <= #EndDate
ORDER BY w.Lastname, w.Firstname
Here is a sample select dealing with constraints you described. CTEs create tables for testing purposes. Main query shows the calculations. You have worked with datediffs and dateadds so there is no mistery. If you haven't use % before, it is modulo operator used to round time to 15 minutes.
;with worker (ID, overtime, startTime, endTime) as
(
select 1, 1, CAST ('08:30' as time), CAST ('16:30' as time)
union all
select 2, 0, CAST ('08:30' as time), CAST ('16:30' as time)
union all
select 3, 0, CAST ('08:30' as time), CAST ('16:30' as time)
),
-- Test table of workflows
wf (workerID, login, logout) as
(
select 1, CAST ('2012-03-11 08:20' as datetime), CAST ('2012-03-11 19:33' as datetime)
union all
select 2, CAST ('2012-03-11 08:50' as datetime), CAST ('2012-03-11 16:20' as datetime)
union all
select 3, CAST ('2012-03-11 08:22' as datetime), CAST ('2012-03-11 16:18' as datetime)
)
select wf.workerID,
wf.login,
wf.logout,
-- if starttime > login return startTime else login
case when DATEDIFF(MI, w.startTime, cast (wf.login as time)) < 0
then cast(CAST (wf.login AS date) AS datetime) + w.startTime
else wf.login
end roundedLogin,
case when w.overtime = 1 -- Round to 15 minutes whenever finished
OR
-- Round to 15 minutes if left ten or more minutes before endTime
DATEDIFF(MI, cast (wf.logout as time), dateadd (MI, -10, w.endTime)) > 0
then dateadd (MI, -(DATEPART (MI, wf.logout) % 15), wf.logout)
-- stop at endTime if overtime = 0 OR left job at apropriate time
else cast(CAST (wf.logout AS date) AS datetime) + w.endTime
end roundedLogout
from worker w
inner join wf
on w.ID = wf.workerID
There will be a problem with this approach. When you start to integrate mathematics into original query you will notice that you have to write expressions evaluating roundedLogin and roundedLogout again to calculate billable hours. You cannot reuse alias defined in the same scope, but you can create derived table or view or even calculated fields. View returning columns from workflows and all additional expressions would probably be the best.
Using this view in other queries would simplify things by encapsulating logic at one place.