Access - create a sub total in a query - sql

I have a query over a few tables and get a result in the form of:
SomeId Input
1 2
1 5
2 3
2 1
1 2
I'd like to be able to sum by Id as a third field, so I would get
SomeId Input subTotal
1 2 2
1 5 7
2 3 3
2 1 4
1 2 9
Is it possible?
Thanks

Here's a couple other ideas. Both have their drawbacks though. Both of them involve using a regular query.
First idea: Calling a VBA function to keep track of totals.
The drawback is that you have to order your table by SomeID.
Also the running total only resets itself when the function gets a different SomeID even if it's a different query. This means that the value of SomeID on the first record must be different than the last record of the last previous query.
SELECT SomeTable.SomeId, SomeTable.SomeInput, MyRunningTotal([SomeID],[SomeInput]) AS SubTotal
FROM SomeTable
ORDER BY SomeTable.SomeId;
Function MyRunningTotal(SomeID As Long, SomeInput As Long) As Long
Static LastSomeID As Long
Static RunningTotal As Long
If SomeID <> LastSomeID Then
RunningTotal = 0
LastSomeID = SomeID
End If
RunningTotal = RunningTotal + SomeInput
MyRunningTotal = RunningTotal
End Function
Second Idea: Using DSum. This is basically a query within a query.
The drawback is that for large recordsets it can be very slow. This is because it has to run a separate query for every record.
Also, you have to add an Auto-increment field (in the sample code below it's ID).
SELECT SomeTable.ID, SomeTable.SomeId, SomeTable.SomeInput,
DSum("SomeInput","SomeTable","[SomeID]=" & [SomeID] & " and [ID]<=" & [ID]) AS SubTotal
FROM SomeTable;

Yes, it's certainly possible, but with the problem as stated it cannot be accomplished by using just Access SQL queries. The two issues are:
The source data has no sequential key value (so a self-join with a <= condition cannot be used)
The source data is not sorted by SomeId (suggesting that the order may have some other significance), which would further complicate a set-based approach.
Fortunately, the VBA required to do this is not too involved:
Sub CreateSubtotals()
Dim cdb As DAO.Database, rst As DAO.Recordset
Dim dct As Object '' Dictionary
Set dct = CreateObject("Scripting.Dictionary") '' New Dictionary
Set cdb = CurrentDb
'' 'dct' will hold the running totals for each 'SomeId'
Set rst = cdb.OpenRecordset( _
"SELECT DISTINCT SomeId " & _
"FROM qryYourOriginalQuery", _
dbOpenSnapshot)
Do While Not rst.EOF
dct.Add rst!SomeId.Value, 0
rst.MoveNext
Loop
rst.Close
'' create new table to hold results
cdb.Execute _
"SELECT SomeId, Input, 0 AS subTotal " & _
"INTO tblYourDataWithSubtotals " & _
"FROM qryYourOriginalQuery", _
dbFailOnError
'' fill in the 'subTotal' column
Set rst = cdb.OpenRecordset("tblYourDataWithSubtotals", dbOpenTable)
Do While Not rst.EOF
dct(rst!SomeId.Value) = dct(rst!SomeId.Value) + rst!Input.Value
rst.Edit
rst!subTotal.Value = dct(rst!SomeId.Value)
rst.Update
rst.MoveNext
Loop
rst.Close
Set rst = Nothing
Set dct = Nothing
Set cdb = Nothing
End Sub

Related

Split multi-value field into different rows using SQL in Microsoft Access

I’ve got a simple table in Microsoft Access that looks like this:
Primary Key
Applications List
123
<Value>|<Value>,<Value>|<Value>
456
<Value>|<Value>,<Value>|<Value>
I need to break out the list of applications into separate rows using the “,” as a delimiter so the end result is a table that looks like this:
Primary Key
Applications List
123
<Value>|<Value>
123
<Value>|<Value>
456
<Value>|<Value>
456
<Value>|<Value>
I’ve tried using the Split function but can’t figure out how to split on the “,” and output the results to a different row like the second table above. I would greatly appreciate your help figuring this one out. Thanks so much!
If you can put a limit on the length of Application List you can build a number table
CREATE TABLE NumTable (ID integer)
up-front with as many rows as there are characters in the longest Application List. Assuming that the longest [Application List] is 1000 characters long, ID=1, ID=2, ..., ID=1000), and then use something like this:
select T.[Primary Key],
mid(T.AL,NT.ID+1
, iif(instr(NT.ID+1,t.AL,',')>0
, instr(NT.ID+1,t.AL,',')-1-nt.ID,1000)) as
from (select [Primary Key], ',' & [Application List] as AL from tblYourTable) as T
inner join
NumTable as NT
on mid(t.AL,NT.ID,1)=','
You can build the number table in EXCEL and paste it; or write a little VBA routine, or even create it dynamically (*).
I can't imagine it performing very well if the volume is high.
Let us know how you proceed!
(*)Generate numbers 1 to 1000000 in MS Access using SQL
If you setup a Table1 and a Table2 with the same field names, you can run this function to do it for you - it basically loops through Table1 records, splits the Applications List into multiple fields and then inserts new records for each field.
Public Sub SplitDataIntoNewTable()
Const DATA_SEPARATOR As String = ","
Dim qdf As DAO.QueryDef
Dim rsOld As DAO.Recordset
Dim rsNew As DAO.Recordset
Dim i As Integer
Dim lngNumAdded As Long
Dim lngKey As Long
Dim strList As String
Dim strNewList As String
Dim varLists As Variant
Set rsOld = CurrentDb.OpenRecordset("Table1", dbOpenDynaset, dbReadOnly)
Set rsNew = CurrentDb.OpenRecordset("Table2", dbOpenDynaset)
With rsOld
While Not .EOF
lngKey = ![Primary Key]
strList = ![Applications List]
varLists = Split(strList, DATA_SEPARATOR)
With rsNew
For i = LBound(varLists) To UBound(varLists)
' Add new record for every list split out of old data
.AddNew
' Note that this CANNOT actually be defined as a PRIMARY KEY - it will have duplicates
![Primary Key] = lngKey
![Applications List] = varLists(i)
lngNumAdded = lngNumAdded + 1
.Update
Next i
End With
.MoveNext
Wend
rsNew.Close
.Close
End With
MsgBox "Added " & lngNumAdded & " New Records"
Set rsOld = Nothing
Set rsNew = Nothing
End Sub
For example I had Table1 look like this:
And resulting Table2 ended up like this

