Month grouping of invoices, based on conditions in SQL - sql

I have a table of invoices that looks like this:
InvoiceDate InvoiceNumber PaidDate PayStatus Amount
-----------------------------------------------------------
2012-1-23 1234 2012-02-28 Unpaid 1234
2012-2-01 2345 2012-03-12 Paid 23456
I need to GROUP these by (and take their monthly sums) certainly conditions.
I came up with a WHERE CLAUSE for only the current month. The logic is like this.
only extract invoices with invoice dates less than or equal to the last day of the previous month
the 'lateness' should not exceed 90 days (lateness = diff(Period - (InvoiceDt + Terms)))
take either unpaid PayStatus OR if it's marked as paid, ActualPaymentdt should be greater than or equal to the last day of the previous month
if the day component of invoice date equals 1 AND it belongs to acct 4300, exclude it
This is only for the current month (which reports on the last day of the prev month). I'm at a loss at how to do it for ALL MONTHS in the invoice table.
-- only extract invoices with invoice dates less than or equal to the last day of the previous month
AND b.InvoiceDt <= DATEADD(dd, -1, DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()), 0))
-- the 'lateness' should not exceed 90 days (lateness = diff(Period - (InvoiceDt + Terms)))
AND DATEDIFF(day, DATEADD(day, ISNULL(b.Term, 0), b.InvoiceDt), DATEADD(dd, -1, DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()), 0))) <= 90
-- take either unpaid PayStatus OR if it's marked as paid, ActualPaymentdt should be greater than or equal to the last day of the previous month
AND (b.PayStatus = 'Unpaid' OR b.ActualPaymentDt >= DATEADD(dd, -1, DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()), 0)))
-- if the day component of invoice date equals 1 AND it belongs to acct 4300, exclude it
AND NOT (b.AccountNumber = 4300 AND DAY(b.InvoiceDt) = 1)

Join on another derived table that contains all months in the invoices table:
CROSS JOIN (SELECT DISTINCT DATEADD(MONTH, DATEDIFF(MONTH, 0, InvoiceDt), 0) as InvoiceMonth
FROM invoices) m
Then substitute GETDATE() with m.InvoiceMonth.
And don't forget to GROUP BY m.InvoiceMonth as well.

Related

How to return values of two date ranges, from the same date column and value column, in two different columns as a result?

