Pivot table with multiple Values by Date - sql

I was wondering if the solution to the ticket at the following link was ever found
Pivot on multiple fields and export from Access.
I am trying to get results from an access table in the following format where: V1 (value 1) is a number and V2 (value 2) is timestamp (hh:mm:ss).
Basically the report pulls for current month so eventually there would be a day at the top for every day of the month and a corresponding Number and time value for each day.
April 1 April 2 April 3
Manager V1 V2 V1 V2 V1 V2
John Doe 4 5:43:12 1 0:56:32 2 3:15:12
It is an elapsed time. I have been looking at Allen Browne's post and I am most of the way there I think. I created two tables: 1) for V1 which is a number of dials; and 2) for V2 which is total talk time. I then used Allen Browne's method but I cannot get the format of the time to be in hh:mm:ss. It is that way in the base table but whatever I try it always seems to just show 1 digit.
TRANSFORM Sum(IIf([FldName]="DIALS",Val([DIALS].[DIALS]),
Val(Format([T‌​ALKTIME].[TT],"hh:nn‌​:ss")))) AS TheValue
SELECT TALKTIME.Manager, DIALS.TW_Program_Code,
[DIALS]![VSE_FirstName] & " " & [DIALS]![VSE_Surname_Name] AS REP
FROM tblXtabColumns, TALKTIME
INNER JOIN DIALS
ON (TALKTIME.TW_Program_Code = DIALS.TW_Program_Code)
AND (TALKTIME.VSE_FirstName = DIALS.VSE_FirstName)
AND (TALKTIME.VSE_Surname_Name = DIALS.VSE_Surname_Name)
AND (TALKTIME.Period = DIALS.Period) AND (TALKTIME.[Manager] = DIALS.[Manager])
WHERE (((Month([TALKTIME].[Period])) = Month(Now())-1))
GROUP BY TALKTIME.Manager, DIALS.TW_Program_Code,
[DIALS]![VSE_FirstName] & " " & [DIALS]![VSE_Surname_Name]
PIVOT [DIALS].[Period] & " " & [FldName];

Is that time of day or elapsed time?
Review http://allenbrowne.com/ser-67.html#MultipleValues. Cannot have multi-line headers. The date would have to be concatenated with the field that provides the V1 and V2 header. Is there even a field that can be used to provide the "V1" and "V2" headers? If not, calculate it. One way:
SELECT *, DCount("*","Table1","Manager='" & [Manager] & "' AND ID <" & [ID])+1 AS Seq FROM Table1 WHERE Month([datefield]) = 4 AND Year([datefield])=2017;
Now use that query in CROSSTAB. Either build 2 CROSSTAB queries and join them or use Allen Browne approach. Example CROSSTAB to pivot only the "V1" data.
TRANSFORM First(Query1.Reading) AS DailyReading
SELECT Query1.Manager
FROM Query1
GROUP BY Query1.Manager
PIVOT Format([ReadDate],"mmm d") & " : " & [Seq] & "Reading";

Related

Condition for Update vs insert