VBA Iteration vs SQL Speed in Access

Goal: I have a bunch of dates, I want to update the records of the minimum date by category:
JVID APDATE TAG into > JVID APDATE TAG
1 201501 Use 1 201501 Don't Use
1 201502 Use 1 201502 Use
1 201502 Use 1 201502 Use
1 201503 Use 1 201503 Use
2 201502 Use 2 201502 Don't Use
2 201503 Use 2 201503 Use
The method I'm using is as follows:
I create a dictionary where the Key = ID, and Value = MinDateByID
Then I loop thorough the keys (for each key in dictionary) and run an update query for each ID that checks an IIF statement updating Use/Don't Use based on the date matching the min date.
This works, but w/ +80k IDs covering +1M records it takes forever.
I'm considering running the same thing, but dumping SQL and just iterating through the records, but I can't imagine that'd be faster?
I'm looking for SQL or VBA suggestions.
Thank you in advance!
EDIT - Added SQL From Comments
UPDATE [FY16 Q12 BE] SET [FY16 Q12 BE].[Record Use] = IIF([FY16 Q12 BE].[Date] = "201601", "Use", "Don't Use") WHERE ([FY16 Q12 BE].[ID]="20165645699");
I look through each of the dictionary key/value pairs ex (20165645699, 201601)
creating and running this script in various forms 80k+ times
MS Access is more restrictive than mainstream databases in joined updates, so I had to use a temporary table T2 to hold the minimum values.
SELECT T1.ID, MIN(T1.RDate) AS MinDate INTO T2
FROM Test1
GROUP BY T1.ID;
Now I can perform the joined update:
UPDATE T1 LEFT JOIN T2
ON T1.ID=T2.ID AND T1.RDate=T2.MinDate;
Finally, I drop the temporary table :
DROP T2;
SET TAG = IIF(T2.ID IS NULL, "Don't Use", "Use");
[I have named your table T1 and the date field RDate to avoid a conflict with a reserved word.]
This could be speeded up further by adding a primary key to T2 on (ID, MinDate) and an index on T1 on (ID, RDate).
I think you can do it with one update query - or at least a combination of multiple queries in one SQL statement.
I'm going to use your example data, since I can't figure out what your actual table or field names are in your comments.
You need to replace Table4 with your table name - and ID/Date/Tag fields to match your column names.
UPDATE SQL:
UPDATE Table4 SET Table4.Tag = "Don't Use"
WHERE ([Date] & "-" & [ID])
In (SELECT MergeID FROM
(SELECT Mins.[MinOfDate] & "-" & [ID] AS MergeID
FROM
(SELECT Table4.ID, Min(Table4.Date) AS MinOfDate
FROM Table4
WHERE Table4.Tag="Use"
GROUP BY Table4.ID) AS Mins) AS Merges);
If you don't need the criteria to only check TAGs that haven't been changed then you can eliminate the criteria WHERE Table4.Tag="Use"
OTHER 'No Tag Check' OPTION
UPDATE SQL:
UPDATE Table4 SET Table4.Tag = "Don't Use"
WHERE ([Date] & "-" & [ID])
In (SELECT MergeID FROM
(SELECT Mins.[MinOfDate] & "-" & [ID] AS MergeID
FROM
(SELECT Table4.ID, Min(Table4.Date) AS MinOfDate
FROM Table4
GROUP BY Table4.ID) AS Mins) AS Merges);
I want to suggest this. But I see you have duplicate dates and I'm not sure how you intend to handle these when you have ties for the earliest date.
update [FY16 Q12 BE]
set TAG = "Don't use"
where not exists (
select 1 from [FY16 Q12 BE] as t2
where t2.ID = [FY16 Q12 BE].ID and t2.[DATE] < [FY16 Q12 BE].[DATE]
)
I settled on an iterative approach - I'm not sure why it's so much faster than the SQL options outline above, but it does the trick. Thank you for your feedback.
Sub MinAPInclude(ByVal Tablename As String)
Dim db As DAO.Database
Dim qd As DAO.QueryDef
Dim rs As DAO.Recordset
Dim strList As String
Dim JVMinAP As Dictionary
Set JVMinAP = New Dictionary
Set db = DBEngine(0)(0)
Dim rst As DAO.Recordset
If Not DoesFieldExist(Tablename, "APDate") Then Exit Sub
SQLStatement = "SELECT [" & Tablename & "].[JVID], Min([" & Tablename & "].[APDate]) AS TargetAP"
SQLStatement = SQLStatement & " FROM [" & Tablename & "]"
SQLStatement = SQLStatement & " GROUP BY [" & Tablename & "].[JVID];"
Set qd = db.CreateQueryDef("", SQLStatement)
Set rs = qd.OpenRecordset
rs.MoveFirst
Do Until rs.EOF
If Not IsNull(rs("JVID")) Then
If Not JVMinAP.Exists(CStr(rs("JVID"))) Then
MinAP = rs("TargetAP")
JVMinAP.Add CStr(rs("JVID")), MinAP
End If
End If
rs.MoveNext
Loop
rs.Close
Set rst = db.OpenRecordset(Tablename)
rst.MoveFirst
Do Until rst.EOF
If rst("Record Use") <> "Include" Then
If rst("APDate") = JVMinAP(CStr(rst("JVID"))) Then
rst.Edit
rst("Record Use") = "Include"
rst.Update
End If
End If
rst.MoveNext
Loop
rst.Close
Set rst = nothing
Set rs = Nothing
Set qd = Nothing
Set db = Nothing
End Sub

