Using Access for Sales Variance Reporting - Query issues - sql

So I want to create a variance report. Simply sales - planned sales = variance etc.
I have two table, one called Actual one called Plan. Both have the same fields,
-account code
-month
-year
-sales
The idea being I populate the Plan table with the years plan data then update Actuals with actuals as they come in.
The problem I have, is how do I build a query that shows both (1) unplanned sales and (2) planned sales without an actual? I can't find a join type that does it.
Or am I completely missing the point and a more obvious way of doing this?!

I am not sure if I understand the problem. If sometimes records are missing in Actual and sometimes in Plan then combine a LEFT JOIN query with a RIGHT JOIN query in a UNION query.
SELECT
P.*,
A.sales - P.sales AS variance
FROM
Plan P
LEFT JOIN Actual A
ON P.[account code] = A.[account code] AND
P.month = A.month AND
P.year = A.year
UNION ALL
SELECT
A.*,
NULL AS variance
FROM
Plan P
RIGHT JOIN Actual A
ON P.[account code] = A.[account code] AND
P.month = A.month AND
P.year = A.year
WHERE
P.[account code] IS NULL
The WHERE clause avoids duplicates and selects only records that were missing in the first SELECT query.

Related

getting duplicate records in Access query

I have three tables: The [inventory snapshot] table shows the name of the product and how many we have on site; the [inbound loads] table shows how many are coming in; the [outbound routes] table lists how many are going out.
I was getting unique values when I only had the first and second table (showing on-hand and 'arriving') but when I added in the third, I began getting multiple records instead of summed records.
Purpose of query
I work in a warehouse and I'm trying to isolate pick slots that are 1) low on inventory; 2)have no more product coming in; 3) I'd like to know if there are open orders to ship out any of the remaining product.
SELECT DISTINCT [inventory snapshot].locn_brcd,
[inventory snapshot].description,
[inventory snapshot].item_name,
Sum([inventory snapshot].on_hand_qty) AS SumOfON_HAND_QTY,
Sum([outbound routes].quantity) AS SumOfQuantity
FROM [outbound routes]
RIGHT JOIN ([inbound loads]
RIGHT JOIN [inventory snapshot]
ON [inbound loads].[wrin number] =
[inventory snapshot].item_name)
ON [outbound routes].[wrin number] =
[inventory snapshot].item_name
GROUP BY [inventory snapshot].locn_brcd,
[inventory snapshot].description,
[inventory snapshot].item_name,
[inbound loads].[quantity to receive]
HAVING ( ( ( Sum([inventory snapshot].on_hand_qty) ) < 10 )
AND ( ( [inbound loads].[quantity to receive] ) IS NULL ) );
Since you resolved your issue, consider further adjustment of grouping columns. Right now, the GROUP BY clause contains one more field than SELECT clause, namely: [inbound loads].[quantity to receive].
Typically, in aggregate SQL queries like this one, numeric values like quantity are not grouped but run as aggregated columns. You may have added it to GROUP BY in order to use it in HAVING. However, non-aggregates can be handled in WHERE to filter before aggregation. (Oddly, MS Access tends to move such level filtering in HAVING using Query Design.)
Consider below refactored SQL which uses table alias to avoid repetition of long table names. Of course, remove your needless location grouping.
SELECT s.locn_brcd
, s.description
, s.item_name
, SUM(s.on_hand_qty) AS SumOfON_HAND_QTY
, SUM(r.quantity) AS SumOfQuantity
FROM [outbound routes] r
RIGHT JOIN ([inbound loads] l
RIGHT JOIN [inventory snapshot] s
ON l.[wrin number] = s.item_name)
ON r.[wrin number] = s.item_name
WHERE l.[quantity to receive] IS NULL
GROUP BY s.locn_brcd
, s.description
, s.item_name
HAVING SUM(s.on_hand_qty) < 10 ;
By the way, the SQL industry tends to use LEFT JOIN rather than RIGHT JOIN for readability which you may not have control as this query may be output from Query Design. Try equivalent version without nesting JOINs where parentheses is required in Access SQL:
FROM ([inventory snapshot] s
LEFT JOIN ([inbound loads] l
ON l.[wrin number] = s.item_name)
LEFT JOIN [outbound routes] r
ON r.[wrin number] = s.item_name

Correct SQL query statement requiring JOIN and UNION

I was trying to solve this question (No. 29) on http://www.sql-ex.ru/
Under the assumption that the income (inc) and expenses (out) of the
money at each outlet are written not more than once a day, get a
result set with the fields: point, date, income, expense. Use Income_o
and Outcome_o tables.
And came up with this solution
SELECT Income_o.point, Income_o.date, Income_o.inc, Outcome_o.out
FROM Income_o
INNER JOIN Outcome_o ON Income_o.point = Outcome_o.point
The result is obviously wrong (and hence my question here). It assumes that a point will never have more than 1 income and expense, so isn't this query correct? I can see from the same page that the correct query has some NULL column values. I would appreciate an explanation (if not the correct answer). My SQL is not a master one (and that's why I am trying to go through those!! So far done 29 out of 125 and only took help from SO on 3 of them)
The expected result is (From the website):
The result of correct query:
A snapshot of the expected result is here - http://snag.gy/yN43V.jpg
P.S. I know that the hint says UNION and JOIN and trying to get my head around this. If I can get the answer myself, I will post it.
You want a full outer join on point and date:
SELECT
COALESCE(i.point, o.point) AS point,
COALESCE(i.date, o.date) AS date,
i.inc,
o.out
FROM
Income_o AS i
FULL JOIN Outcome_o AS o ON i.point = o.point AND i.date = o.date
;
The COALESCE expressions ensure that NULL is not returned for those columns: if the Income_o side has a NULL (because the table has no match for an Outcome_o row), the value is then taken from the other side.
Alternatively you can go with a union of two outer joins, left and right:
SELECT
i.point,
i.date,
i.inc,
o.out
FROM
Income_o AS i
LEFT JOIN Outcome_o AS o ON i.point = o.point AND i.date = o.date
UNION
SELECT
o.point,
o.date,
i.inc,
o.out
FROM
Income_o AS i
RIGHT JOIN Outcome_o AS o ON i.point = o.point AND i.date = o.date
;
If the tables have matches on the specified condition, both joins will return them, but UNION will eliminate duplicate entries. This second method is essentially an alternative implementation of full outer join, useful for cases where the FULL JOIN syntax is not supported. (MySQL is one product that does not support FULL JOIN.)
You can use Group By with Aggregate Function to achieve the desired result, the sub query will combine the result set but will give results as per date, if there are two (in and out transaction) on same date, these will appear as two rows, to make it one row we can use Group By with aggregate function.
select point, date, max(inc), max(out)
from
(
select point, date, inc, NULL as out
from income_o
union all
select point, date, NULL, out
from outcome_o
)
dt
group by point, date
I think you are looking for LEFT JOIN
SELECT Income_o.point, Income_o.dat, Income_o.inc, Outcome_o.outc
FROM Income_o
LEFT JOIN Outcome_o ON Income_o.point = Outcome_o.point
try this SQL Fiddle example