I have a table tblCosts which i display on an msaccess front end which enables users to add new entries as well as update existing ones. The table is structured as below.
ExpenseType Month Year Cost
Hardware June 2017 $500
Software July 2017 $300
Hardware Sept 2017 $150
I have an update and insert queries which work fine when run manually.
However I am having trouble differentiating the condition when to fire the query on the form. For example, if the record exists in the table, it should run the update query, if record does not exist, it should run the insert query.
For example if someone puts in
- Hardware Sept 2017 $120 it should update the 3rd entry from 150 to 120 but if someone puts in
- Furniture Sept 2017 $350 it should recognize that Furniture is not part of the DB and run the insert query. I have the update and insert queries but need help in identifying the condition when to run them.
The Update query I'm using is:
Update tblCosts
set tblCosts.Cost=[Forms]![frmCost]![txtCost]
where tblCosts.ExpenseType = [Forms]![frmCost]![txtExpType]
and tblCosts.Month = [Forms]![frmCost]![txtMonth]
and tblCosts.Year = [Forms]![frmCost]![txtYear]
The Insert query I'm using is:
Insert into tblCosts (ExpenseType , Month, Year, Cost)
Select [Forms]![frmCost]![txtExpType] as Exp1,
[Forms]![frmCost]![txtMonth] as Exp2,
[Forms]![frmCost]![txtYear] as Exp 3,
[Forms]![frmCost]![txtCost] as Exp 4
Need code (VBA or macro) behind a form that determines which action query to run. In VBA something like:
If DCount("*", "tablename", "ExpenseType='" & Me.cbxExpense & "' AND [Month]='" & Me.tbxMonth & "' AND [Year]=" & Me.tbxYear) = 0 Then
CurrentDb.Execute "INSERT INTO tablename (Expense, [Month], [Year], Cost) VALUES ('" & Me.cbxExpense & "', '" & Me.tbxMonth & "', " & Me.tbxYear & ", " & Me.tbxCost & ")"
Else
CurrentDb.Execute "UPDATE tablename SET Cost=" & Me.tbxCost & " WHERE Expense='" & Me.cbxExpense & "' AND [Month]='" & Me.tbxMonth & ", [Year]=" & Me.tbxYear
End If
Probably also want some validation code to make sure all four controls have data before executing queries.
The real trick is figuring out what event to put code into - the Cost AfterUpdate will work as long as the other fields have data entered first, otherwise the validation will fail and user will have to re-enter cost.
Could have code that doesn't make each control available until previous value is entered.
Month and Year are reserved words and should not use reserved words as names for anything.
Would be better to save month numbers instead of month names for sorting purposes.
Why updating a value which really should be a calculated aggregation of transaction records?

Count instances of consecutive dates for associated name (VBA, SQL)

