How to report MS Access data, calculated columns with group by - sql

I have an Access 2003 database which records fault call help requests in a medium size organisation of around 200 users. Calls are logged (and appended into the database) via a Classic ASP page, and a team of systems administrators use a seperate classic ASP web page to view calls, provide a response, etc.
All calls are recorded in one table called tblFaultCall, it's structure is below
tblFault call
ID : Autonumber
strName
strPhone
dtmDateOpen : Date/Time (date call logged)
dtmDateClosed : Date/Time (date call closed)
dtmTime : Date/Time (time call logged)
strStatus (always 'Open', 'Pending' or 'Closed')
strCategory (always one of 10 categories, held as as list in tblCatgory, and used in lookup lists in the ASP web page)
strFaultDesc
strResolution
strCallOwner
dtmDatePending : Date/Time (date call set to pending, if it ever was)
For management, I need a way of easily creating a quarterly report which shows as below
Call recieved between dd/mm/yyyy and dd/mm/yyyy
----
Category Calls recieved Of which 'Closed' closed within 5 days Closed within 14 days Open Pending
Cateogry x 1052 950 700 200 50 50
Cateogry Y 65 60 50 5 0 5
I need an easy way to do this. I need the manager to be able to insert the dates he wants, and then click a button and it all comes up. I cannot work out how to create one query which gives all of this. It's easy to give just the categories and number of Open calls, but then can't work out how to add a further column to show number of Closed calls, or the number closed within x days, etc. I can create individual queries for the harder columns, but not get it all together.
So, options are
Classic ASP - I think would involve a lot of individual SQLs for the calculated fields
Access Report ?
Some kind of export to Excel?
VBA in Excel to link back to prepared queries in Access?
Any advise would be appreciated.

You should be able to get that data in one query. Try this one:
SELECT AllCalls.strCategory, CallsReceived, CallsClosed, ClosedWithin5Days, ClosedWithin14days, CallsOpen, CallsPending
FROM
((
SELECT strCategory,
Count(ID) AS CallsReceived,
Sum(IIF(strStatus='Closed',1,0)) AS CallsClosed,
Sum(IIF(strStatus='Open',1,0)) AS CallsOpen,
Sum(IIF(strStatus='Pending',1,0)) AS CallsPending
FROM tblFaultCall
WHERE dtmDateOpen BETWEEN #6/1/2014# and #6/30/2014#
GROUP BY strCategory
) AS AllCalls
LEFT JOIN
(
SELECT strCategory,
Count(ID) AS ClosedWithin5Days
FROM tblFaultCall
WHERE DateDiff("d", dtmDateOpen, dtmDateClosed) <=5
AND dtmDateOpen BETWEEN #6/1/2014# and #6/30/2014#
GROUP BY strCategory
) AS FiveDay ON AllCalls.strCategory=FiveDay.strCategory)
LEFT JOIN
(
SELECT strCategory,
Count(ID) AS ClosedWithin14Days
FROM tblFaultCall
WHERE DateDiff("d", dtmDateOpen, dtmDateClosed) between 5 and 14
AND dtmDateOpen BETWEEN #6/1/2014# and #6/30/2014#
GROUP BY strCategory
) AS FourteenDay ON AllCalls.strCategory=FourteenDay.strCategory
The classic ASP part should be very similar to your other pages: query the database, loop through the resulting data, output it to the screen. You would use the same approach if you were generating a spreadsheet too.