For Each loop for multiple customer id's within MS Access VBA

I have an existing table which hold 1000's of records. I need to update each record depending on the customer id and a date field associated with it.
Basically so i can put an ordered number beside each date depending on the order of the dates.
I think I need to use two 'for each' loops to get this done. I.E.
For Each Customer ID in tblCustomers
'gather all records for that customer and get all dates in order from each record via recordset?
For Each Date
newfield = newfield+ 1
end loop
end loop
Could anyone point me in the right direction to figure this out?
Thanks
Something like the following:
Dim rstCustomers As DAO.Recordset
Set rstCustomers = CurrentDb.OpenRecordset("SELECT CustomerID FROM tblCustomers GROUP BY CustomerID")
If rstCustomers.RecordCount > 0 Then
rstCustomers.MoveFirst
Do Until rstCustomers.EOF
Dim rstRecords As DAO.Recordset
Set rstRecords = CurrentDb.OpenRecordset("SELECT RecordDate, OrderField FROM tblRecords WHERE CustomerID = " & rstCustomers!CustomerID & " ORDER BY RecordDate")
If rstRecords.RecordCount > 0 Then
Dim iCount as Integer
iCount = 1
rstRecords.MoveFirst
Do Until rstRecords.EOF
rstRecords.Edit
rstRecords!OrderField = iCount
rstRecords.Update
iCount = iCount + 1
rstRecords.MoveNext
Loop
End If
rstRecords.Close
Set rstRecords = Nothing
rstCustomers.MoveNext
Loop
End If
rstCustomers.Close
Set rstCustomers = Nothing
Loop records with using Recordset

