Optimizing NOT IN query in Access SQL - 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!

Related

SQL case expression to run report based on Fiscal Year

I'm attempting to create some reports in my organization's new Help Desk system, which uses sql for its database and reporting systems. I've never interacted with sql before, so I'm having to make this up as I go. I've been able to cannibalize most of what I need from other, pre-made reports, but I'm running into a road block getting the report to group by Fiscal Year and not Calendar Year. The report I'm currently working on is meant to calculate the average time to closure on tickets. The original version grouped and ordered everything by the calendar year, and appeared to be working. However, when I try to retrofit the code to group/sort based on a July 1-June 30 Fiscal Year using the suggested method in this answer (as shown in my report code below), I'm getting the error, "The multi-part identifier "htblticket.date could not be bound."
I also tried some variants on this answer, with even less success. Does anyone have some suggestions on what I might be screwing up here, or some ideas on better ways to approach this? I'd appreciate any help/enlightenment you can provide!
Select Top 1000000 Case
When DatePart(mm, htblticket.date) > 6 Then DatePart(yyyy, htblticket.date)
+ 1
Else DatePart(yyyy, htlbticket.date)
End As [Fiscal Year],
Convert(Decimal(9,2),Avg(Cast(DateDiff(ss, htblticket.date,
ClosedDate.CloseDate) As decimal) / 86400)) As AverageDays
From htblticket
Inner Join (Select Top 1000000 htblticket.ticketid,
Max(htblhistory.date) As CloseDate
From htblticket
Inner Join htblticketstates On htblticketstates.ticketstateid =
htblticket.ticketstateid
Inner Join htblhistory On htblhistory.ticketid = htblticket.ticketid
Inner Join htblticketstates htblticketstates1 On
htblhistory.ticketstateid = htblticketstates1.ticketstateid
Inner Join htblhistorytypes On htblhistorytypes.typeid =
htblhistory.typeid
Where htblticketstates.statename = 'Closed' And
htblticketstates1.statename = 'Closed' And
htblhistorytypes.name In ('Status changed',
'Note added and state changed', 'Internal note added and state changed')
Group By htblticket.ticketid) As ClosedDate On ClosedDate.ticketid =
htblticket.ticketid
Group By Case
When DatePart(mm, htblticket.date) > 6 Then DatePart(yyyy, htblticket.date)
+ 1
Else DatePart(yyyy, htlbticket.date)
End
Order By [Fiscal Year] Desc
Your question boils down to how to group by the (Australian) financial year July-June.
One simple approach would be to create a column “financial_year_start” which is an expression that subtracts 6 months from each date and extracts the year of the result, then group by that.

Showing Purchase Counts for ALL months - Including 0 purchase months

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))

SQL Count of Instances of Date in MS Access

So I've got a table of data in the link below (an excel spreadsheet in a zip file):
https://drive.google.com/file/d/0B4mYzBk2sry_eDg5NVhTcmtXTTg/view?usp=sharing
It has been sorted and ordered as 'Date Issued - Oldest to Most Recent'.
What I'm after is a solution that gives me a count of instances an issue was made in a month for the Financial Year (01/07/2015 to 30/06/2016).
For instance, referring to the data, July 2015 had 13 issues for that month, August 2015 had 16 issues, September 2015 had 9 issues, etc.
I've created the code below to generate the data provided in the spreadsheet:
SELECT tblCustomerNames_1.CustomerName AS [Issued To], tblCustomerNames.CustomerName AS [Issued By], tblIssueSheets.DateIssued AS [Date Issued], Count(tblMarkHistory.MarkHistoryID) AS [Marks Issued]
FROM (tblCustomerNames AS tblCustomerNames_1 INNER JOIN (tblIssueSheets INNER JOIN tblCustomerNames ON tblIssueSheets.IssuedBy = tblCustomerNames.CustomerID) ON tblCustomerNames_1.CustomerID = tblIssueSheets.CustomerID) INNER JOIN tblMarkHistory ON tblIssueSheets.IssueID = tblMarkHistory.IssueID
WHERE (((tblIssueSheets.DateIssued)>=[DateFrom] And (tblIssueSheets.DateIssued)<[DateTo]) AND ((tblCustomerNames.CustomerID)=2447))
GROUP BY tblCustomerNames_1.CustomerName, tblCustomerNames.CustomerName, tblIssueSheets.DateIssued
ORDER BY tblCustomerNames_1.CustomerName;
Please note that data has been deliberately left out as I figured the information I've provided thus far is more important to what is needed. If it isn't sufficient, let me know and I'll provide the rest of the data.
A result table of the totals of Date Issued by month and year is what I'm after.
I'm not sure how to go about this at all. Any assistance would be appreciated.
Using your saved query as source, it could be:
Select
Month([Date Issued]) As [Month],
Count(*) As Issues
From
YourQuery
Where
[Date Issued] Between #2015/07/01# And #2016/06/30#
Group By
Month([Date Issued])
Order By
Year([Date Issued]),
Month([Date Issued])
If Month# should follow the financial year, use a generic function to offset the dates from the calendar year:
Public Function DateFinancial( _
ByVal datDate As Date) _
As Date
' Number of months from start of calendar year to start of financial year.
Const clngMonthOffset As Long = 6
Dim datFinancial As Date
datFinancial = DateAdd("m", -clngMonthOffset, datDate)
DateFinancial = datFinancial
End Function