Good morning all,
I am trying to determine instances of consecutive dates (excluding Sunday) from a data set. The data is stored in Access and I am pulling the required dates into Excel. I am then trying to determine how many instances each person has in the data provided. Example below.
Data example:
| Name | Date of absence|
| Bob | 02/01/17 |
| Jill | 02/01/17 |
| Bob | 03/01/17 |
| Jill | 04/01/17 |
Result example:
Bob - 1 Instance, 2 days
Jill - 2 Instance, 2 days
I started trying to work through this with VBA in Excel using loops to rotate through each instance of absence until all people had been completed/ticked off, however the code was becoming really cumbersome and it felt very inefficient, not to mention how slow it was getting for larger data sets! I wonder if it is possible to query the database for the info or to write something a bit more efficient.
Any help or suggestions would be appreciated!
Update:
Testing Tom's suggestion;
Sql = "SELECT Absence.Racf,count(RecordDate) as dups"
Sql = Sql & " FROM Absence"
Sql = Sql & " left outer join"
Sql = Sql & " (select Racf, [RecordDate]+IIf(Weekday([RecordDate],7)=1,2,1) as date1 from Absence) t1"
Sql = Sql & " on Absence.RecordDate=t1.date1 and Absence.Racf=t1.Racf"
Sql = Sql & " where date1 Is Not Null"
Sql = Sql & " group by Absence.Racf"
But unfortunately on the list of dates below it returns 7, instead of 5.
Dates:
23-Feb-16,24-Feb-16,08-Aug-16,09-Aug-16,10-Aug-16,31-Aug-16,24-Oct-16,25-Oct-16,26-Oct-16,25-Jan-17,26-Jan-17,27-Jan-17
So this is how the SQL might actually look in an Access query
SELECT table1.name,count(date) as dups
FROM Table1
left outer join
(select name, [date]+IIf(Weekday([Date],7)=1,2,1) as date1 from table1) t1
on table1.date=t1.date1 and table1.name=t1.name
where date1 is not null
group by table1.name
;
If you want to run this from Excel using a macro, here is a useful reference.
I lifted the code from there and changed the lines which set up the SQL query string to
SQL = "SELECT table1.name,count(date) as dups"
SQL = SQL & " FROM table1"
SQL = SQL & " left outer join"
SQL = SQL & " (select name, [date]+IIf(Weekday([Date],7)=1,2,1) as date1 from table1) t1"
SQL = SQL & " on table1.date=t1.date1 and table1.name=t1.name"
SQL = SQL & " where date1 Is Not Null"
SQL = SQL & " group by table1.name"
and it worked fine.
Try this if you want to get sequences with length greater than one
SELECT Absence.Racf, Count(Absence.RecordDate) AS CountOfRecordDate
FROM (Absence LEFT JOIN (select Racf, RecordDate+IIf(Weekday([RecordDate],7)=1,2,1) as RecordDate1 from Absence) AS t1 ON (Absence.RecordDate = t1.RecordDate1) AND (Absence.Racf = t1.Racf))
LEFT JOIN (select Racf, [RecordDate]-IIf(Weekday([RecordDate],2)=1,2,1) as RecordDate2 from Absence) AS t2 ON (Absence.RecordDate = t2.RecordDate2) AND (Absence.Racf = t2.Racf)
WHERE (((t1.RecordDate1) Is Not Null) AND ((t2.RecordDate2) Is Null))
GROUP BY Absence.Racf;
Or this if you want to get sequences of one or more consecutive dates
SELECT Absence.Racf, Count(Absence.RecordDate) AS CountOfRecordDate
FROM Absence LEFT JOIN (select Racf, [RecordDate]+IIf(Weekday([RecordDate],7)=1,2,1) as RecordDate2 from Absence) AS t2 ON (Absence.RecordDate = t2.RecordDate2) AND (Absence.Racf = t2.Racf)
WHERE (((t2.RecordDate2) Is Null))
GROUP BY Absence.Racf;
adding to the SQL string as before.
This can be done using array formula in Excel. In D I have =INDEX($A2:$A$15,MATCH(0,COUNTIF($D$1:$D1,$A2:$A$15),0)) to get the unique employees, then in E I have the following to count the instances =SUM(--(($A$1:$A$15=D1)*(OFFSET($A$1:$A$15,1,0)=D1)*(OFFSET($B$1:$B$15,1,0)-$B$1:$B$15)=1)) which gives the result something like this. You'll need to add another criteria, based on weekday (I will adjust a little later as running low on time) This relies on the data being in date order
EDIT : I understand this is not the full answer and will require modification, a starting point :o)
Covering the Sunday absence (will still need weekday check):
=D1 & " " & COUNTIF($A$1:$A$15,D1) &" instances " & SUM(--(--($A$1:$A$15=D1)*--(OFFSET($A$1:$A$15,1,0)=D1))*--(--(OFFSET($B$1:$B$15,1,0)-$B$1:$B$15=1)+--(OFFSET($B$1:$B$15,1,0)-$B$1:$B$15=2)))&" Consecutive"
Checking the weekday also
=D2 & " " & COUNTIF($A$1:$A$15,D2) &" instances " & SUM(--(--($A$1:$A$15=D2)*--(OFFSET($A$1:$A$15,1,0)=D2))*--(--(OFFSET($B$1:$B$15,1,0)-$B$1:$B$15=1)+--(WEEKDAY(OFFSET($B$1:$B$15,1,0),2)=1)*((OFFSET($B$1:$B$15,1,0)-$B$1:$B$15=2)))) & " Consecutive"
A SQL approach would be something along the lines of, based on a table 000Absence, which is the data from examples EEName and AbsDate.
SELECT abs1.EEName, abs1.AbsDate,
(select count(abs2.EEName) from 000Absence as abs2 where abs2.[EEName]=abs1.[EEName]) AS INSTANCES,
(select count(abs3.EEName) from 000Absence as abs3 where abs3.[EEName]=abs1.[EEName] and abs3.[AbsDate]=abs1.[AbsDate]+iif(weekday(abs3.[AbsDate],7)=1,2,1)) AS CONSECUTIVE
FROM 000Absence AS abs1;
Where the output can be got from the query, grouping by Employee etc.