Find Previous and next value in access using SQL

I am using a Microsoft Access 2010 database to import values from one table and append them to a summary table.
One of the issues I am having is finding the previous and next value from the select statement.
This would look as follows.
JOINT JOINT AHEAD JOINT BEHIND
100103 200203
200203 300303 100103
300303 200203
I would like to create this using a SQL code
Be cautious when considering correlated subqueries. They can be very slow. And if you build a query which includes two correlated subqueries, you will magnify the problem.
If your source table contains a smallish number of rows (say a few dozen), the slowness may not be an issue. However, if the table includes a thousand rows you will most certainly notice it. And if your JOINT field is not indexed, the performance could be painfully slow.
If you will be running your query from within an Access session, you can use domain functions (DMin and DMax) instead of correlated subqueries. Domain functions are often criticized as slow. However, in this situation they can be dramatically faster than correlated subqueries.
Correction: You don't need to run your query from within an Access session for it to be able to use the DMin() and DMax() functions. I attached a VBScript example which opens an ADO Recordset based on my qryDomainFunctions. It works without error and correctly reports RecordCount: 1000
I created a table, joints, with a long integer field joint as primary key and added 1000 rows. Then I created these 2 queries:
qryCorrelatedSubqueries:
SELECT
a.joint,
(SELECT TOP 1 joint
FROM joints b
WHERE b.joint>a.joint
ORDER BY joint) AS Ahead,
(SELECT TOP 1 joint
FROM joints b
WHERE b.joint<a.joint
ORDER BY joint DESC) AS Behind
FROM joints AS a;
qryDomainFunctions:
SELECT
j.joint,
DMin("joint","joints","joint > " & [joint]) AS joint_ahead,
DMax("joint","joints","joint < " & [joint]) AS joint_behind
FROM joints AS j;
Here is a transcript from the Immediate window where I compared the speed of those 2 queries, using the QueryDuration function below. That function returns duration in milliseconds.
? QueryDuration("qryDomainFunctions")
0
? QueryDuration("qryCorrelatedSubqueries")
889
Note that both those queries benefit from the index on the joints field. When I dropped the index, compacted the db, and re-ran the tests I got these results:
? QueryDuration("qryDomainFunctions")
16
? QueryDuration("qryCorrelatedSubqueries")
4570
This is the module with the code I used. QueryDuration is by no means the last word on performance measurement. However it's good enough to give us a rough idea of the relative speeds of those 2 queries.
Option Compare Database
Option Explicit
Private Declare Function apiGetTickCount Lib "kernel32" _
Alias "GetTickCount" () As Long
Public Function QueryDuration(ByVal pQueryName As String) As Long
Dim db As DAO.Database
Dim lngStart As Long
Dim lngDone As Long
Dim rs As DAO.Recordset
Set db = CurrentDb()
lngStart = apiGetTickCount() ' milliseconds '
Set rs = db.OpenRecordset(pQueryName, dbOpenSnapshot)
If Not rs.EOF Then
rs.MoveLast
End If
lngDone = apiGetTickCount()
rs.Close
Set rs = Nothing
Set db = Nothing
QueryDuration = lngDone - lngStart
End Function
DomainFunctionsQuery.vbs:
Option Explicit
Dim cn, rs
Set cn = CreateObject("ADODB.Connection")
cn.Open "Provider=Microsoft.Jet.OLEDB.4.0;" & _
"Data Source='database1.mdb'"
Set rs = CreateObject("ADODB.Recordset")
rs.CursorLocation = 3 ' adUseClient '
rs.Open "qryDomainFunctions", cn, 3 ' adOpenStatic = 3 '
WScript.Echo "RecordCount: " & rs.RecordCount
rs.Close
Set rs = Nothing
cn.Close
Set cn = Nothing
How about:
SELECT a.JOINT,
(SELECT TOP 1 Joint
FROM Joint b
WHERE b.JOINT>a.JOINT
ORDER BY Joint) AS Ahead,
(SELECT TOP 1 Joint
FROM Joint b
WHERE b.JOINT<a.JOINT
ORDER BY Joint DESC) AS Behind
FROM Joint AS a;