Group data from 3 tables and sort by week ending date

Here is the problem I am stuck on.
I have three tables which log the dates and locations customers visit my sales centers. One table is the initial visit, the second is a able for repeat visits, of which there could be many and the third is a deposit table which stores the data related to when people leave a deposit and on which job they left the deposit.
So I need to sort all do this data by the week ending date, which I do have working on one table at a time. So for example, anyone who visited my sales center on March 3 would be counted in as traffic for the week ending march 9.
Now I would like to query the data so that I call pull the dates from the other two tables as well and have them sort by week ending along with the other data.
So my final output would look like this:
Week ending | initial visit | bback | deposit
3/9/2014 9 3 0
3/16/2104. 12. 0. 1
My tables structure looks like this:
Salescenter_clientinfo
Initial_visit (date)
Community
Customer_bback
Bback_date
Bback_community
Customer Deposit
Deposit_date
Doposit_community
All of these tables also have a field which is the customer_id which links them all.
As stated earlier, I do have the sql working which gets the job done using one table but how do I accomplish this on multiple tables?
I forgot to mention, I run this sql from an excel pivot table against an MSSql database. I am using excel to do the pivot and be the final report.
Any help in very much appreciated.
Thanks in advance.
Steve
If I'm understanding your original question correctly, you're trying to track everything that happened at your center, in each category, during a specific week. So if a customer's initial visit was during week 1, and he/she came back during week 2, those two items would fall under different weeks.
Based on that assumption, the linkage on Customer_ID is actually a bit of a red herring: you want data aggregated by week, and don't care if it all reflects the same customer's initial visit.
If so, then the following SQL might do what you are looking for:
Set Datefirst 1;
SELECT
ISNULL(vc.WeekEnding, ISNULL(b.WeekEnding, d.WeekEnding)) WeekEnding,
vc.InitialVisit,
b.BBack,
d.Deposit
FROM
(
SELECT
DATEADD(dd, 7 - DATEPART(DW, initial_visit), initial_visit) AS WeekEnding,
COUNT(initial_visit) AS InitialVisit
FROM dbo.SalesCenter_clientinfo
GROUP BY DATEADD(dd, 7 - DATEPART(DW, initial_visit), initial_visit)
) vc
FULL OUTER JOIN
(
SELECT
DATEADD(dd, 7 - DATEPART(DW, Bback_date), Bback_date) AS WeekEnding,
COUNT(Bback_date) AS BBack
FROM dbo.Customer_bback
GROUP BY DATEADD(dd, 7 - DATEPART(DW, Bback_date), Bback_date)
) b ON
vc.WeekEnding = b.WeekEnding
FULL OUTER JOIN
(
SELECT
DATEADD(dd, 7 - DATEPART(DW, Deposit_date), Deposit_date) AS WeekEnding,
COUNT(Deposit_date) AS Deposit
FROM dbo.Customer_deposit
GROUP BY DATEADD(dd, 7 - DATEPART(DW, Deposit_date), Deposit_date)
) d ON
vc.WeekEnding = d.WeekEnding
Fiddle

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.