Select a maximum value within a date range

Task:
Append/edit the currently working code below to return only one row per patient, the maximum value of d1_10.xtransfer (datatype int) with the restriction that this row's d1_10.dstartdate <= glob_End_Date.
Caveats:
There are similar questions on StackOverflow and its sister sites. None that I have found have successfully helped with a resolution to this issue.
This is a medical EHR database, I can share code, but any discussion of results has to be general and exclude patient information.
I am replacing the SQL query within a pre-existing Excel spreadsheet to do something different. Excel pulls information from our database with an ODBC connection. Our database is using Ingres SQL which accepts most, but not all, of your typical SQL code varieties. It's possible that a piece of code will generally work in other flavors of SQL but not with the combo of Ingres and Excel. I've got the spreadsheet working and returning results, now it's about making some fixes by writing SQL code that works in this software.
Thus far:
With the currently working code below (no maximum d1_10.xtransfer restrictions) we return all rows with d1_10.dstartdate in the user selected date range and with the user selected d1_10.xinstitute. We want just the latest one. That is, the patient's row with either the maximum d1_10.dstartdate within the date range, or the maximum d1_10.xtransfer (index that counts up as they are added) within the date range.
Currently working code:
"SELECT " & _
"d1.xpid ""XPID"", " & _
"d0_v1.name_family ""NAME_FAMILY"", " & _
"d0_v1.name_given1 ""NAME_GIVEN1"", " & _
"d0_v1.name_given2 ""NAME_GIVEN2"", " & _
"d1.sex ""SEX"", " & _
"d1.birthdate ""DOB"", " & _
"d0_v1.hsp_pid, " & _
"c58.brief_name, " & _
"c73.cname, " & _
"date_trunc('day',d1_10.dstartdate) ""DSTARTDATE"", " & _
"date_trunc('day',d1_17.ddeath) ""DDEATH"" " & _
"FROM d1 " & _
"JOIN d0_v1 ON d1.xpid = d0_v1.xpid " & _
"JOIN d1_2 ON d1.xpid = d1_2.xpid " & _
"JOIN c58 ON d1_2.xmodality = c58.xcmodality " & _
"JOIN d1_10 ON d1.xpid = d1_10.xpid " & _
"JOIN c73 ON d1_10.xinstitute = c73.xcsite " & _
"JOIN d1_17 ON d1.xpid = d1_17.xpid " & _
"WHERE " & _
"d1_10.xinstitute = " & institute_index & " AND " & _
"d1_10.dstartdate >= '" & glob_Start_Date & " 00:00:00' and " & _
"d1_10.dstartdate <= '" & glob_End_Date & " 23:59:59' "
The closest I have gotten with code that runs from the excel spreadsheet is with this additional line in the WHERE clause:
d1_10.xtransfer = (SELECT MAX(d1_10.xtransfer) FROM d1_10 GROUP BY xpid)
With this additional line we now return only one row from each patient that has a d1_10.xtransfer within the date range. But if they have a row where d1_10.xtransfer is more recent than the date range, then they don't show up in the results at all.
With this line the code is taking MAX(d1_10.xtransfer) for each xpid before it applies the date restriction. By my logic we want it to do so after instead, but I have been unable to come up with code that runs that gets it any closer than this.
Thanks in advance. I'll keep this question updated with additional info below this page break.
Additional Info:
Per PaulM:
Yes, xpid is a patient ID index number, unique to each patient.
Added/edited line in WHERE clause to: "d1_10.xtransfer = (SELECT MAX(xtransfer) FROM d1_10 d1_10_b WHERE d1_10.xpid = d1_10_b.xpid AND d1_10_b.dstartdate <= '" & glob_End_Date & " 23:59:59') "
Patient Bob has transfers on both the 14th and 17th of June that fit the rest of the criteria.
When inputting a date range with an end date of Jun 17+, the spreadsheet correctly returns a row for Bob with his Jun 17 transfer.
When inputting a date range with an end date of Jun 14,15 or 16, the spreadsheet incorrectly does not return a row for Bob.
It seems as though it still takes the maximum xtransfer before restricting by date.
Per PaulM's comment:
I ran the subselect for a specific patient as follows:
Input:
SELECT MAX(xtransfer) FROM d1_10 d1_10_b WHERE d1_10_b.xpid = '2258' AND d1_10_b.dstartdate <= '20-apr-2016 23:59:59'
It outputted a value of MAX(xtransfer) = '48233'. This is correct.
So, when run in Visual SQL as its own statement, setting d1_10_b.xpid equal to a specific patient, it correctly pulls the maximum xtransfer from the date range. (There was a more recent xtransfer outside of the date range, and it still correctly displayed the maximum xtransfer from within the date range.)
I then tried running this exact same subselect in the where close for the spreadsheet. That is, I manually selected the same date range (which is being passed through as a variable correctly and successfully) but I subbed out d1_10.xpid = d1_10_b.xpid for d1_10_b.xpid = '2258'. This did not work. The spreadsheet did not show a row for this patient, seemingly because it still applies the MAX() function before it restricts by the date range in the subquery. And yet, the subquery works when run by itself.
Much appreciation for any further suggestions.
You need to add the date restriction in the subselect as well as the main query. Also I suspect the group by is wrong. By adding a group by you're making the subselect a list of patients xtransfer values with the largest value for each xpid (identifies a patient?). However that means if the row you're interested in from the main query happens to have an xtransfer value that matches the largest one belonging to a different xpid you're getting a false match.
What you really need is to add a join on xpid from the subselect back up to the main query. To do that you'll need a different correlation name e.g.
d1_10.xtransfer = (SELECT MAX(xtransfer)
FROM d1_10 d1_10_b
WHERE d1_10.xpid = d1_10_b.xpid
AND d1_10_b.dstartdate > = ... {as above} )