Selecting the most frequent value in a column based on the value of another column in the same row?

So basically what I'm trying to do is generate a report for our stores. We have an incident report website where the employees can report an incident that takes place at any of our stores. So in the general report I'm trying to generate, I want to show the details for each store we have (Five stores). This would include the name of the store, number of incidents, oldest incident date, newest incident date, and then the most recurring type of incident at each store.
SELECT Store.Name AS [Store Name], COUNT(*) AS [No. Of Incidents], Min(CAST(DateNotified AS date)) AS [Oldest Incident], Max(CAST(DateNotified AS date)) AS [Latest Incident],
( SELECT TOP 1 IncidentType.Details
FROM IncidentDetails
INNER JOIN Store ON IncidentDetails.StoreID = Store.StoreID
INNER JOIN IncidentType On IncidentDetails.IncidentTypeID = IncidentType.IncidentTypeID
Group By IncidentType.Details, IncidentDetails.StoreID
Order By COUNT(IncidentType.Details) DESC) AS [Most Freqeuent Incident]
FROM IncidentDetails
INNER JOIN Store ON IncidentDetails.StoreID = Store.StoreID
INNER JOIN IncidentType On IncidentDetails.IncidentTypeID = IncidentType.IncidentTypeID
GROUP BY Store.Name
Just to make it clear, the IncidentDetails table stores all the details about the incident including which store it occured at, what the type of incident was, time/date, etc.
What this does though is it gives me 5 rows for each store, but the [Most Frequent Incident] value is the same for every row. Basically, it gets the most frequent incident value for the whole table, regardless of which store it came from, and then displays that for each store, even though different stores have different values for the column.
I've been trying to solve this for a while now but haven't been able to :-(
You have too many joins and no correlation clause.
There are several ways to approach this problem. You have already started with an aggregation in the outer query and then a nested subquery. So, this continues that approach. I think this does what you want:
SELECT s.Name AS [Store Name], COUNT(*) AS [No. Of Incidents],
Min(CAST(DateNotified AS date)) AS [Oldest Incident],
Max(CAST(DateNotified AS date)) AS [Latest Incident],
(SELECT TOP 1 it.Details
FROM IncidentDetails id2 INNER JOIN
IncidentType it2
On id2.IncidentTypeID = it2.IncidentTypeID
WHERE id2.StoreId = s.StoreId
Group By it.Details
Order By COUNT(*) DESC
) AS [Most Freqeuent Incident]
FROM IncidentDetails id INNER JOIN
Store s
ON id.StoreID = s.StoreID
GROUP BY s.Name, s.StoreId;
Notes:
Removed the IncidentType table from the outer joins. This doesn't seem needed (although it could be used for filtering).
Added s.StoredId to the group by clause. This is needed for the correlation in the subquery.
Added a where clause so the subquery is only processed once for each store in the outer query.
Removed the join to Store in the subquery. It seems unnecessary, if the queries can be correlated on StoreId.
Changed the group by in the subquery to use Details. That is the value being selected.
Added table aliases, which make queries easier to write and to read.

Issue when I join three tables

I have three tables TeamTerritoryMapping, UpdQuotaTbl and tblInvoiceFile
When I join the first two tables I am getting the correct result
select
TTM.Team, Sum(Upd.Quota) as Quota
from
TeamTerritoryMapping TTM
inner join
UpdQuotaTbl upd on upd.ITM = TTm.Territory
where
upd.Month = 'july'
group by
TTM.Team
but when I join the third table for another column revenue from tblinvoicefile, some of the rows are getting duplicated and the end result is becoming higher. Below is the query which I am using to join 3 tables
select
TTM.Team,
Sum(upd.Quota) as quota,
sum(inv.[End MS Sales Revenue]) as Revenue
from
UpdQuotaTbl Upd
inner join
TeamTerritoryMapping TTM on TTM.Territory = upd.ITM
inner join
tblInvoiceFile inv on inv.[Inv Territory] = TTM.Territory
where
upd.Month = 'july'
and inv.[End Fiscal Month] = 'July, 2013'
So how can I eradicate the duplicate values in third table, I am getting the correct value when I join two tables ie TeamTerritoryMapping,UpdQuotaTbl and also tables TeamTerritoryMapping.
It looks like tblInvoiceFile has multiple entires for [Invenio Territory] which is causing this issue. If your intention is to bring in and sum all the [End MS Sales Revenue] for that table you can try something like this
SELECT TTM.Team,Sum(upd.Quota) as quota,sum(inv.[End MS Sales Revenue]) as Revenue
FROM UpdQuotaTbl Upd
INNER JOIN TeamTerritoryMapping TTM ON TTM.Territory = upd.ITM
INNER JOIN
(SELECT [Invenio Territory], SUM([End MS Sales Revenue]) AS [End MS Sales Revenue]
FROM tblInvoiceFile
WHERE [End Fiscal Month] = 'July, 2013'
GROUP BY [Invenio Territory]) AS inv
ON inv.[Invenio Territory] = TTM.Territory
WHERE upd.Month = 'july'
My initial response would be : The reason you're getting some results multiple times is because the ON used to join tblInvoiceFile is not 'unique' enough. If you look at the primary key (or a unique?) of said table you'll probably notice that it involves more than just the Inv Territory field.
To solve it you can do two different things.
Expand the ON clause to include more fields that make the connection unique
Keep the query as it is now but aggregate the results so the invoice information gets SUMmed
The problem is, it seems like you're already SUM()-ing the information (although as noted by Win, you're missing the GROUP BY), so I'm not quite sure why this would not work.
UPDATE: Only now realise that your problem is not doubled records, but doubled values in the first SUM(). Best way to solve this is by PRE-aggregating the values as shown by Abhi.