I have following data in my table,
Table = BillHeader
Sales column = Sales
Date column = CreateDate
Location name = Location
Result needed:
Location
Sum_of_Sale_1
Sum_of_Sale_2
Sum_of_Sale_1 = Sum of Sales up to yesterday for this month.
Sum_of_Sale_2 = Sum of Sales up to same date range as Sum_of_Sale_1 during last month.
For example, if today is 20th of June, Sum_of_Sale_1 = Sum of sales from 1st June to 19th of June
and Sum_of_Sale_2 = Sum of sales from 1st May to 19th of May.
Basically what I need is these two results of different date ranges, which should be selected form the same three columns, should appear next to each other in the result. I want to know how the sales performance was last month's same date range as to this month's date range (up to yesterday for this month).
Thanks!!
EDIT - 1
Here is the actual current working code:
SET #FDM = DATEADD(mm, DATEDIFF(mm, 0, GETDATE()), 0)
SELECT sum ([LAB_TRN_BillHeader].[AmountToBePaid]) as Total_Sale
,LAB.dbo.[LAB_TRN_BillHeader].[CollectingCenterCode]
,LAB.dbo.[LAB_Comm_MST_CollectingCenter].[Name]
,LAB.dbo.[LAB_Comm_MST_Branch].[BranchName]
FROM Lab.dbo.[LAB_TRN_BillHeader]
INNER JOIN LAB.dbo.[LAB_Comm_MST_CollectingCenter] on LAB.dbo.[LAB_TRN_BillHeader].[CollectingCenterCode] = LAB.dbo.[LAB_Comm_MST_CollectingCenter].[CollectingCenterCode]
INNER JOIN LAB.dbo.[LAB_Comm_MST_Branch] on LAB.dbo.[LAB_TRN_BillHeader].[BranchCode] = LAB.dbo.[LAB_Comm_MST_Branch].[BranchCode]
WHERE Date between #FDM and DATEADD(day,0, CAST(GETDATE() AS date)) and {{select_Laboratory}} and LAB.dbo.[LAB_TRN_BillHeader].[IsVoid] = '0' and LAB.dbo.[LAB_TRN_BillHeader].[CollectingCenterCode] in ('URCR022','MRPMC','KUCC','KOCC','EHECC')
GROUP BY LAB.dbo.[LAB_TRN_BillHeader].[CollectingCenterCode], LAB.dbo.[LAB_Comm_MST_CollectingCenter].[Name], LAB.dbo.[LAB_Comm_MST_Branch].[BranchName]
Current Result:
|Total_Sale|CollectingCenterCode|Name|BranchName|
|xxx |xxx |x |xx |
Required Result:
|Total_Sale|Total_Sale2|CollectingCenterCode|Name|BranchName|
|xxx |xxx |xx |x |xx |
Total_Sale = Sale of current month up to yesterday
Total_Sale2 = Sale of Last month up to current month's yesterday's date.
-- MSSQL Version - 2014
-- <Create_Date> is a time stamp in the table in <Create_Date> column. The date/time is obtained from that timestamp. Each transaction is saved with a respective timestamp at it's time of occurrence.
-- {{select_Laboratory}} is a field filter alias in Metabase (this code was copied from a Metabase dashboard). The actual code is LAB.dbo.[LAB_TRN_BillHeader].[BranchCode] = '001'
Considering a sales CreateDate is likely of type Datetime or Datetime2, a safe approach would be:
DECLARE #yesterday DATE = GETDATE();
DECLARE #lastMonth DATE = DATEADD(MONTH, -1, #yesterday);
DECLARE #firstDayOfThisMonth DATE = DATEADD(DAY, 1 - DAY(#yesterday), #yesterday);
DECLARE #firstDayOfLastMonth DATE = DATEADD(DAY, 1 - DAY(#lastMonth), #lastMonth);
SELECT #yesterday,
#firstDayOfThisMonth,
#lastMonth,
#firstDayOfLastMonth;
SELECT [locationId],
SUM( CASE
WHEN CreateDate >= #firstDayOfThisMonth
AND CreateDate < #yesterday THEN
AmountToBePaid
END
) AS Sum_of_Sale_1,
SUM( CASE
WHEN CreateDate >= #firstDayOfLastMonth
AND CreateDate < #lastMonth THEN
AmountToBePaid
END
) AS Sum_of_Sale_2
FROM BillHeader
GROUP BY [locationId];
EDIT: Note that in dates like March 31,30 previous month's end date could be Feb 28, 29.
You could use conditional aggregation with the following date functions:
DATEADD(Day, 1, EOMONTH(GETDATE(), -1)) gets the first date of the current month, i.e. current month is Jan-2023 it will return '2023-01-01'.
CAST(GETDATE() AS DATE) gets today's date.
DATEADD(Day, 1, EOMONTH(GETDATE(), -2)) gets the first date of the previous month, i.e. current month is Jan-2023 it will return '2022-12-01'.
DATEADD(Month, -1, CAST(GETDATE() AS DATE) gets the date of the day one-month pre today's date.
SELECT Location,
SUM(CASE
WHEN CreateDate >= DATEADD(Day, 1, EOMONTH(GETDATE(), -1)) AND
CreateDate < CAST(GETDATE() AS DATE)
THEN Sales END) Sum_of_Sale_1,
SUM(CASE
WHEN CreateDate >= DATEADD(Day, 1 ,EOMONTH(GETDATE(), -2)) AND
CreateDate < DATEADD(Month, -1, CAST(GETDATE() AS DATE))
THEN Sales END) Sum_of_Sale_2
FROM BillHeader
GROUP BY Location
See demo
There are different ways of doing the calculations and I don't know if this logic will seem more straightforward or as concise. Also you might not be able to use variables. Lastly, you didn't specify which version you're running.
Although you can incorporate this into an existing query that covers a wider range of dates, here is a stand-alone option that should restrict the execution to a narrower range of dates. The case logic, as written, does assume that rows have already been filtered so the only thing left to determine is whether the sale comes from current month or prior month.
select Location,
sum(case when month(CreateDate) <> month(getdate()) then Sales end) as Sales1,
sum(case when month(CreateDate) = month(getdate()) then Sales end) as Sales2
from BillHeader
where
-- go back enough days to guarantee covering last two months
-- this may be able to utilize an index
SalesDate between dateadd(day, -30 - day(getdate()), cast(getdate() as date))
and getdate()
-- now eliminate extra dates that are not relevant
and month(getdate()) - month(CreateDate) in (0, 1, -11) /* year might roll over */
and day(getdate()) > day(CreateDate)
group by Location;
For a YOY comparison over the same calendar month:
select Location,
sum(case when year(CreateDate) <> year(getdate()) then Sales end) as Sales1,
sum(case when year(CreateDate) = year(getdate()) then Sales end) as Sales2
from BillHeader
where
-- go back enough days to guarantee covering last 13 months
-- (or rewind as 396 days via parallel logic from earlier)
SalesDate between
dateadd(year, -1, dateadd(day, 1 - day(getdate()), cast(getdate() as date)))
and getdate()
and month(getdate()) = month(CreateDate)
and day(getdate()) > day(CreateDate)
group by Location;
This might be better done as a union with two different date ranges combined together:
-- ...
where
SalesDate between
dateadd(year, -1, dateadd(day, 1 - day(getdate()), cast(getdate() as date)))
and dateadd(year, -1, getdate())
-- ... union all ...
where
SalesDate between
dateadd(day, 1 - day(getdate()), cast(getdate() as date))
and getdate()
-- ...

Last Day of Each Month Calculation

I have a table X which holds data for each day for a brand. The data for each day is cumulative i.e. sales data for 3 will have data for 1, 2 and 3. Thus data for the last day of each month will be the sales for that month for that brand and company. I want to get the sum of all the sales for that brand for the last 3 months excluding the current month on the last day of each month.
i.e for March: I want sales from 31st Jan 2019 + 28th Feb 2019 + 31st Dec 2018 for each brand and company.
How can I achieve this?
if you are using MSSQL you can use EOMONTH function, example is as under
DECLARE #date VARCHAR(255) = '2/24/2019';
SELECT EOMONTH ( #date ) AS Result;
for MySQL you can use LAST_DAY function
SELECT LAST_DAY('2019-02-24');
Let's say name of your column representing the sales date is "sales_date", then the following predicate will give you the days you're interested in:
sales_date in (
dateadd(day, -1, dateadd(month, datediff(month, 0, getdate()) - 2, 0)),
dateadd(day, -1, dateadd(month, datediff(month, 0, getdate()) - 1, 0)),
dateadd(day, -1, dateadd(month, datediff(month, 0, getdate()), 0))
)

YTD Query comparing this year to last year

I am trying to provide a percentage of growth from Last YTD to Current YTD
I have my current YTD query
select sum(total) from invoiceinfo
WHERE InvoiceDateTime BETWEEN DATEADD(yy, DATEDIFF(yy,0,GETDATE()), 0) AND GETDATE()
How do I query for last YTD for the same day as this year. I imagine once I have this query I will then need to calculate the percentage of growth
For the Last YTD I believe you can use this query below.
You need to basically replace your GETDATE() with the same day of last year: DATEADD(yy, -1, GETDATE()).
select sum(total) from invoiceinfo
WHERE InvoiceDateTime
BETWEEN DATEADD(yy, DATEDIFF(yy,0,DATEADD(yy, -1, GETDATE())), 0)
AND DATEADD(yy, -1, GETDATE())

Find the sum of amount between dates

I have the following scenario:
There is a table that contains data loaded weekly and last day of the month for each customer and product.
I need to get the data sum for the data loaded on the last day of the month for each product (let's say) - that consolidates it for all the customers.
Input is 2 dates - start and end of the period and I need to get this done in a single SQL query.
Below is the query that I am using. Period is a datetime field.
SELECT
Period
,sum([Amount]) as 'Amount'
from tableA
where Period between convert(datetime,'201110'+'01',121) and dateadd(second,-1,dateadd(month,1,convert(datetime,'201201'+'01',121)))
and cond1 = .....
and cond2 = ....
group by
Period
The query above results in data as shown below.
I need to find out the amount on the last day of each month, i.e. one for 31-Oct-11, 30-Nov-11, 31-Dec-11 and so on.
The final result should look as below (as opposed to what I am getting currently - above):
Can you please help !!
If you just want the values for the last day of each month in the period I think adding a condition like this should work:
AND Period = DATEADD(d, -1, DATEADD(m, DATEDIFF(m, 0, period) + 1, 0))
The query would look something like this:
SELECT
Period,
SUM([Amount]) AS 'Amount'
FROM tableA
WHERE Period BETWEEN CONVERT(DATETIME,'201110'+'01',121) AND DATEADD(SECOND,-1,DATEADD(MONTH,1,CONVERT(DATETIME,'201201'+'01',121)))
AND Period = DATEADD(d, -1, DATEADD(m, DATEDIFF(m, 0, period) + 1, 0))
AND cond1 = .....
AND cond2 = ....
GROUP BY Period
If your period datetime has a time value you will have to remove that part to get the match with the last day date that the condition provides like this:
AND DATEADD(dd, DATEDIFF(dd, 0, Period), 0) =
DATEADD(d, -1, DATEADD(m, DATEDIFF(m, 0, period) + 1, 0))

Find orders placed on last day of any month using sargable query?

I wrote this query to find orders placed on last day of any month.
I know this approach is not recommended if orderdate is indexed. What approach should i use to make it sargable?
select o.orderid, o.orderdate, o.custid, o.empid
from sales.Orders o
where day(o.orderdate) in (30, 31)
or (month(o.orderdate) = 02 and day(o.orderdate)= 28)
or (month(o.orderdate) = 02 and day(o.orderdate)= 29);
You can do this with computed columns:
alter table Orders add column nextdayofmonth as day(dateadd(day, 1, orderdate));
create index orders_nextdayofmonth on orders(orders_nextdayofmonth);
The nextdayofmonth is for the next day, so leap years can easily be handled. After all, the day after the "last day" is the "first day" of the next month.
Then phrase your query as:
where next_dayofmonth = 1
This expression is sargable.
DATEADD is sargable:
WHERE DATEADD(day, DATEDIFF(day, 0, o.orderdate), 0) =
DATEADD(day, -1, DATEADD(month, DATEDIFF(month, 0, o.orderdate) + 1, 0))
The first is just the old way to truncate the time from a datetime. The second adds one month, "truncates" the month and subtracts a day.
Here's a fiddle that returns the last day of the current month with the same "trick".
This query would fail for leap years as it would give you 2 dates as the last day of the month in February. To make it SARGable, you would need to take out the functions on the date column.
This should work fine
select o.orderid, o.orderdate, o.custid, o.empid
from sales.Orders o
where
day(o.orderdate)=day(DATEADD(ms, -3, DATEADD(mm, DATEDIFF(m, 0,o.orderdate) + 1, 0)))
The below query gives the last day of current month, replace getdate() with the date variable as shown above:
SELECT day(DATEADD(ms, -3, DATEADD(mm, DATEDIFF(m, 0, GETDATE()) + 1, 0)))
You can also do is:
select TOP 1 orderid, orderdate, custid, empid
from sales.Orders
ORDER BY orderdate DESC
Get all dates that DAY part of its tomorrow is 1:
SELECT *
FROM Sales.Orders
WHERE DAY(DATEADD(dd, 1, orderdate)) = 1