Each column can be calculated, mostly with iif statements:
Total calls = count(calls)
Closed calls = sum(iif(<call is closed>,1,0) (however you define <call is closed>)
Closed in 5 days = sum(iif(<call is closed in 5 days>,1,0))
and so on

Related

SQL Command Returning Unexpected Results

I am having issues with a sql procedure I wrote quite some time ago. The procedure is designed to generate a report that displays all missing records within our database.
For example, if ticket numbers 10, 11, and 13 have been used, then the procedure should return ticket number 12 on the report.
We manage each ticket book and issue them out accordingly when a person takes one. When the ticket book is issued, all 25 tickets in the book are created in the database, and when each individual ticket is entered into the database, the ticket is marked as used. This is how the procedure checks what tickets are used and not used.
This is the current procedure we are utilizing
create procedure FindMissingTickets
as
select TicketNum, TicketBookNum, UnitID, DateIssued, IssuedBy
from TicketBooks a
where a.Used='No'
and a.TicketNum < (select MAX(b.TicketNum)
from TicketBooks b
where b.Used='Yes'
and b.UnitID=a.UnitID
and b.BookType='Truck' or b.BookType='Work')
and a.BookType='Truck' or a.BookType='Work'
order by TicketNum desc
go
This is a quick example of how our database table is laid out:
So in theory, the ticket number 525 should be shown on this report. What is actually is happening is all of the records from ticket number 493 (the first ticket in the database) to 550 (the last ticket of this type) are shown in the report, even though that all of them except 525 are set the Used
I don't fully understand how this procedure could return any records that have the column Used set to Yes.
Any help is greatly appreciated.
It's just missing parentheses, you need to place parentheses around the last part of your where clause:
and (a.BookType='Truck' or a.BookType='Work')
I think you just need to fix up your WHERE clause:
where a.Used='No'
and a.TicketNum < (select MAX(b.TicketNum)
from TicketBooks b
where b.Used='Yes'
and b.UnitID=a.UnitID
and b.BookType='Truck' or b.BookType='Work')
and (a.BookType='Truck' or a.BookType='Work')
You had:
WHERE A and B or C
I think you mean:
WHERE A and (B or C)

Access query, grouped sum of 2 columns where either column contains values

Another team has an Access database that they use to track call logs. It's very basic, really just a table with a few lookups, and they enter data directly in the datasheet view. They've asked me to assist with writing a report to sum up their calls by week and reason and I'm a bit stumped on this problem because I'm not an Access guy by any stretch.
The database consists of two core tables, one holding the call log entries (Calls) and one holding the lookup list of call reasons (ReasonsLookup). Relevant table structures are:
Calls
-----
ID (autonumber, PK)
DateLogged (datetime)
Reason (int, FK to ReasonLookup.ID)
Reason 2 (int, FK to ReasonLookup.ID)
ReasonLookup
------------
ID (autonumber PK)
Reason (text)
What they want is a report that looks like this:
WeekNum Reason Total
------- ---------- -----
10 Eligibility Request 24
10 Extension Request 43
10 Information Question 97
11 Eligibility Request 35
11 Information Question 154
... ... etc ...
My problem is that there are TWO columns in the Calls table, because they wanted to log a primary and secondary reason for receiving the call, i.e. someone calls for reason A and while on the phone also requests something under reason B. Every call will have a primary reason column value (Calls.Reason not null) but not necessarily a secondary reason column value (Calls.[Reason 2] is often null).
What they want is, for each WeekNum, a single (distinct) entry for each possible Reason, and a Total of how many times that Reason was used in either the Calls.Reason or Calls.[Reason 2] column for that week. So in the example above for Eligibility Request, they want to see one entry for Eligibility Request for the week and count every record in Calls that for that week that has Calls.Reason = Eligibility Request OR Calls.[Reason 2] = Eligibility Request.
What is the best way to approach a query that will display as shown above? Ideally this is a straight query, no VBA required. They are non-technical so the simpler and easier to maintain the better if possible.
Thanks in advance, any help much appreciated.
The "normal" approach would be to use a union all query as a subquery to create a set of weeks and reasons, however Access doesn't support this, but what you can do that should work is to first define a query to make the union and then use that query as a source for the "main" query.
So the first query would be
SELECT datepart("ww",datelogged) as week, Reason from calls
UNION ALL
SELECT datepart("ww",datelogged), [Reason 2] from calls;
Save this as UnionQuery and make another query mainQuery:
SELECT uq.week, rl.reason, Count(*) AS Total
FROM UnionQuery AS uq
INNER JOIN reasonlookup AS rl ON uq.reason = rl.id
GROUP BY uq.week, rl.reason;
You can use a Union query to append individual Group By Aggregate queries for both Reason and Reason 2:
SELECT DatePart("ww", Calls.DateLogged) As WeekNum, ReasonLookup.Reason,
Sum(Calls.ID) As [Total]
FROM Calls
INNER JOIN Calls.Reason = ReasonLookup.ID
GROUP BY DatePart("ww", Calls.DateLogged) As WeekNum, ReasonLookup.Reason;
UNION
SELECT DatePart("ww", Calls.DateLogged) As WeekNum, ReasonLookup.Reason,
Sum(Calls.ID) As [Total]
FROM Calls
INNER JOIN Calls.[Reason 2] = ReasonLookup.ID
GROUP BY DatePart("ww", Calls.DateLogged) As WeekNum, ReasonLookup.Reason;
DatePart() outputs the specific date's week number in the calendar year. Also, UNION as opposed to UNION ALL prevents duplicate rows from appearing.

sql statement to calculate the average for a selected set of values

I am new to access and SQL statements. I have two tables, Site_ID and SE_WaterQuality_Data. For each site, several water quality parameters were collected over 5 weeks in summer and 5 weeks in winter. I want to be able to run a query that will return a table that shows the average of a particular parameter (eg Temp) grouped by the Site_ID and the sample period (eg summer 2013). I am close but my output table only shows the average value and not the site ID or sample period. The query also prompts the user to enter a particular Site_ID and I want it to run the query for all sites.
My SQL statement at the moment is
SELECT Avg(SE_WaterQuality_Data.[TEMP (C)]) AS [AvgOfTEMP (C)]
FROM SE_WaterQuality_Data
WHERE (((SE_WaterQuality_Data.EMS_ID)=[Site_ID].[EMS_ID]))
GROUP BY SE_WaterQuality_Data.EMS_ID, SE_WaterQuality_Data.SummaryPeriod;
And my output is
AveOFTEMP(C)
14.7
5.2
How can I change the SQL statement to 1) run the query for all sites and 2) return a table such as the one below:
Desired Output
Site_ID* SamplePeriod* AveTemp
1 Sum2013 14.2
1 Win2013 5.6
5 Sum2013 18.5
Help please......
If you want to run for all sites, take out the WHERE clause. And if you want to show other columns, include them in your SELECT clause.
SELECT [EMS_ID] AS [Site_ID],
[SummaryPeriod] AS [Sample_Period],
Avg(SE_WaterQuality_Data.[TEMP (C)]) AS [AvgOfTEMP (C)]
FROM SE_WaterQuality_Data
GROUP BY SE_WaterQuality_Data.EMS_ID, SE_WaterQuality_Data.SummaryPeriod;
I hope I got the syntax details right. I don't use SQL Server, I use MySQL. But the basic ideas are the same in all SQL dialects.
SELECT Site.Site_Id, WQ.SummaryPeriod, Avg(WQ.TEMP) AS AveTemp
FROM SE_WaterQuality_Data WQ, Site
WHERE WQ.EMS_ID = Site.EMS_ID
GROUP BY 1, 2
;

