Get the previous month number including year - sql

I have a table in which I get the coupons of a specific company.
My coupon table is like:
Company_Coupon
ID
CompanyID
Month
Total_Coupons
Year
I keep my months in the format of their number (1-12).
When I update the total coupons for the invoicing, I want to update the ones from the previous month.
I do this by the following query:
UPDATE Company_Coupon
SET Total_Coupons = #count
WHERE CompanyID = 1205
AND Month = MONTH(GETDATE())-1 AND Year = YEAR (GETDATE())
My query works but I noticed that this won't work in January 2019.
How can I update this query so that it will work in January 2019?

Try with case when like below:
UPDATE Company_Coupon
SET Total_Coupons = #count
WHERE CompanyID = 1205
AND Month = (case when MONTH(GETDATE())-1=0 then 12 else MONTH(GETDATE())-1 end) AND Year = (case when MONTH(GETDATE())-1=0 then YEAR (GETDATE())-1 else YEAR (GETDATE()) end)

You can try to get different number between 1900-01-01 and your data, then do some calculation to get last month.
Query 1:
SELECT DATEADD(month, DATEDIFF(month,0,'2019-01-01') - 1, 0)
UNION ALL
SELECT DATEADD(month, DATEDIFF(month,0,'2018-08-01') - 1, 0)
Results:
| |
|----------------------|
| 2018-12-01T00:00:00Z |
| 2018-07-01T00:00:00Z |
so you query can be
UPDATE Company_Coupon
SET Total_Coupons = #count
WHERE
CompanyID = 1205
AND
Month = MONTH(DATEADD(month, DATEDIFF(month,0,GETDATE()) - 1, 0))
AND
Year = YEAR (DATEADD(month, DATEDIFF(month,0,GETDATE()) - 1, 0))

As I said in a comment, I'd prefer to have a single YearMonth column with the correct data type for datetime work, but we can do something very similar here:
UPDATE Company_Coupon
SET Total_Coupons = #count
FROM Company_Coupon
CROSS APPLY (SELECT
DATEADD(month,DATEDIFF(month,'20010201',GETDATE()),'20010101')) t(LastMonth)
WHERE CompanyID = 1205
AND Month = MONTH(LastMonth) AND Year = YEAR(LastMonth)
The two dates used in the above expression do not matter much. All that matters really is the relationship between them. Here, the second date falls a month before the first and it's the relationship that effectively gets applied to GETDATE() by the DATEADD/DATEDIFF expression. It's a pattern that can be used in lots of different ways - e.g. a variant of this pattern can be used to find the last day of 3 months ago if you're not on a SQL version that supports EOMONTH.

If you use SQL Server 2012 or later version, you can employ the eomonth() function that returns last day of a previous month for a given date. From it, you can extract both month() and year() parts and use them in your query:
UPDATE c SET Total_Coupons = #count
from dbo.Company_Coupon c
WHERE c.CompanyID = 1205
AND c.Month = MONTH(eomonth(GETDATE()))
AND c.Year = YEAR(eomonth(GETDATE()));

Related

How do I include months that have no data?