Query creating an identifier for each packet

Sorry the title is not very descriptive but it is a tricky problem to word.
I have some data, about 200 or more rows of it, and each row has a PacketID, so several rows belong in the same packet. What I need to do, is convert all the PacketIDs from (Example - BDFD-2) to just a number (Example - 1) so all the entries with a packet identifier x need to have a packet identifier of say 3. Is there an SQL query that can do this? Or do I just have to go through manually.
You asked about a query. I wrote a quick VBA procedure instead just because it was so easy. But I'm unsure whether it is appropriate for your situation.
I created tblPackets with a numeric column for new_PacketID. I hoped that will make it clearer to see what's going on. If you truly need to replace PacketID with the new number, you can alter the procedure to store CStr(lngPacketID) to that text field. So this is the sample data I started with:
PacketID new_PacketID packet_data
BDFD-2 a
R2D2-22 aa
BDFD-2 b
R2D2-22 bb
EMC2-0 aaa
EMC2-0 bbb
And this is the table after running the procedure.
PacketID new_PacketID packet_data
BDFD-2 1 a
R2D2-22 3 aa
BDFD-2 1 b
R2D2-22 3 bb
EMC2-0 2 aaa
EMC2-0 2 bbb
And the code ...
Public Sub RenumberPacketIDs()
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim lngPacketID As Long
Dim strLastPacketID As String
Dim strSql As String
strSql = "SELECT PacketID, new_PacketID" & vbCrLf & _
"FROM tblPackets" & vbCrLf & _
"ORDER BY PacketID;"
Set db = CurrentDb
Set rs = db.OpenRecordset(strSql)
With rs
Do While Not .EOF
If !PacketID <> strLastPacketID Then
lngPacketID = lngPacketID + 1
strLastPacketID = !PacketID
End If
.Edit
!new_PacketID = lngPacketID
.Update
.MoveNext
Loop
.Close
End With
Set rs = Nothing
Set db = Nothing
End Sub
I think an approach like that could be fine for a one-time conversion. However if this is an operation you need to perform repeatedly, it could be more complicated ... especially if you need each PacketID replaced with the same number from one run to the next ... eg. BDFD-2 was replaced by 1 the first time, so must be replaced by 1 every time you run the procedure.
If you only have a few packet IDs, you can just use update:
UPDATE table_name
SET PacketID =
(
CASE PacketID
WHEN 'BDFD-2' THEN 3
WHEN 'ABCD-1' THEN 5
ELSE 2
END
)
The ELSE is optional.
I am not sure why you even want to convert the packet ids to a number, they seem perfectly fine as they are. You could create a table of packets as follows
SELECT DISTINCT TableOfRows.Packet_id AS PacketId INTO Packets FROM TableOfRows;
You can then use this to select the packet you are interested in and display the corresponding rows