Add all column values that are equal to row value SSRS

I have a table of records each with its own creation date and closure date. I am trying to create a graph that will show the number of open and closed records on each day. So far, i've set up a binary matrix that will display a 1 if the record was open on a certain month and 0 otherwise. So, if i wanted to find the total on a certain week, i could just use a RunningValue to sum all the rows for a certain column. Unfortunately, i cannot seem to find a way to graph open and closed records on the same bar graph. So far, ive created a column in the query that has the number of the closed week. I assumed that i could just add these all up if they equal the current week but this doesnt seem to work. I used the following expression (the comparison is weird because i thought it might have something to do with comparing to values to each other) obviously this is me just testing :
'=CINT(Fields!Ident_Week.Value) & " / " & Fields!Close_Week.Value & " = " & SUM(IIF(CINT(Fields!Ident_Week.Value)/CINT(Fields!Close_Week.Value)=1,1,0))'
Im tempted now (embaressingly so) to just create 52 variables and assign the values that way. But i thought id ask here first. What do you think the best way is to find the closed records created on a certain week? Im using SSRS 2008 R2
a sample of my dataset is below (only relavent information is displayed)
Ident_Week Closed_Week Ident_Date Closed_Date Jan Feb .... Dec
1 3 1/1/13 1/15/13 1 0 0
I think you may be over complicating the dataset a little.
Try using UNPIVOT as below:
http://sqlfiddle.com/#!3/b6270c/6
You should be able to do what you need with this. Let me know if you need any further explanation.

Group by run when there is no run number in data (was Show how changing the length of a production run affects time-to-build)

