Showing Purchase Counts for ALL months - Including 0 purchase months - sql

I want to join a complete calendar table, with per user purchasing data to show, for each user, any purchase counts for every month from 2014 to 2017. Some users may not have a purchase until 2016, but I would still want to have the results show 0's for each month up to the first purchase date, as well as any 0's in between months as well.
I can't get the 0 months to be included! I think it's because I'm doing this across many unique users, but it felt like the below code should work.
select
c.fscl_yr_num
,c.fscl_month_num
,t.user_id
,sum(nvl(t.trans_counter, 0))
from
appca.d_cal c
left join
transaction_data t
on c.cal_dt = t.trans_dt
and t.trans_type = 'Purchase'
and c.fscl_yr_num in (2014, 2015, 2016, 2017)
group by
c.fscl_yr_num
,c.fscl_month_num
,t.user_id
order by
t.user_id
,c.fscl_yr_num
,c.fscl_month_num
;

Try
SUM(NVL(t.trans_counter,0))

Related

How to add custom YoY field to output?

I'm attempting to determine the YoY growth by month, 2017 to 2018, for number of Company bookings per property.
I've tried casting and windowed functions but am not obtaining the correct result.
Example Table 1: Bookings
BookID Amnt BookType InDate OutDate PropertyID Name Status
-----------------------------------------------------------------
789555 $1000 Company 1/1/2018 3/1/2018 22111 Wendy Active
478141 $1250 Owner 1/1/2017 2/1/2017 35825 John Cancelled
There are only two book types (e.g., Company, Owner) and two Book Status (e.g., Active and Cancelled).
Example Table 2: Properties
Property ID State Property Start Date Property End Date
---------------------------------------------------------------------
33111 New York 2/3/2017
35825 Michigan 7/21/2016
The Property End Date is blank when the company still owns it.
Example Table 3: Months
Start of Month End of Month
-------------------------------------------
1/1/2018 1/31/2018
The previous developer created this table which includes a row for each month from 2015-2020.
I've tried many various iterations of my current code and can't even come close.
Desired Outcome
I need to find the YoY growth by month, 2017 to 2018, for number of Company bookings per property. The stakeholder has requested the output to have the below columns:
Month Name Bookings_Per_Property_2017 Bookings_Per_Property_2018 YoY
-----------------------------------------------------------------------
The number of Company bookings per property in a month should be calculated by counting the total number of active Company bookings made in a month divided by the total number of properties active in the month.
Here is a solution that should be close to what you need. It works by:
LEFT JOINing the three tables; the important part is to properly check the overlaps in date ranges between months(StartOfMonth, EndOfMonth), bookings(InDate, OutDate) and properties(PropertyStartDate, PropertyEndDate): you can have a look at this reference post for general discussion on how to proceed efficiently
aggregating by month, and using conditional COUNT(DISTINCT ...) to count the number of properties and bookings in each month and year. The logic implicitly relies on the fact that this aggregate function ignores NULL values. Since we are using LEFT JOINs, we also need to handle the possibility that a denominator could have a 0 value.
Notes:
you did not provide expected results so this cannot be tested
also, you did not explain how to compute the YoY column, so I left it alone; I assume that you can easily compute it from the other columns
Query:
SELECT
MONTH(m.StartOfMonth) AS [Month],
COUNT(DISTINCT CASE WHEN YEAR(StartOfMonth) = 2017 THEN b.BookID END)
/ NULLIF(COUNT(DISTINCT CASE WHEN YEAR(StartOfMonth) = 2017 THEN p.PropertyID END), 0)
AS Bookings_Per_Property_2017,
COUNT(DISTINCT CASE WHEN YEAR(StartOfMonth) = 2018 THEN b.BookID END)
/ NULLIF(COUNT(DISTINCT CASE WHEN YEAR(StartOfMonth) = 2018 THEN p.PropertyID END), 0)
AS Bookings_Per_Property_2018
FROM months m
LEFT JOIN bookings b
ON m.StartOfMonth <= b.OutDate
AND m.EndOfMonth >= b.InDate
AND b.status = 'Active'
AND b.BookType = 'Company'
LEFT JOIN properties p
ON m.StartOfMonth <= COLAESCE(p.PropertyEndDate, m.StartOfMonth)
AND m.EndOfMonth >= p.PropertyStartDate
GROUP BY MONTH(m.StartOfMonth)

