I have created a report for management that will total everything up by month with in a date range. Management has now decided that rather than by month they would like to go by period. We have 13 periods in a year each is 28 days except the last one is 29 or 30 depending on if its a leap year. The beginning of the first period is always 1-1-YYYY. So now I will need to figure out what the beginning and end of each period is and total up each period. I am not really sure how to do this since every year the dates will change and they may want to look at periods from the previous year through the current period. The code and results I am currently using are enclosed
SELECT
DATEADD(MONTH, DATEDIFF(MONTH, 0, finspecteddate), 0) AS 'Date'
,COUNT(*) AS Lots
,sum(flotSize) as 'Lot Size'
,sum(LReject) 'Lots Rejected'
,sum(fnumreject) as Rejected
,sum(fsampleSize) as 'Sample Size'
,sum(BDueDate) as 'Before Due Date'
FROM
ReportData
WHERE
finspecteddate >= '01-01-2014'
AND finspecteddate <= '10-15-2014'
GROUP BY
DATEADD(MONTH, DATEDIFF(MONTH, 0, finspecteddate), 0)
ORDER BY
date
Modify the following queries to suit your needs:
;WITH Period AS (
SELECT 1 AS ReportingPeriod,
CAST('2013-01-01' AS datetime) AS PeriodStartDate,
CAST('2013-01-28' AS datetime) AS PeriodEndDate
UNION ALL
SELECT CASE
WHEN p.ReportingPeriod = 13 THEN 1
ELSE p.ReportingPeriod + 1
END,
CASE
WHEN p.ReportingPeriod = 13 THEN DATEADD(YEAR,YEAR(p.PeriodStartDate)-1899,'1900-01-01')
ELSE DATEADD(DAY,28,p.PeriodStartDate)
END,
CASE
WHEN p.ReportingPeriod = 12 THEN DATEADD(YEAR,YEAR(p.PeriodStartDate)-1900,'1900-12-31')
ELSE DATEADD(DAY,28,p.PeriodEndDate)
END
FROM Period p
WHERE p.PeriodStartDate < '2017-12-03'
)
SELECT
P.PeriodStartDate
,P.PeriodEndDate
,COUNT(*) AS Lots
,sum(flotSize) as 'Lot Size'
,sum(LReject) 'Lots Rejected'
,sum(fnumreject) as Rejected
,sum(fsampleSize) as 'Sample Size'
,sum(BDueDate) as 'Before Due Date'
FROM
ReportData R
INNER JOIN Period P ON R.finspecteddate >= P.PeriodStartDate AND R.finspecteddate <= P.PeriodEndDate
WHERE
finspecteddate >= '01-01-2014'
AND finspecteddate <= '10-15-2014'
GROUP BY
P.PeriodStartDate
,P.PeriodEndDate
ORDER BY
P.PeriodStartDate
It uses a recursive CTE to build a period table, which is then joined to ReportData to aggregate asccording to your requirements. I don't have SQL Server 2005 to test it on. It works with 2008. Post a SQL Fiddle if you need help in 2005.
If you haven't got one, create a period calendar table with a year, period number, start date and end date columns. Then when you need to refer to periods, you can refer to the table. When they change the definition of what a period is, you can change the table. When they decide that February 29 doesn't count as one of the 28 days, you can change the table. When they decide to use the first Monday instead of the first Thursday as the start of the year, you just change the table. And best of all, changing how next year works won't change how last year works.
Then you just join to the table to determine which period you're in.
Related
I'm facing an issue while working with custom dates in T-SQL, we have a client that works with a different methodology of start and end of his month, instead of the default day 01 to start the month and ending in 31, 30 or 29, it's month start at day 26 and ends at 25 of the next month.
E.g., how usually is:
select sum(sales) as sales
from sales
where salesDate between '2020-09-01' and '2020-09-30'
-- or left(salesDate,7) = '2020-09'
Client custom month:
select sum(sales) as sales
from sales
where salesDate between '2020-08-26' and '2020-09-25' -- for setember
So, for this need, I have to calculate how many sales this client did from january until now, month per month, with this custom way... how can I do that?
Example of the query result I want to perform with this month determination:
This is a pretty awful situation. One method is to construct the first date of the month based on the rules:
select mon, sum(sales) as sales
from sales s cross apply
(values (case when day(salesdate) >= 26
then dateadd(month, 1, datefromparts(year(salesdate), month(salesdate), 1))
else datefromparts(year(salesdate), month(salesdate), 1)
) v(mon)
where v.mon >= '2020-01-01'
group by v.mon;
I would recommend adding the fiscal month column as a persisted computed column in the salesDate table so you can add an index and not have to worry about the computation.
Or, better yet, add a calendar table where you can look up the fiscal month for any (reasonable) date.
I have a column with values like '3rd-Wednesday', '2nd-Tuesday', 'Every-Thursday'.
I'd like to create a column that reads those strings, and determines if that date has already come this month, and if it has, then return that date of next month. If it has not passed yet for this month, then it would return the date for this month.
Expected results (on 4/22/16) from the above would be: '05-18-2016', '05-10-2016', '04-28-2016'.
I'd prefer to do it mathematically and avoid creating a calendar table if possible.
Thanks.
Partial answer, which is by no means bug free.
This doesn't cater for 'Every-' entries, but hopefully will give you some inspiration. I'm sure there are plenty of test cases this will fail on, and you might be better off writing a stored proc.
I did try to do this by calculating the day name and day number of the first day of the month, then calculating the next wanted day and applying an offset, but it got messy. I know you said no date table but the CTE simplifies things.
How it works
A CTE creates a calendar for the current month of date and dayname. Some rather suspect parsing code pulls the day name from the test data and joins to the CTE. The where clause filters to dates greater than the Nth occurrence, and the select adds 4 weeks if the date has passed. Or at least that's the theory :)
I'm using DATEFROMPARTS to simplify the code, which is a SQL 2012 function - there are alternatives on SO for 2008.
SELECT * INTO #TEST FROM (VALUES ('3rd-Wednesday'), ('2nd-Tuesday'), ('4th-Monday')) A(Value)
SET DATEFIRST 1
;WITH DAYS AS (
SELECT
CAST(DATEADD(MONTH,DATEDIFF(MONTH,0,GETDATE()),N.Number) AS DATE) Date,
DATENAME(WEEKDAY, DATEADD(MONTH,DATEDIFF(MONTH,0,GETDATE()),N.Number)) DayName
FROM master..spt_values N WHERE N.type = 'P' AND N.number BETWEEN 0 AND 31
)
SELECT
T.Value,
CASE WHEN MIN(D.Date) < GETDATE() THEN DATEADD(WEEK, 4, MIN(D.DATE)) ELSE MIN(D.DATE) END Date
FROM #TEST T
JOIN DAYS D ON REVERSE(SUBSTRING(REVERSE(T.VALUE), 1, CHARINDEX('-', REVERSE(T.VALUE)) -1)) = D.DayName
WHERE D.Date >=
DATEFROMPARTS(
YEAR(GETDATE()),
MONTH(GETDATE()),
1+ 7*(CAST(SUBSTRING(T.Value, 1,1) AS INT) -1)
)
GROUP BY T.Value
Value Date
------------- ----------
2nd-Tuesday 2016-05-10
3rd-Wednesday 2016-05-18
4th-Monday 2016-04-25
I'm trying to run a simple query to calculate what will be a reminder based upon one date (enrollment_date.) It's a recurring event every 90 days starting with the enrollment date but our staff needs only to see the next possible date. So if one date is in December they will only see that date until it passes and then only see the next after that date has passed. Is that possible? I'm using oracle sql
All that will be seen is:
Until December 12 2015
Client Next Inquiry Date
Client A December 12, 2015
After December 12 2015
Client Next Inquiry Date
Client A March 11 2016
It's not much but this is what I have so far.
SELECT
client_name, enrollment_date,
CASE
WHEN CURRENT_DATE <= DATE(day, 90, nextdue_date)
I think there are still quite a few unknowns with your scenario, but here is an example (for SQL Server) that will show you the next 100 90-day reminders. You can use a CTE to generate a list of reminder dates for each client then use the MIN from that list for each client that is still greater than the current date.
DECLARE #enroll_date DATE = '2015-12-12' --For testing
--DECLARE #enroll_reminder DATE = DATEADD(DAY, 90, #enroll_date)
;WITH cte AS (
SELECT 1 AS count_
UNION ALL
SELECT count_+1
FROM cte
WHERE count_<100
)
SELECT
'Client A'--replace with your column name
,MIN(DATEADD(DAY, 90*count_, #enroll_date)) AS test
FROM cte
--GROUP BY your_column_name
HAVING MIN(DATEADD(DAY, 90*count_, #enroll_date)) > GETDATE()
In this scenario, the next reminder is 2016-03-11, then when that day passes it will show 2016-06-09, then 2016-09-07, etc.
Check if this works for you...
Since this is Oracle (PL/SQL) we cannot use TOP 1 and hence using ROWNUM property.
SELECT client_name as "Client"
, enrollment_date as "Next Inquiry Date"
FROM <table_name>
WHERE enrollment_date < (SYSDATE + 90)
AND ROWNUM = 1
ORDER BY enrollment_date
I am working with an excel file that contains data and a vba to filter the data via ADO using an SQL string.
My database contains:
Surname; Start_date (of the contract), Dur_mths (contract duration in months), Value (Total value of the contract).
A contract may have the duration of 2-3 years. Therefore I would like to divide the total value of each contract by duration(Value/m), check if the contract is active in the specific period and then sum the results for each Surname. Each Surname may have several contracts active in a given period. I would like to see the sum of all contract values divided by their durations for each Surname for a given period.
My current working code for one month (January 2012) looks like this:
SELECT [Surname],
Sum (iif([Start_date] <= '2012-1-1'
AND DateAdd ("m",[Dur_mths], [Start_date]) >= DateSerial(2012,1+1, 0),[Value]/[Dur_mths],0))
AS [SumValue] FROM [Data$]
GROUP BY [Surname]
Now, I would like to make it work for a quarter (or a year).
I understand this still requires checking for each month in a quarter if the contract is active betw. start of contract and end of contract (start plus duration in months).
Here comes my question:
Is there an easy way to develop the code to do the same for a quarter (a year, any period)? Ie. to check if the contract is active for several months in a given period (quarter, year)? And then sum the values of active contracts(/duration in months) for each customer?
Thank you very much for all the help.
Jacek
EDIT! : In the case of one quarter, for one customer or surname, the calculation should test: in how many months in the quarter (or in the year, or in the specified period) the contract is active.
m1 m2 m3
contr1 50 50 0 (contract ended in m2)
contr2 0 0 20 (contract starts in m3)
contr3 10 10 10 (contract started 1y ego and will continue well into 2015) etc.
For this single surname, the sum for the quarter should give 150.
EDIT2:
I am now thinking how to perhaps use a loop to calculate how many months in a quarter (or a year, or a given period) the contract was active. Eg. I am trying to develop a loop, sopmething like (It is not actually any working code but an idea what I think needs to be done, I am not a programmer unfortunately):
(??? i do not know what to do with it yet...)
DECLARE #I INT, #N INT;
DECLARE #StartDate DATETIME #EndDate DATETIME
SET #I = 1
SET #N = 0
SET #QueryStartDate = 2012-01-01
SET #EndDate = DateAdd ("m",[Dur_mths], [Start_date])
WHILE #I <= 3
BEGIN
SET #N = #N + iif(DateSerial(year[Start_date];month[Start_date]+#I-1;1); <= #QueryStartDate AND #EndDate >= DateSerial(year(#StartDate),month(#StartDate+#I, 0),1,0))
SET #I = #I + 1
END
(??? i do not know what to do with it yet...)
It would return the number of months the contract was active - #N. I would use it to multiply my contract/(duration in months) in the initial code. I am trying now to read how to incorporate it in my initial code.
You can, perhaps, use the same DateAdd function with various parameters to get the data for a quarter or a year, as below:
AND DateAdd ("q",[Dur_quarter], [Start_date]) >= DateSerial(2012,1+1, 0),[Value]/[Dur_quarter],0))
and
AND DateAdd ("yyyy",[Dur_year], [Start_date]) >= DateSerial(2012,1+1, 0),[Value]/[Dur_year],0))
The complete details of the DATEADD function are provided on MSDN here.
For the time being I arrived at a following solution for a single quarter -by simply repeating the code for the month. The date range and region is entered to sql string from excel cells...I am still wondering if it can be made somehow universal for any period (with a loop?) In addition I added a left join
SELECT [Data$].[Surname],[DataInternal$].[Code], [reg],
Sum (iif([Start_date]
<= '2012-1-1'
AND DateAdd ("m",[Dur_mths], [Start_date])
>= DateSerial(2012,1+1, 0)
,[Value]/[Dur_mths],0)) AS [M1],
Sum (iif([Start_date]
<= '2012-2-1'
AND DateAdd ("m",[Dur_mths], [Start_date])
>= DateSerial(2012,1+2, 0)
,[Value]/[Dur_mths],0)) AS [M2],
Sum (iif([Start_date]
<= '2012-3-1'
AND DateAdd ("m",[Dur_mths], [Start_date])
>= DateSerial(2012,1+3, 0)
,[Value]/[Dur_mths],0)) AS [M3],
([M1]+[M2]+[M3]) AS [Q]
FROM [Data$]
LEFT JOIN [DataInternal$] ON [Data$].[Surname] = [DataInternal$].[Surname]
WHERE 1 = 1 AND [reg] = 'N'
GROUP BY [Data$].[Surname],[Code],[reg] ORDER BY [Data$].[Surname]
Empty lines added only for readability. The strings are first concatenated in excel to form a one SQL query string executed in vba macro. I will be grateful for all the comments and corrections. My question remains unadressed yet, if it is perhaps possible to loop such a solution in EXCEL ADO VBA.
I have a Sales table:
id_item, quantity, date_time
What I need is to sum the items sold in a month and divide them by the days of the month from a selected period of months.
Example - The user selects the dates of Oct 1 to Dec 31. I need to show the items_sold/days_of_month:
Month Items sold Days of month Items/Day
Sep 25 30 0.83333
Oct 36 31 1.16
Dec 15 31 0.4838
I have to specify by Kind of item. the kind is obtained from another table called Items. I use dateformat dd/mm/yy.
select
month(date_time),
sum(quantity) / (select(datepart(dd,getdate())))
from
sales v
join items a on v.id_item=a.id_item
where
a.kind='Kind of Item'
and cast(Convert(varchar(10), date_time, 112) as datetime)
between '01/10/2012' and '31/12/2012'
group by
month(date_time)
My problem is selecting the days of the months, how can I select x number of months and divide the sum(quantity) of each month by the days of each?
I know this part of the code only selects the days of the current month:
(select(datepart(dd,getdate())))
Try this on for size:
DECLARE
#FromDate datetime,
#ToDate date; -- inclusive
SET #FromDate = DateAdd(month, DateDiff(month, 0, '20121118'), 0);
SET #ToDate = DateAdd(month, DateDiff(month, 0, '20121220') + 1, 0);
SELECT
Year = Year(S.date_time),
Month = Month(S.date_time),
QtyPerDay =
Sum(s.quantity) * 1.0
/ DateDiff(day, M.MonthStart, DateAdd(month, 1, M.MonthStart))
FROM
dbo.Sales S
INNER JOIN dbo.Items I
ON S.id_item = I.id_item
CROSS APPLY (
SELECT MonthStart = DateAdd(month, DateDiff(month, 0, S.date_time), 0)
) M
WHERE
I.kind = 'Kind of Item'
AND S.date_time >= #FromDate
AND S.date_time < #ToDate
GROUP BY
Year(S.date_time),
Month(S.date_time),
M.MonthStart
It will select any full month that is partially enclosed by the FromDate and ToDate. The * 1.0 part is required if the quantity column is an integer, otherwise you will get an integer result instead of a decimal one.
Some stylistic notes:
Do NOT use string date conversion on a column to ensure you get whole days. This will completely prevent any index from being used, require more CPU, and furthermore is unclear (what does style 112 do again!?!?). To enclose full date periods, use what I showed in my query of DateCol >= StartDate and DateCol < OneMoreThanEndDate. Do a search for "sargable" to understand a very key concept here. A very safe and valuable general rule is to never put a column inside an expression if the condition can be rewritten to avoid it.
It is good that you're aliasing your tables, but you should use those aliases throughout the query for each column, as I did in my query. I recognize that the aliases V and A came from another language so they make sense there--just in general try to use aliases that match the table names.
Do include the schema name on your objects. Not doing so is not a huge no-no, but there are definite benefits and it is best practice.
When you ask a question it is helpful to explain all the logic so people don't have to guess or ask you--if you know (for example) that users can input mid-month dates but you need whole months then please indicate that in your question and state what needs to be done.
Giving the version of SQL server helps us zero in on the syntax required, as prior versions are less expressive. By telling us the version we can give you the best query possible.
Note: there is nothing wrong with putting the date calculation math in the query itself (instead of using SET to do it). But I figured you would be encoding this in a stored procedure and if so, using SET is just fine.