It would seem that there is a much simpler way to state the problem. Please see Edit 2, following the sample table.
I have a number of different products on a production line. I have the date that each product entered production. Each product has two identifiers: item number and serial number I have the total number of labour hours for each product by item number and by serial number (i.e. I can tell you how many hours went into each object that was manufactured and what the average build time is for each kind of object).
I want to determine how (if) varying the length of production runs affects the average time it takes to build a product (item number). A production run is the sequential production of multiple serial numbers for a single item number. We have historical records going back several years with production runs varying in length from 1 to 30.
I think to achieve this, I need to be able to assign 'run id'. To me, that means building a query that sorts by start date and calculates a new unique value at each change in item number. If I knew how to do that, I could solve the rest of the problem on my own.
So that suggests a series of related questions:
Am I thinking about this the right way?
If I am on the right track, how do I generate those run id values? Calculate and store is an option, although I have a (misguided?) preference for direct queries. I know exactly how I would generate the run numbers in Excel, but I have a (misguided?) preference to do this in the database.
If I'm not on the right track, where might I find that track? :)
Edit:
Table structure (simplified) with sample data:
AutoID Item Serial StartDate Hours RunID (proposed calculation)
1 Legend 1234 2010-06-06 10 1
3 Legend 1235 2010-06-07 9 1
2 Legend 1237 2010-06-08 8 1
4 Apex 1236 2010-06-09 12 2
5 Apex 1240 2010-06-10 11 2
6 Legend 1239 2010-06-11 10 3
7 Legend 1238 2010-06-12 8 3
I have shown that start date, serial, and autoID are mutually unrelated. I have shown the expectation that labour goes down as the run length increases (but this is a 'fact' only via received wisdom, not data analysis). I have shown what I envision as the heart of the solution, that being a RunID that reflects sequential builds of a single item. I know that if I could get that runID, I could group by run to get counts, averages, totals, max, min, etc. In addition, I could do something like hours/ to get percentage change from the start of the run. At that point I could graph the trends associated with different run lengths either globally across all items or on a per item basis. (At least I think I could do all that. I might have to muck about a bit, but I think I could get it done.)
Edit 2: This problem would appear to be: how do I get the 'starting' member (earliest start date) of each run when I don't already have a runID? (The runID shown in the sample table does not exist and I was originally suggesting that being able to calculate runID was a potentially viable solution.)
AutoID Item
1 Legend
4 Apex
6 Legend
I'm assuming that having learned how to find the first member of each run that I would then be able to use what I've learned to find the last member of each run and then use those two results to get all other members of each run.
Edit 3: my version of a query that uses the AutoID of the first item in a run as the RunID for all units in a run. This was built entirely from samples and direction provided by Simon, who has the accepted answer. Using this as the basis for grouping by run, I can produce a variety of run statistics.
SELECT first_product_of_run.AutoID AS runID, run_sibling.AutoID AS itemID, run_sibling.Item, run_sibling.Serial, run_sibling.StartDate, run_sibling.Hours
FROM (SELECT first_of_run.AutoID, first_of_run.Item, first_of_run.Serial, first_of_run.StartDate, first_of_run.Hours
FROM dbo.production AS first_of_run LEFT OUTER JOIN
dbo.production AS earlier_in_run ON first_of_run.AutoID - 1 = earlier_in_run.AutoID AND
first_of_run.Item = earlier_in_run.Item
WHERE (earlier_in_run.AutoID IS NULL)) AS first_product_of_run LEFT OUTER JOIN
dbo.production AS run_sibling ON first_product_of_run.Item = run_sibling.Item AND first_product_of_run.AutoID run_sibling.AutoID AND
first_product_of_run.StartDate product_between.Item AND
first_product_of_run.StartDate
Could you describe your table structure some more? If the "date that each product entered production" is a full time stamp, or if there is a sequential identifier across products, you can write queries to identify the first and last products of a run. From that, you can assign IDs to or calculate the length of the runs.
Edit:
Once you've identified 1,4, and 6 as the start of a run, you can use this query to find the other IDs in the run:
select first_product_of_run.AutoID, run_sibling.AutoID
from first_product_of_run
left join production run_sibling on first_product_of_run.Item = run_sibling.Item
and first_product_of_run.AutoID <> run_sibling.AutoID
and first_product_of_run.StartDate < run_sibling.StartDate
left join production product_between on first_product_of_run.Item <> product_between.Item
and first_product_of_run.StartDate < product_between.StartDate
and product_between.StartDate < run_sibling.StartDate
where product_between.AutoID is null
first_product_of_run can be a temp table, table variable, or sub-query that you used to find the start of a run. The key is the where product_between.AutoID is null. That restricts the results to only pairs where no different items were produced between them.
Edit 2, here's how to get the first of each run:
select first_of_run.AutoID
from
(
select product.AutoID, product.Item, MAX(previous_product.StartDate) as PreviousDate
from production product
left join production previous_product on product.AutoID <> previous_product.AutoID
and product.StartDate > previous_product.StartDate
group by product.AutoID, product.Item
) first_of_run
left join production earlier_in_run
on first_of_run.PreviousDate = earlier_in_run.StartDate
and first_of_run.Item = earlier_in_run.Item
where earlier_in_run.AutoID is null
It's not pretty, and will break if StartDate is not unique. The query could be simplified by adding a sequential and unique identifier with no gaps. In fact, that step will probably be necessary if StartDate is not unique. Here's how it would look:
select first_of_run.AutoID
from production first_of_run
left join production earlier_in_run
on (first_of_run.Sequence - 1) = earlier_in_run.Sequence
and first_of_run.Item = earlier_in_run.Item
where earlier_in_run.AutoID is null
Using outer joins to find where things aren't still twists my brain, but it's a very powerful technique.