MS Access/Access-SQL: Switch Function to Calculate Distances based on Criteria

I am somewhat new to MS Access/SQL and StackOverflow, so bear with me. I have a problem that I can't seem to figure out. I had posted this before, but got no responses, and editing my original post did not help.
I have quantity and distance data for assets for each week. Not all weeks have Quantity or distances (some just have 1 or the other). Here is a sample of the data for one asset(put in CSV):
Asset,Week,Qty,Dist,Actual_Dist
2153,1,,125,
2153,2,,65,
2153,3,50.1,118,
2153,4,,123,
2153,5,96.6,91,
2153,6,,103,
2153,7,,120,
2153,8,,106,
2153,9,100.6,,
2153,13,96,,
2153,14,,102,
2153,15,,40,
2153,18,84.82,,
2153,21,97.8,,
2153,25,96.7,,
2153,28,31.27,63,
2153,29,77.5,,
What I want to be able to do, is take a SUM of the "DIST" field until the row has a corresponding "QTY" field value, and this would be calculated in the "Actual_Dist" field.
Looking at my example, Weeks 1 and 2 have no Qty values, however in Week 3, Qty is 50.1 and I would want the "Actual_Dist" calculated as the sum of "Dist" from Weeks 1-3 inclusive. So essentially, Row 3's "Actual_Dist" would be SUM(125+65+118).
Right now I see 3 cases:
Case 1: as above, if no Qty, but has a Dist, then sum the distances until the next Qty value.
Case 2: If Qty exists, but no Dist, then disregard
Case 3: If Qty has a value, and Dist has a value, and Qty has previous values before (ie., Week 28), then "Actual_Dist" = Dist
So I was thinking of doing a select switch to cover the two main cases (1 & 3):
Select Asset, Week, Qty, Dist, Switch (Qty like 'NULL' AND Dist <> 'NULL', SUM(Dist) AS Actual_Dist, Switch (Qty <> 'NULL AND Dist <> 'NULL', DIST) AS Actual_Dist
**Not sure if my Switch is done right, but I think you get the picture?
Now my issues comes in the sum function above. How do I get it sum properly and take the distance values before a qty value is present (Case 2)? I apologize if the formatting or presentation of sample data is poor. Just signed up. I will likely have to clarify some points, so let me know and I will clarify as necessary.
It is also important to note that this is just one asset, and there are many. For the sum function above, I need it to be able to sum the records above for ANY given number of records.
Hope someone can help.
Thanks
EDIT: #Cha had posted the following:
SELECT Data.Asset, Data.Week, Data.Qty, Data.Dist, Switch(Not IsNull([Qty]) And Not IsNull([Dist]),[Dist], Not IsNull([Qty]),Nz(DSum("Dist","Data","Asset=" & CStr([Asset]) & " And Week <= " & CStr([Week]) & " And Week > " & CStr(Nz(DMin("Week","Data","Asset=" & CStr([Asset]) & " And Week < " & CStr([Week]) & " And Not IsNULL(Qty)"),0))),0)) AS Actual_Dist FROM Data;
This code gave me errors due to data mismatch, so I changed all the data types to "Number" and modified the code as follows:
SELECT Data.Asset, Data.Week, Data.Qty, Data.Dist, Switch(Not IsNull([Qty]),Nz(DSum("Dist","[Data]","Asset=" & [Asset] & " And Week <= " & [Week] & " And Week > " & Nz(DMin("Week","[Data]","Asset=" & [Asset] & " And Week < " & [Week] & " And Not IsNULL([Qty])"),0)),0)) AS Actual_Dist
FROM Data;
The above code now satisfies Case 1, but only satisfies it for Row 3 and 5. This Case does not satisfy Rows 9 and 13, and it needs to apply there too. I believe the issue with those rows is that the "Dist" is NULL.
There is another issue, Case 1 and Case 3 overwrite eachother occasionally (when both Qty and Dist are not NULL. Is there a way to create 1 switch to run Case 1, and another (with the same code) to apply Case 3 but not Case 1?
Any help would be much appreciated!
Thanks
If you can use VBA, then try this:
Public Sub GetActual_Dist()
Const TABLE_NAME As String = "tblAssets"
Dim sTemp_Dist As Single
With CurrentDb.OpenRecordset("SELECT * FROM [tblAssets] WHERE NOT ([Qty] is not Null AND [Dist] is Null);")
If .EOF And .BOF Then
MsgBox "No data"
Exit Sub
End If
Do Until .EOF
If ![Qty] Is Null Then
sTemp_Dist = sTemp_Dist + ![Dist]
Else
.Edit
![Actual_Dist] = sTemp_Dist
.Update
sTemp_Dist = 0
End If
.MoveNext
Loop
End With
End Sub
Change the TABLE_NAME to the one in your database.

Calculations using multiple fields in SQL?

How would I calculate the highest goals per minute if I have a table that consists of fields for Goals and Minutes Played? At the moment I have it so the highest stat (whichever the user chooses) is put into a Label along with the forename and surname of the that particular record. I'd like to do the same thing but now using calculation which uses multiple fields. How would I do this? (Below is how I'm doing the bit I've already done)
ElseIf cb_Stat.Text = "Most Assists" Then
SQL = "SELECT TOP 1 * FROM PlayerDatabase ORDER BY Assists DESC"
Dim da As New OleDbDataAdapter(SQL, Connection)
da.Fill(ds, "PlayerDatabase")
lbl_Result.Text = cb_Stat.Text & ": " & ds.Tables("PlayerDatabase").Rows(0).Item(2) & " " & ds.Tables("PlayerDatabase").Rows(0).Item(1) & " (" & ds.Tables("PlayerDatabase").Rows(0).Item(7) & ")"
Two ways of doing this:
select goals, minutes, round(goals/minutes,2) gpm --edit last digit for number of decimal places
from PlayerDB
order by goals/minutes
or this. It's a little bit longer, but cleaner, and can be incorporated into a view
select * from (
select goals, minutes, round(goals/minutes,2) gpm --edit last digit for number of decimal places
from PlayerDB
) order by gpm