I am trying to create a report that shows how many training records will expire within a chosen date range however when I run the report it excludes months that have no training records going out of date. I have tried various solutions I've seen posted but I haven't been able to get any of them to work in my case.
This is my query:
SELECT COUNT(ISNULL(TRAININGRECORDID, 0)) AS NUMBEROFRECORDS
,DEPARTMENTNUMBER
,DATENAME( Month, EXPIRY ) + '-' + DATENAME( Year, EXPIRY ) AS [MONTHYEAR]
FROM Training_Records TR
JOIN Departments TD ON TR.DEPARTMENTID = TD.DEPARTMENTID
WHERE TR.EXPIRY IS NOT NULL
AND TD.DEPARTMENTNUMBER IN (#DEPTNO)
AND TR.EXPIRY BETWEEN #StartDate AND #EndDate
GROUP BY TD.DEPARTMENTNUMBER, DATENAME(Year, TR.EXPIRY), DATENAME(Month, TR.EXPIRY)
ORDER BY TD.DEPARTMENTNUMBER, [MONTHYEAR]
An example of results from this query looks like this:
NUMBEROFRECORDS DEPARTMENTNUMBER MONTHYEAR
1 21 April-2023
4 23 June-2023
1 83 August-2023
I am displaying the results of this query in a matrix with MONTHYEAR as the columns. In the example above the report will display April, June and August 2023 but will skip over the months May, July 2023 because there are no records going out of date in those months but despite that I still want them displayed in my report/returned in my query.
I've tried various solutions I've found on here but none of them have worked for me. How would I go about including these months with no records going out of date?
You need to first get all of the months, and then outer join to them (not using BETWEEN). Here is an example that gets April, May, June, and July, and then shows how you would outer join that against your table.
DECLARE #StartDate date = '20220405',
#EndDate date = '20220708';
;WITH Months(TheMonth) AS
(
SELECT DATEFROMPARTS(YEAR(#StartDate), MONTH(#StartDate), 1)
UNION ALL
SELECT DATEADD(MONTH, 1, TheMonth)
FROM Months
WHERE TheMonth < DATEFROMPARTS(YEAR(#EndDate), MONTH(#EndDate), 1)
)
SELECT TheMonth -- , COALESCE(SUM({your table}.{column}),0)
FROM Months AS m
-- LEFT OUTER JOIN {your table}
-- ON {your table}.{date column} >= m.TheMonth
-- AND {your table}.{date column} < DATEADD(MONTH, 1, m.TheMonth);
Output:
TheMonth
2022-04-01
2022-05-01
2022-06-01
2022-07-01
Example db<>fiddle
If your range could last more than 100 months, you'll need to add:
OPTION (MAXRECURSION 0);

Set week number based on first Monday of Year

I have a requirement to set the week number of a table from the first day to the first Monday to the next Monday and so on. I can easily get the first Day and first Monday of year but I do not know how to increment trough the table in 7 days intervals from the first Monday so that I can set the week number.
I have something like this:
UPDATE table
SET weeknumberofyear = #WeekNumber + 1
WHERE datefield = DATEADD(Day,7,(SELECT DATEADD(DAY, (##DATEFIRST - DATEPART(WEEKDAY, #Date) + (8 - ##DATEFIRST) * 2) % 7, #Date)))
Since the datepart(week,datefield) function gets week number based on Sunday as the first day of the week, all you have to do is check datepart(weekday,datefield) and if it is 1 (Sunday) or 2 (Monday), subtract 1 from the datepart(week,datefield) function:
update table
set weeknumberofyear = datepart(week,datefield) -
case when datepart(weekday,datefield) in(1,2) then 1 else 0 end
EDIT This doesn't account for Years when Sunday or Monday are the first day of the year. In those cases, you would get 0 for weeknumberofyear. To fix this, perform a second update to your table. Even though this takes two updates, I still think it is more efficient than cycling through all the records.
update table
set weeknumberoftheyear = weeknumberoftheyear + 1
where year(datefield) in(
select distinct year(datefield)
from table
where weeknumberoftheyear = 0
)
EDIT WeekNumberOfTheMonth Update - Now that we have the WeekNumberOfTheYear value, we can use a ranking function on that field to update the WeekNumberOfTheMonth column without any recursion.
update t
set t.weeknumberofthemonth = u.weeknumberofthemonth
from table t
inner join (
select distinct weeknumberoftheyear,
dense_rank() over(partition by month(datefield)
order by weeknumberoftheyear) weeknumberofthemonth
from table ) u
on u.weeknumberofyear = t.weeknumberofyear
I'm not sure what you mean by all the talk about "Monday", but if you're looking to get the week number from a date, you can do something like this:
UPDATE table
SET weeknumberofyear = DATEPART(wk, datefield)

Calculate items, loop by month, adding month each time through

I have a table of tickets. I am trying to calculate how many tickets were "open" at each month end over the course of the current year. As well, I am pushing this to a bar chart and I am needing out put this into an array through LINQ.
My SQL query to get my calculation is:
SELECT
(SELECT COUNT(*) FROM tblMaintenanceTicket t WHERE (CreateDate < DATEADD(MM, 1, '01/01/2012')))
-
(SELECT COUNT(*) FROM tblMaintenanceTicket t WHERE (CloseDate < DATEADD(MM, 1, '01/01/2012'))) AS 'Open #Month End'
My logic is the following: Count all tickets open between first and end of the month. Subtract that count from the tickets closed before the end of the month.
UPDATED:
I have updated my query with the comments below and it is not working with errors in the GROUP, but I am not truly understanding the logic I guess, my lack of skill in SQL is to blame.
I have added a SQL Fiddle example to show you my query: http://sqlfiddle.com/#!3/c9b638/1
Desired output:
-----------
| Jan | 3 |
-----------
| Feb | 4 |
-----------
| Mar | 0 |
-----------
Your SQL has several erros . . . are grouping by CreateDate but you don't have it as a column from the subqueries. And, you don't have a column alias on the count(*).
I think this is what you are trying to do:
select DATENAME(MONTH,CreateDate), DATEPART(YEAR,CreateDate),
(sum(case when CreateDate < DATEADD(MM, 1, '01/01/2012') then 1 else 0 end) -
sum(case when CloseDate < DATEADD(MM, 1, '01/01/2012') then 1 else 0 end)
)
from tblMaintenanceTicket
group by DATENAME(MONTH,CreateDate), DATEPART(YEAR,CreateDate)
Your comment seems to elucidate what you want clearer than your question (the explanation in the question is a bit buried). What you need is a driver table of months and then join this to your table. Something like:
select mons.yr, mons.mon, count(*) as OpenTickets
from (select month(CreateDate) as mon, year(CreateDate) as yr,
cast(min(CreateDate) as date) as MonthStart,
cast(max(CreateDate) as date) as monthEnd
from tblMaintenanceTicket
group by month(CreateDate), year(CreateDate)
) mons left outer join
tblMaintenanceTicket mt
on mt.CreateDate <= mons.MonthEnd and
(mt.CloseDate > mons.MonthEnd or mt.CloseDate is null)
group by mons.yr, mons.mon
I am assuming records are created on every day. This is a convenience so I don't have to think about getting the first and last day of each month using other SQL functions.
If your query is returning what you need, then simply use DATENAME(MONTH, yourDate) to retrieve the month and group by Month,Year:
SELECT SUM(*), DATENAME(MONTH,yourDate), DATEPART(YEAR,yourDate)
FROM
(
your actual query here
)
GROUP BY DATENAME(MONTH,yourDate), DATEPART(YEAR,yourDate)

Fiscal Year To-Date in Where Clause (T-SQL)

Company's Fiscal Year: July 1 - June 30
I have a query where I am trying to capture aggregate # of units and $ revenue by product and cost center for the fiscal year-to-date. It will run on the 1st of the month and look through the last day of the previous month. Fiscal year does not appear in the report - it is criteria.
Mix of pseudocode and SQL:
Where
If datepart(mm,getdate()) - 1 < 7
THEN
transaction_post_date BETWEEN 7/1/ previous year AND dateadd(day,-(day(getdate()),getdate())
Else
transaction_post_date BETWEEN 7/1/current year AND dateadd(day,-(day(getdate()),getdate())
Am I on the write track? How do I write the SQL for a specific date on a year that depends on SQL - 7/1/current year?
I am weak using variables and do not even know if I have access to create them on the SQL Server DB, which is rather locked down. Definitely can't create a function. (I'm a business analyst.)
UPDATE, Fiscal year goes forward, so July 1, 2010, is Fiscal Year 2011.
I think this works:
Year(dateadd(month,6,htx.tx_post_date)) = Year(DateAdd(Month, 5, GetDate()))
Feeback?
And now I've been asked to add Fiscal Year-To-Date fields for quantity and revenue to the following query which gave me totals for
Select
inv.ITEM_CODE
, inventory.ITEM_NAME
, cc.COST_CENTER_CODE
, tx.REV_CODE_ID
, tx.PRICE
, tx.ITEM_SALE_ID
, sum(tx.quantity)
, sum(tx.amount)
from
transactions tx
inner join inventory inv on inv.item_id = tx.item_id
left outer join cost_center cc on cc.cost_center_id = tx.cost_center_id
where
DATEPART(mm, tx.tx_date) = DATEPART(mm,dateadd(m,-1,getdate()))
and DATEPART(yyyy, tx.tx_date) = DATEPART(yyyy,dateadd(m,-1,getdate()))
group by
inv.ITEM_CODE
, inventory.ITEM_NAME
, cc.COST_CENTER_CODE
, tx.REV_CODE_ID
, tx.PRICE
, tx.ITEM_SALE_ID
I need to add the fiscal year-to-date quantity and and amount columns to this report. Would a correlated subquery by the way to go? Would the joins be tricky? I've never used a subquery with an aggregation/grouping query.
Thanks for all the previous help.
Here is how I would do it if I needed to group by Fiscal Year:
Group by Year(DateAdd(Month, -6, TransactionDate))
May be not exactly it, but you get the idea.
I would add a calculated column to your table called FiscalYear (with the proper calculation) and select based on that column
I believe the easiest way is to do this in two steps. Use the WHERE Clause to filter your YTD and then a GROUP BY to group by FY. Since your FY begins in July(7) then increment the FY if the month is greater than June(6).
WHERE CLAUSE:
WHERE
DATEDIFF(DAY, transaction_post_date, Cast(Month(GetDate()) as varchar) +
'/' + Cast(Day(GetDate()) as varchar) + '/' + CAST(Case WHEN
MONTH(transaction_post_date) > 6 then YEAR(transaction_post_date) + 1 else
Year(transaction_post_date) end as varchar)) >=0
GROUP BY CLAUSE:
GROUP BY CASE WHEN MONTH(transaction_post_date) > 6 then
Year(transaction_post_date) + 1 else YEAR(transaction_post_date) end

Finding orders made within any ISO week of a specific year using T-SQL

As part of a reporting setup, I have a SQL query fetching the number of orders placed each week:
select datepart(isowk, order_date), count(*)
from orders where year(order_date) = #Year
group by datepart(isowk, order_date), year(order_date)
order by 1
Note that I am using the new isowk format in the datepart function call, as business in Denmark typically use ISO week numbers.
Running this query with #Year = 2010 yields a result set simialar to this:
1 5
2 7
3 10
...
53 3
You and I both know that 2010 isn't over yet, and certainly there has been no week 53 yet. Actually, there has - the first three days of the year belonged to ISO week 53 of 2009. While it might be possible to explain this to other programmers, the people who are going to read my reports are never going to understand this. Hence, I want to get rid of this week 53, by "moving" the data over to 2009.
How can I rewrite my WHERE clause to filter the data set to orders made between monday of week 1 and friday of the last week (52 or 53) in the year defined as #Year?
In fact I do not know any programming language which delivers a isoyear ...
create function isoyear(#date datetime) returns smallint as begin
declare #isoyear smallint =
case
when datepart(isowk, #date) = 1 and month(#date) = 12
then year(#date)+1
when datepart(isowk, #date) = 53 and month(#date) = 1
then year(#date)-1
else year(#date)
end;
return #isoyear;
end;
Hope we'll get a datepart(isoyear, ...) next time ...
You could try this:
set datefirst 1;
declare #Year smallint = 2010;
declare #DayInFirstWeek datetime = cast(#Year as varchar)+'0104';
declare #FirstDayInFirstWeek datetime =
#DayInFirstWeek - datepart(dw,#DayInFirstWeek)+1
declare #DayInFirstWeekNextYear datetime = cast(#Year+1 as varchar)+'0104';
declare #LastDayInLastWeek datetime =
#DayInFirstWeekNextYear - datepart(dw,#DayInFirstWeekNextYear);
select datepart(isowk, order_date), count(*)
from orders
where order_date between #FirstDayInFirstWeek and #LastDayInLastWeek
group by datepart(isowk, order_date)
order by 1;
If your first day of week isn't Monday it perhaps won't work!
Do an IF statement - I don't guarantee the SQL server syntax, but:
// IF ISO week is 53, return week 1 of next year.
GROUP BY IF(datepart(isowk, order_date)) = 53,
CONCAT("1/", year(order_date) + 1),
CONCAT(datepart(isowk, order_date), "/", year(order_date))
I am going to take a stab at this myself, using the approach described in my comments on Manfred's answer. The idea is to find the first day of the first ISO week of the specified year and the year after, allowing for index-enabled between filtering of the input records.
First, I create a function which will find the first day of the first ISO week in a specific year:
create function firstIsoDay(#year int) returns datetime as begin
declare #first datetime
select #first = cast(cast(#year as char(4)) as datetime)
select #first = dateadd(ww, datediff(ww, 0, #first), 0)
if datepart(isowk, #first) > 1 set #first = #first + 7
return #first
end
Disclaimer: This function is untested (having no Sql Server 2008 available at the moment).
The actual data query can then be made using two invocations of this function in a between filter:
select datepart(isowk, order_date), count(*) from orders
where order_date between firstIsoDay(#Year) and firstIsoDay(#Year + 1)
group by datepart(isowk, order_date), year(order_date)
order by 1
Remember that between is two-way inclusive. If the values in order_date contain truncated dates (no time), -1 should be added to the end of the where clause. In my use case, order_date has both date and time, so between two dates at midnight should do just fine.
Just to spell out the fundamental answer here:
If you are querying based on ISO weeks, you must filter based on ISO Year (and not calendar year)
How you add an ISO year column to your data (or where clause) is as others have described.