Subtracting results from two queries / sub queries

I'm trying to create a query which returns the results of customer orders (created in a month e.g. January) - the cancelled customer orders in that exact month (cancelled customer orders in month of January) and display the results grouped by location (Rows) and by year with month (Columns).
Currently I have a table containing all the customer order information both created and cancelled. Each customer order has a unique order number, location where it was sold, creation date and cancellation date. If the customer order is still valid, then the cancellation date will be null or "//". If the customer order is cancelled then it will have a cancellation date. As some additional information a customer order can be created in January 2019 and cancelled in July or August, or December etc. What I would like to obtain is the net customer orders for all the months by doing gross customer orders for a month - cancelled customer orders for that month and for a specific location = net customer orders for that month for that location.
In order to achieve this what I have tried, was to create two separate queries from the table, first one containing all the valid customer orders and the second one containing all the cancellations. Then i tried creating a cross-tab between the two other queries, trying to count what I mentioned above, grouping by location and then pivoting the of the year and month.
First query with valid customer orders named cust_valid (simplified):
SELECT cust_ords.[SaleLoc], cust_ords.[OrderNum], cust_ords.[CreationDate], cust_ords.[CancelDate]
FROM cust_ords
WHERE cust_ords.[CancelDate] = "" OR cust_ords.[CancelDate] = "//";
Second query with cancelled customer orders named cust_cancelled (simplified):
SELECT cust_ords.[SaleLoc], cust_ords.[OrderNum], cust_ords.[CreationDate], cust_ords.[CancelDate]
FROM cust_ords
WHERE cust_ords.[CancelDate] <> "" OR cust_ords.[CancelDate] <> "//";
Last, a cross-tab between them:
TRANSFORM Count(cust_valid.[OrderNum]) AS [NetOrderCount]
SELECT cust_valid.[SaleLoc]
FROM cust_valid LEFT JOIN cust_cancelled ON cust_valid.[CreationDate] = cust_cancelled.[CancelDate]
WHERE cust_valid.[CreationDate] = cust_cancelled.[CancelDate]
GROUP BY cust_ords.[SaleLoc]
PIVOT cust_valid.[CreationDate];
In this sense, I am trying to obtain (count) the net customer orders (total created for a month - what was cancelled in that month) for every given location and display the results per month (basically the columns names should be the year and the month). So for example if i have 10 customer orders in January, 5 in February and 15 in March, if 3 of the ones in January get cancelled in March, then I would like to count for the month of March 15 - 3, thus ending up with January 10, February 5, March 12.
First of all, you say an order is valid is valid if cancellation date is null or //, however you test for:
WHERE cust_ords.[CancelDate] = "" OR cust_ords.[CancelDate] = "//";
To test for null use [CancelDate] is null, or shorthand the test to ISNULL([CancelDate],'//')='//'
Second, in your second query you test for cancelled orders, with
WHERE cust_ords.[CancelDate] <> "" OR cust_ords.[CancelDate] <> "//";
That is not the negation of your test for cancelled orders!
!(A or B) => !A and !B
So you should use
WHERE cust_ords.[CancelDate] <> "" and cust_ords.[CancelDate] <> "//";
Or rather ISNULL(cust_ords.[CancelDate],'//')!='//'
Noow to your query itself, you are joining on dates, that is you join orders on a given date with cancellations on the same date. However you want to see orders and cancellation pr month. Since you left join cancellations you will only ever count cancellations that happen on the same date as orders!
SELECT
cust_valid.[SaleLoc]
, Format(
iif(isnull(cust_ords.[CancelDate],'//')='//'
,cust_ords.[CreationDate]
,cust_ords.[CancelDate]
,'MMMM yy') Mnth
, sum(iif(isnull(cust_ords.[CancelDate],'//'='//',1,0)) ValidOrders
, sum(iif(isnull(cust_ords.[CancelDate],'//'='//',0,1)) CancelledOrders
, sum(iif(isnull(cust_ords.[CancelDate],'//'='//',1,0))
- sum(iif(isnull(cust_ords.[CancelDate],'//'='//',0,1)) NetOrderCount
FROM
cust_ords
group by
cust_valid.[SaleLoc]
, Format(
iif(isnull(cust_ords.[CancelDate],'//')='//'
,cust_ords.[CreationDate]
,cust_ords.[CancelDate]
,'MMMM yy')
This should give you the basic data useful for pivoting, at least in SQL Server
THank you #Søren Kongstad for your useful explanations. I have modified / corrected the code accordingly to provide me with the needed results:
SELECT CustOrders.[Grupp namn], Format(IIf(CustOrders.[DatAnnulCde]="/ /",CustOrders.[DatCreatCde],CustOrders.[DatAnnulCde]),"yyyy-mm") AS year_month,
Sum(IIf(IsNull(CustOrders.DatCreatCde),0,1)) AS GrossOrders, Sum(IIf(CustOrders.DatAnnulCde<>"/ /",1,0)) AS CancelledOrders,
Sum(IIf(IsNull(CustOrders.DatCreatCde),0,1)) - Sum(IIf(CustOrders.DatAnnulCde<>"/ /",1,0)) AS NetOrders
FROM CustOrders
WHERE CustOrders.[CarType] = "Renault PC" And CustOrders.[DatCreatCde] >= Format(Year(Now())&"-01-01","yyyy-mm-dd")
GROUP BY CustOrders.[Grupp namn], Format(IIf(CustOrders.[DatAnnulCde]="/ /",CustOrders.[DatCreatCde],CustOrders.[DatAnnulCde]),"yyyy-mm");
The fields have different names then in the initial examples
DatAnnulCde = CancelDate
DatCreatCde = CreationDate

select all records from one table and return null values where they do not have another record in second table

I have looked high and low for this particular query and have not seen it.
We have two tables; Accounts table and then Visit table. I want to return the complete list of account names and fill in the corresponding fields with either null or the correct year etc. this data is used in a matrix report in SSRS.
sample:
Acounts:
AccountName AccountGroup Location
Brown Jug Brown Group Auckland
Top Shop Top Group Wellington
Super Shop Super Group Christchurch
Visit:
AcccountName VisitDate VisitAction
Brown Jug 12/12/2012 complete
Super Shop 1/10/2012 complete
I need to select weekly visits and show those that have had a complete visit and then the accounts that did not have a visit.
e.g.
Year Week AccountName VisitStatus for week 10/12/2012 should show
2012 50 Brown Jug complete
2012 50 Top Group not complete
2012 50 Super Shop not complete
e.g.
Year Week AccountName VisitStatus for week 1/10/2012 should show
2012 2 Brown Jug not complete
2012 2 Top Group not complete
2012 2 Super Shop complete
please correct me if am worng
select to_char(v.visitdate,'YYYY') year,
to_char(v.visitdate,'WW') WEAK,a.accountname,v.visitaction
from accounts a,visit v
where a.accountname=v.ACCCOUNTNAME
and to_char(v.visitdate,'WW')=to_char(sysdate,'WW')
union all
select to_char(sysdate,'YYYY') year,
to_char(sysdate,'WW') WEAK,a.accountname,'In Complete'
from accounts a
where a.accountname not in ( select v.ACCCOUNTNAME
from visit v where to_char(v.visitdate,'WW')=to_char(sysdate,'WW'));
The following answer assumes that
A) You want to see every week within a given range, whether any accounts were visited in that week or not.
B) You want to see all accounts for each week
C) For accounts that were visited in a given week, show their actual VisitAction.
D) For accounts that were NOT visited in a given week, show "not completed" as the VisitAction.
If all those are the case then the following query may do what you need. There is a functioning sqlfiddle example that you can play with here: http://sqlfiddle.com/#!3/4aac0/7
--First, get all the dates in the current year.
--This uses a Recursive CTE to generate a date
--for each week between a start date and an end date
--In SSRS you could create report parameters to replace
--these values.
WITH WeekDates AS
(
SELECT CAST('1/1/2012' AS DateTime) AS WeekDate
UNION ALL
SELECT DATEADD(WEEK,1,WeekDate) AS WeekDate
FROM WeekDates
WHERE DATEADD(WEEK,1,WeekDate) <= CAST('12/31/2012' AS DateTime)
),
--Next, add meta data to the weeks from above.
--Get the WeekYear and WeekNumber for each week.
--Note, you could skip this as a separate query
--and just included these in the next query,
--I've included it this way for clarity
Weeks AS
(
SELECT
WeekDate,
DATEPART(Year,WeekDate) AS WeekYear,
DATEPART(WEEK,WeekDate) AS WeekNumber
FROM WeekDates
),
--Cross join the weeks data from above with the
--Accounts table. This will make sure that we
--get a row for each account for each week.
--Be aware, this will be a large result set
--if there are a lot of weeks & accounts (weeks * account)
AccountWeeks AS
(
SELECT
*
FROM Weeks AS W
CROSS JOIN Accounts AS A
)
--Finally LEFT JOIN the AccountWeek data from above
--to the Visits table. This will ensure that we
--see each account/week, and we'll get nulls for
--the visit data for any accounts that were not visited
--in a given week.
SELECT
A.WeekYear,
A.WeekNumber,
A.AccountName,
A.AccountGroup,
IsNull(V.VisitAction,'not complete') AS VisitAction
FROM AccountWeeks AS A
LEFT JOIN Visits AS V
ON A.AccountName = V.AccountName
AND A.WeekNumber = DATEPART(WEEK,V.VisitDate)
--Set the maxrecursion number to a number
--larger than the number of weeks you will return
OPTION (MAXRECURSION 200);
I hope that helps.

To display only previous three months even the months before is not exist in database

Below is my new sql so far as i do not manage to use Dale M advice,
SELECT
all_months.a_month_id AS month,
year($P{date}) as year,
count(case when clixsteraccount.rem_joindate between DATE_FORMAT($P{date}-INTERVAL 2 MONTH, '%Y-%m-01') AND $P{date} THEN clixsteraccount.rem_registerbycn end) AS
total_activation,
'ACTIVATION(No)' AS fake_column
FROM clixsteraccount right join all_months on all_months.a_month_id = date_format(clixsteraccount.rem_joindate,'%m') and
(clixsteraccount.rem_registrationtype = 'Normal')and(clixsteraccount.rem_kapowstatus='pending' or clixsteraccount.rem_kapowstatus='success')
GROUP BY year,month
HAVING month BETWEEN month(date_sub($P{date},interval 2 month)) and month($P{date})
So, what i do is create a table with two fields, a_month_id(1,2,3...,12) and a_month(name of months). Sql above does give me what i want which is to display previous 3 months even the months before is not exist.
exp: data start on July. So, i want to display May,June and July data like 0,0,100.
The problem occur when it comes to next months or next year. When i try to generate sql based on parameter on Jan, it doesn't work like i thought. I do realize the problem are with 'Having' condition. Do anyone have idea how to improvised this sql to make it continue generate in the next,next year.
Thank you in advanced.
OK, I will make a few suggestions and give you an answer that will work on SQL Server - you will need to make any translations yourself.
I note that your query will aggregate all years together, i.e. Dec 2012 + Dec 2013 + Dec 2014 etc. Based on your question I don't think that is your intention so I will keep each distinct. You can change the query if that was your intention. I have also not included your selection criteria (other than by the month).
I suggest that you utilize an index table. This is a table stored in your database (or the master database if possible) with an clustered indexed integer column running from 0 to n where n is a sufficiently large number - 10,000 will be more than enough for this application (there are 12 months in a year so 10,000 represents 833 years). This table is so useful everyone should have one.
SELECT DATEADD(month, it.id, 0 ) AS month
,ISNULL(COUNT(clixsteraccount.rem_registerbycn), 0) AS registration
,'REGISTRATION(No)' AS fake_column
FROM cn
INNER JOIN ON ca.rem_registerbycn = cn.cn_id
clixsteraccount ca
RIGHT JOIN
IndexTable it ON it.id = DATEDIFF(month, 0, clixsteraccount.rem_joindate)
WHERE it.id BETWEEN DATEDIFF(month, 0, #StartDate) - 3 AND DATEDIFF(month, 0, GETDATE())
GROUP BY it.id
The way it works is by converting the clixsteraccount.rem_joindate to an integer that represents the number of months since date 0 (01-01-1900 in SQL Server). This is then matched to the id column of the IndexTable and limited by the dates you select. Because every number exists in the index table and we are using an outer join it doesn't matter if there are months missing from your data.

Optimizing NOT IN query in Access SQL

I am new to Access and am using Access 2007.
I am doing a simple query on a database that has a list of customers who visits a workshop.
I want to send out reminders to the customers for their servicing 3 months from the last time they visited. I have created a query to be able to return me the list of customers who has visited 3 months from the current month. For example, if it is May now, 3 months ago would be March (inclusive of May).
However, customers who visited 3 months ago may have visited again 2 months ago. For example, customer A came in March and April. His last visit was in April, and hence, should not appear in the result if I were to run the query in May, as his reminder should only be sent out in June.
My query has taken care of this, however, it is rather slow. It takes some time for it to load in Access. Any help would be appreciated in optimizing it.
The only important field here is Invoice.DebCode which is the customersID in the database. There is another table DEBTOR, which is the table of customers together with their particulars.
I used the INNER JOIN as I need to display the customer(Debtor) address and particulars in the result.
SELECT Invoice.InvNo, Invoice.InvDate, Invoice.DebCode, Debtor.DebName, Debtor.AddL1, Debtor.AddL2, Debtor.AddL3, Invoice.CarNo, Invoice.ChaNo, Invoice.ExcReason
FROM Debtor
JOIN Invoice ON Debtor.DebCode = Invoice.DebCode
WHERE Year(InvDate) = Year(Now())
AND Month(InvDate) = Month(Now()) - 2
AND Invoice.DebCode NOT IN (SELECT Invoice.DebCode
FROM Invoice
WHERE Year(InvDate) = Year(Now())
AND ( (Month(InvDate) = Month(Now()) -1)
OR (Month(InvDate) = Month(Now())) )
You can dramatically speed up your query by adjusting your WHERE clauses so that the comparisons get done directly against the date field (ie, without passing it through the Month() and Year() functions). Doing it this way allows the Jet engine to make use of the index you have on the Invoice.InvDate field (you do have that field indexed, right?).
SELECT I.InvNo, I.InvDate, I.DebCode, D.DebName, D.AddL1, D.AddL2, D.AddL3,
I.CarNo, I.ChaNo, I.ExcReason
FROM Debtor AS D
INNER JOIN Invoice AS I
ON D.DebCode = I.DebCode
WHERE I.InvDate Between DateSerial(Year(Now()), Month(Now()) - 2, 1)
And DateSerial(Year(Now()), Month(Now()) - 1, 0)
AND I.DebCode NOT IN
(SELECT Invoice.DebCode FROM Invoice
WHERE Invoice.InvDate > DateSerial(Year(Now()), Month(Now()) - 1, 0))
What about something like:
SELECT a.debcode, a.debname, a.debstuff, b.most_recent AS last_over_three_months
FROM debtor AS a INNER JOIN
(
SELECT debcode, Max(invdate) AS most_recent
FROM invoice
GROUP BY debcode
)
as b
ON a.debcode= b.debcode
WHERE (month(now()) - Month(most_recent) >2);
You will have to tweak for your stuff, but the idea is a subquery that select the most recent customer visit and then selects from that only the records that meet your month criteria.
I managed to speed up the query thanks to mwolfe02 suggestion.
For archiving and completion sake, I will explain my sql statements below.
SELECT I.InvNo, I.InvDate, I.DebCode, D.DebName, D.AddL1, D.AddL2, D.AddL3,
I.CarNo, I.ChaNo, I.ExcReason
FROM Debtor AS D
INNER JOIN Invoice AS I
ON D.DebCode = I.DebCode
WHERE I.InvDate Between DateSerial(Year(Now()), Month(Now()) - 2, 1)
And DateSerial(Year(Now()), Month(Now())- 1, 0)
AND I.DebCode NOT IN
(SELECT Invoice.DebCode FROM Invoice
WHERE Invoice.InvDate Between DateSerial(Year(Now()), Month(Now()) - 1, 1)
And DateSerial(Year(Now()), Month(Now()), 0))
I edited the bottom sub query as mwolfe checked only for customers in the current month. The customers eligible for a reminder only if they came 3 months ago. That is to say, they cannot have visited between the current month and the month before.
For example, customer A visited in April and May. The current month is June, thus he is not eligible for the reminder, as his last visit was in May.
Customer B visited in April and June, thus he is not eligible for the reminder, as his last visit was in June.
Hence, the english version of the query would be:
Get customers who came 3 months ago from the last day of the current month and did not come in the current month and the month before.
I hope this helps anyone who has the same problem.
As darkjh and mikey suggested, we can "select the most recent customer visit and then selects from that only the records that meet your month criteria."
Thanks all!