Complex SQL Query Help

I am trying to do rather complex SQL query to produce a report. This is a database used by an inventory and accounting system.
Essentially I need to produce a report with the following columns
Month / Year (group results by month / year)
Reseller (order results by reseller with in the month / year group)
Total sales - Sales - Hardware
Total sales - Sales - Consumables
The following tables will need to be used in the report:
Invoice
Reseller
Job
JobStockItem
Stock
Essentially the query would need to start as:
1. Select all invoices from Invoice
2. Get the reseller name from Reseller.Name (join on Reseller.ID with Invoice.CustomerID)
3. Get the associated job ID from Job table (join on Job.InvoiceID with Invoice.ID)
4. Get each component of the invoice from JobStockItems (join JobStockItem.JobID on Job.ID)
5. Get the stock item in in the job from Stock (join on JobStockItems.StockId on Stock.ID) and see if the category (Stock.Category1) is either Hardware or Consumables
6. If the stock item is hardware or consumables, use the sale price in the JobStockItem (JobStockItem.PriceExTax) and add it towards the total for the month of the resellers purchases
The month and year come from the invoice date (Invoice.InvoiceDate).
Now I could produce this result myself by executing a bunch of queries and processing myself, one each for the above steps, but it's going to end up slow and I'm sure there'd have to be a query out there that could wrap all those requirements up and do it in one?
I have not attempted to do the query yet as to be honest, I don't know where to start - it's a lot more complex than anything I've done in the past.
I am just using Management Studio, not using Reporting Services, Crystal Reports or anything. My aim is to dump the output to HTML when I have it working.
Thanks heaps in advance.
It think if you Left Join into the JobStockItems table twice (once for hardware, and once for consumables), you can manage all that in one query. The final query will look something like this (Don't have my editor up right now, so apologies for any typos)
SELECT DATEPART(m, Invoice.InvoiceDate) month,
DATEPART(yy, Invoice.InvoiceDate) year,
Reseller.Name,
SUM(jobstockitems_hardware.Price) sales_hardware,
SUM(jobstockitems_consumables.Price) sales_consumables,
FROM Invoice
INNER JOIN Reseller
ON Invoice.CustomerID = Reseller.ID
INNER JOIN Job
ON Invoice.ID = Job.InvoiceID
LEFT JOIN (SELECT JobID, SUM(PriceExTax) Price
FROM JobStockItems
INNER JOIN Stock
ON JobStockItems.StockID = Stock.StockID
AND Stock.Category1 = 'Hardware'
GROUP BY JobID) jobstockitems_hardware
ON Job.ID = jobstockitems_hardware.JobID
LEFT JOIN (SELECT JobID, SUM(PriceExTax) Price
FROM JobStockItems
INNER JOIN Stock
ON JobStockItems.StockID = Stock.StockID
AND Stock.Category1 = 'Consumables'
GROUP BY JobID) jobstockitems_consumables
ON Job.ID = jobstockitems_consumables.JobID
GROUP BY DATEPART(m, Invoice.Date),
DATEPART(yy, Invoice.Date),
Reseller.Name
ORDER BY DATEPART(yy, Invoice.Date) ASC,
DATEPART(m, Invoice.Date) ASC,
Reseller.Name ASC
I'm assuming that Retailer has a column called Name that you want to return as well, feel free to change that to ID or whatever else you'd rather return.
Edit: Fixed query to remove duplicates
Partly you want to PIVOT your results. So you can simply join your data together and let the PIVOT do the selecting and summarizing of categories:
select *
from (
select Year = datepart(year, i.InvoiceDate),
Month = datepart(month, i.InvoiceDate),
ResellerName = r.name,
StockCategory = s.Category1,
jsi.PriceExTax
from Invoice i
inner join Reseller r
on r.ID = i.CustomerID
inner join Job j
on j.InvoiceID = i.ID
inner join JobStockItem jsi
on jsi.JobID = j.ID
inner join Stock s
on s.ID = jsi.StockID
) d
pivot (sum(PriceExTax) for StockCategory in (Hardware, Consumables)) p
order by Year, Month, ResellerName;
In the end you'll need some conditions in the inner query (e.g. where i.InvoiceDate between ...).
Perhaps you have to multiply the PriceExTax with the amount in JobStockItem...
DPMattingley has produced a good query, but with one limitation - if a time period has no results, it won't show a row at all. This may well be an unlikely case in the specific example here but where it does happen it's annoying to find the report hides the zero results month!
My standard solution to this involves taking the month from a numbers table, which forces all time periods to appear. Using DPMattingley's query as a starting point -
select
mths.month,
mths.year,
d.Name,
d.sales_hardware,
d.sales_consumables
from
/*All the in-range months on which to report*/
(select
datepart(m,dateadd(m,number,minDate)) month,
datepart(yy,dateadd(m,number,minDate)) year
from
numbers n
inner join (select min(invoice.invoicedate) as minDate from invoice) m
on n.number between 0 and datediff(m,minDate,getdate())) mths
left join
/*Data for each month*/
(SELECT DATEPART(m, Invoice.InvoiceDate) month,
DATEPART(yy, Invoice.InvoiceDate) year,
Reseller.Name,
SUM(jobstockitems_hardware.Price) sales_hardware,
SUM(jobstockitems_consumables.Price) sales_consumables
FROM
Invoice
INNER JOIN Reseller
ON Invoice.CustomerID = Reseller.ID
INNER JOIN Job
ON Invoice.ID = Job.InvoiceID
LEFT JOIN (SELECT JobID, SUM(PriceExTax) Price
FROM JobStockItems
INNER JOIN Stock
ON JobStockItems.StockID = Stock.StockID
AND Stock.Category1 = 'Hardware'
GROUP BY JobID) jobstockitems_hardware
ON Job.ID = jobstockitems_hardware.JobID
LEFT JOIN (SELECT JobID, SUM(PriceExTax) Price
FROM JobStockItems
INNER JOIN Stock
ON JobStockItems.StockID = Stock.StockID
AND Stock.Category1 = 'Consumables'
GROUP BY JobID) jobstockitems_consumables
ON Job.ID = jobstockitems_consumables.JobID
GROUP BY DATEPART(m, Invoice.Date),
DATEPART(yy, Invoice.Date),
Reseller.Name) d
on mths.month=d.month
and mths.year=d.year
ORDER BY mths.month ASC,
mths.year ASC,
Reseller.Name ASC