MS Access - Editing a query's sql with vba - change "SELECT Top n" - sql

Context - I'm building an access database that keeps track of sailboat races and calculates overall season scores as well as smaller "series" of scores. However, in a "series" not every race is counted. For example, if a series has 10 races, I only want to count the top 7 races of each individual person.
I have a separate query that is able to calculate the number of races actually counted based on the total number in each series. The query I am working on now calculates each individual's score by adding up their points for their top "n" races in that series. I don't have an extensive knowledge in sql or vba, but I was able to figure out how to use the "SELECT Top n" to filter each individual's top scores and then use a SUM to get the total.
The problem I have now is that the "n" has to be adaptable because the series could have any number of races. After some research, I learned that the only way to alter "SELECT TOP" is to use vba to rewrite the query's definition. I'm not exactly sure how to accomplish this- I don't even know where to put the code to alter the query in vba.
Again, I don't have much experience in vba, but I'm eager to learn in order to accomplish what I need. Any help is appreciated and I can show my sql if needed.

So, I think you want to store the value of the number of races in a series into a variable, and use that variable in your Top N query.
Dim Db As DAO.Database
Dim rs As DAO.Recordset
Dim series As Integer
Set db = CurrentDb
Set rs = db.OpenRecordset("YourTableNameOrQueryName")
'Here we can open the Table and store the number of series into a variable.
series = rs!YourSeriesCountFieldInTableOrQuery
Dim SQL As String
SQL = "SELECT Top " & series & " races FROM YourTable"
' You can ensure you have the right number of series by setting a break point or
' Using Debug.Print (SQL) to see the SQL in the output window.
db.Execute "SQL", dbFailOnError
'The SQL string would be your query that you have working, as posted in your OP.
'The only difference would be the string concatenation of the number of series that is dynamic
rs.Close
Set rs = Nothing

Related

How to process a table serially for count

I need to process through a list of technical skills one by one and get a count of the number of developers we have in 3 locations who have that skill. For example, car type = "Java". How many persons have this skill listed in their resume.
I have 2 tables:
Skills: contains a single column listing skills - "Java" for example
Resources: contains 4 columns, Resource-ID, Name, Location, and a long text field called "Resume" containing text of their resume.
If this were a single skill I would process the SQL something like below (SQL syntax not exact)
SELECT count FROM [Resources] WHERE ([Resources].[Resume] Like "SKILL-ID*");
I want to process the Skills table serially printing the "Skill" and the count in each location.
Help appreciated
I've only used Access as a DB for single record retrieval, never using table values as input to loop through a process. I suspect that this is a simple execution in MS Access.
Ok, so we have to process that table.
I would simple "send out" a row with Location and skill for each match. We could write some "messy" code to then group by, but that is what SQL is for!!!
We could quite easy keep/have/use/enjoy the results in code, but its better to send the results out to a working table. Then we can use what sql does best - group and count that data.
So, then, the code could be this:
Sub CountSkills()
' empty out our working report table
CurrentDb.Execute "DELETE * FROM ReportResult"
Dim rstSkills As DAO.Recordset
Dim rstResources As DAO.Recordset
Dim rstReportResult As DAO.Recordset
Dim strSQL As String
Set rstSkills = CurrentDb.OpenRecordset("Skills")
strSQL = "SELECT Location, Resume FROM Resources " & _
"ORDER BY Location"
Set rstResources = CurrentDb.OpenRecordset(strSQL)
Set rstReportResult = CurrentDb.OpenRecordset("ReportResult")
Do While rstResources.EOF = False
' now for each resource row, process skill set
rstSkills.MoveFirst
Do While rstSkills.EOF = False
If InStr(rstResources!Resume, rstSkills!Skill) > 0 Then
rstReportResult.AddNew
rstReportResult!Location = rstResources!Location
rstReportResult!Skill = rstSkills!Skill
rstReportResult.Update
End If
rstSkills.MoveNext
Loop
rstResources.MoveNext
Loop
End Sub
Now, the above will wind up with a table looking like this:
So, now we can query (and count) against above data.
So, this query would do the trick:
SELECT Location, Skill, Count(1) AS SkillCount
FROM ReportResult
GROUP BY Location, Skill
And now we get this:
And you can flip the above query to group by skil, then location if you wish.
so, at the most simple level?
We write out ONE row + location for every match, and then use SQL on that to group by and count.
We COULD write code to actually count up by location, but that VBA code would as noted be a bit messy, and just spitting out rows of location and skill means we can then group by skill count, skill location count, or location, skill counts just by using "simple" sql against that list of location and skill record list.
So, now use the report wizard on that query above, and we get something like this:
Of course it is simple to change around the above report, but you get the idea for such a simple task as you noted.
Summarizing count of developers by skill and location can be accomplished with SQL. It requires a dataset of all possible skill/location pairs. Consider this simple example:
Resources
ID
Name
Location
Resume
1
a
x
Java,Excel
2
b
x
Excel
3
c
y
Excel
4
d
z
VBA,Java
SELECT Skills.SkillName, Resources.Location,
Sum(Abs([Resume] Like '*' & [SkillName] & "*")) AS DevCt
FROM Skills, Resources
GROUP BY Skills.SkillName, Resources.Location;
SkillName
Location
DevCt
Excel
x
2
Excel
y
1
Excel
z
0
Java
x
1
Java
y
0
Java
z
1
VBA
x
0
VBA
y
0
VBA
z
1
This approach utilizes a Cartesian product of Skills and Resources tables to generate the data pairs. This type of query can perform slowly with large dataset. If it is too slow, saving the pairs to a table could improve performance. Otherwise, a VBA solution will be only recourse and would likely involve looping recordset object.
Regardless of approach, be aware results will be skewed if Resume has content like "excellence". Bad data output is pitfall of poor database design.
Solution:
Resource and Skill table were added into MS_Access
Step 1: Create a query that executes the below SQL to get a counts (here named "Step1Query"):
SELECT Skills.SkillName, Resources.Location,
Sum(Abs([Resume] Like '*' & [SkillName] & "*")) AS DevCt
FROM Skills, Resources
GROUP BY Skills.SkillName, Resources.Location;
Step 2: Create a second query that uses the Step 1 query as input. (you can do this via the wizard):
TRANSFORM Sum(Step1Query.DevCt) AS SumOfDevCt
SELECT Skills.SkillName, Resources.Location,
Sum(Abs([Resume] Like '*' & [SkillName] & "*")) AS DevCt
FROM Skills, Resources
GROUP BY Skills.SkillName, Resources.Location
PIVOT Step1Query_qry.[Location];
Result lists out a matrix form. Thanks all for your help.

Why the Recordset from VBA just return one record?

I have 3 tables, "persons", "per_resi" and "residence"
This three tables form a many to many relation.
Table "person" fields: id, name etc....
Table "residence" fields: id, Street etc.....
Table "per_resi" fields: person_id and residence_id (together principal index)
Well, the problem is when I design a query in the graphic Access tool it Works as it should be.
But if I do in VBA it only return 1 record.
Dim svivienda As String
Dim rvivienda As Recordset
svivienda = "SELECT tbl_persona.Id, tbl_vivienda.Calle, tbl_vivienda.Numero " _
& "FROM tbl_vivienda INNER JOIN (tbl_persona INNER JOIN tbl_perso_viv ON tbl_persona.Id = tbl_perso_viv.Id_persona) " _
& "ON tbl_vivienda.Id = tbl_perso_viv.Id_vivienda WHERE tbl_persona.Id = " & 168 & ";"
Set rvivienda = CurrentDb.OpenRecordset(svivienda, dbOpenDynaset)
I have tried LEFT JOIN and RIGHT JOIN but always the same just one record on the recordset.
Any ideas?
MS access 2013
Thanks in advance.
Thank guys,
This was a very novel question.
Here is the answer.
The RecordCount property does not report the amount of records you have.
The value of the RecordCount property equals the number of records
that have actually been accessed. For example, when you first create a
dynaset or snapshot, you have accessed (or visited) only one record.
If you check the RecordCount property immediately after creating the
dynaset or snapshot (assuming it has at least one record), the value
is 1. To visit all the records, use the MoveLast method immediately
after opening the Recordset, and then use MoveFirst to return to the
first record. This is not done automatically because it may be slow,
especially for large result sets.
Count the number of records in a DAO Recordset
Thanks!!!
Add following statement :
rvivienda.MoveNext
will return the next record of the recordset
or :
rvivienda.MoveLast
will return the last record of the recordset
You will see the result.
The goosie2018's answer is CORRECT. I just show you the simple way to understand.
SUMMARY
So, I think the recordset you get from the database will not show the result look like an Array or a List, but a cursor. And the default cursor points to the first row, so if you use :
rvivienda.RecordCount
you should receive the number of records you actually got.
Sorry for my English ! And thanks for reading.

Calculation based on values in 2 different rows

I have a table in MS Access which has stock prices arranged like
Ticker1, 9:30:00, $49.01
Ticker1, 9:30:01, $49.08
Ticker2, 9:30:00, $102.02
Ticker2, 9:30:01, $102.15
and so on.
I need to do some calculation where I need to compare prices in 1 row, with the immediately previous price (and if the price movement is greater than X% in 1 second, I need to report the instance separately).
If I were doing this in Excel, it's a fairly simple formula. I have a few million rows of data, so that's not an option.
Any suggestions on how I could do it in MS Access?
I am open to any kind of solutions (with or without SQL or VBA).
Update:
I ended up trying to traverse my records by using ADODB.Recordset in nested loops. Code below. I though it was a good idea, and the logic worked for a small table (20k rows). But when I ran it on a larger table (3m rows), Access ballooned to 2GB limit without finishing the task (because of temporary tables, the size of the original table was more like ~300MB). Posting it here in case it helps someone with smaller data sets.
Do While Not rstTickers.EOF
myTicker = rstTickers!ticker
rstDates.MoveFirst
Do While Not rstDates.EOF
myDate = rstDates!Date_Only
strSql = "select * from Prices where ticker = """ & myTicker & """ and Date_Only = #" & myDate & "#" 'get all prices for a given ticker for a given date
rst.Open strSql, cn, adOpenKeyset, adLockOptimistic 'I needed to do this to open in editable mode
rst.MoveFirst
sPrice1 = rst!Open_Price
rst!Row_Num = i
rst.MoveNext
Do While Not rst.EOF
i = i + 1
rst!Row_Num = i
rst!Previous_Price = sPrice1
sPrice2 = rst!Open_Price
rst!Price_Move = Round(Abs((sPrice2 / sPrice1) - 1), 6)
sPrice1 = sPrice2
rst.MoveNext
Loop
i = i + 1
rst.Close
rstDates.MoveNext
Loop
rstTickers.MoveNext
Loop
If the data is always one second apart without any milliseconds, then you can join the table to itself on the Ticker ID and the time offsetting by one second.
Otherwise, if there is no sequence counter of some sort to join on, then you will need to create one. You can do this by doing a "ranking" query. There are multiple approaches to this. You can try each and see which one works the fastest in your situation.
One approach is to use a subquery that returns the number of rows are before the current row. Another approach is to join the table to itself on all the rows before it and do a group by and count. Both approaches produce the same results but depending on the nature of your data and how it's structured and what indexes you have, one approach will be faster than the other.
Once you have a "rank column", you do the procedure described in the first paragraph, but instead of joining on an offset of time, you join on an offset of rank.
I ended up moving my data to a SQL server (which had its own issues). I added a row number variable (row_num) like this
ALTER TABLE Prices ADD Row_Num INT NOT NULL IDENTITY (1,1)
It worked for me (I think) because my underlying data was in the order that I needed for it to be in. I've read enough comments that you shouldn't do it, because you don't know what order is the server storing the data in.
Anyway, after that it was a join on itself. Took me a while to figure out the syntax (I am new to SQL). Adding SQL here for reference (works on SQL server but not Access).
Update A Set Previous_Price = B.Open_Price
FROM Prices A INNER JOIN Prices B
ON A.Date_Only = B.Date_Only
WHERE ((A.Ticker=B.Ticker) AND (A.Row_Num=B.Row_Num+1));
BTW, I had to first add the column Date_Only like this (works on Access but not SQL server)
UPDATE Prices SET Prices.Date_Only = Format([Time_Date],"mm/dd/yyyy");
I think the solution for row numbers described by #Rabbit should work better (broadly speaking). I just haven't had the time to try it out. It took me a whole day to get this far.

Variable Date Ranged Data Sets with SQL Statement? Used with MS Access Tables

I have some large series of datasets. That is why when imported, they are automatically divided into tables by year.
EX: Data is from 2014, it goes into table "2014"
If Data is from 2013, it goes into table "2013", all on its own. If the table does not exist, it creates the table, and also inserts "2014" in tblYears to keep track of the tables that have been created.
My issue: I'm trying to allow the user to be able to look-up date ranges of the data. So Maybe someone wants to see Data spanning across February 2013 to March 2015.
So I'm sure I can find a SQL query with a WHERE clause to look-up only the data within the Date Range the user gives, however my issue arises if the date range spans across multiple years. How do I tell the SQL statement to Include all the tables? Let's say someone has a date range of 2013, 2014, and 2015. How do I make sure (variably speaking) that it can include all tables in the FROM?
You could create a query of the relevant years every time the user opens the DB. That way if years are added it is always there. and since it is dropped/recreated every time they open the file it is always up to date.
Something like
Dim db As DAO.Database
Dim qNewUnion As DAO.QueryDef
Dim rsYears As DAO.Recordset
Dim sqlUnion As String, unionAllString As String, unionQueryName As String
unionQueryName = "AllResultYears"
unionAllString = " Union All "
Set db = CurrentDb
Set rsYears = db.OpenRecordset("Your years Table")
'make all your union sql using relevant years
Do While Not rsYears.EOF
sqlUnion = sqlUnion & vbNewLine & " Select * from Results_" & rsYears("Year") & unionAllString
rsYears.MoveNext
Loop
rsYears.Close
'lop off that trailing Union All
sqlUnion = Left(sqlUnion, Len(sqlUnion) - Len(unionAllString))
Set qNewUnion = New QueryDef
qNewUnion.SQL = sqlUnion
qNewUnion.Name = unionQueryName
'delete this query if it already exists
On Error Resume Next
db.QueryDefs.Delete unionQueryName
On Error GoTo 0
'add your new query
db.QueryDefs.Append qNewUnion
Then you use this AllResultYears query as the basis for your search.
Of course this will be much more expensive that just putting your data in one table. That's the optimal solution.

compare all fields in access using vba/sql

I know I can compare values between two tables, but I have not needed to do it for more than 2 or 3 fields up to this point so comparing them each individually hasnt been an issue, I used code such as this:
DoCmd.RunSQL "INSERT INTO Issues
SELECT Eligibility.[Member Id]
, Eligibility.[Sex Code]
, Eligibility.State
FROM Eligibility LEFT JOIN Ref
ON Eligibility.[Sex Code] = Ref.[Sex Code]
WHERE (((Ref.[Sex Code]) Is Null));"
now however, i need to compare about 140 different fields. is there a better way to do this than writting 140 sql statements and running them all one by one?
i want it to find where fields dont contain the same info and then pull the entire row from both tables,or at the very least the value in the 5th column, member id, and then i can run another query to pull the entire row off of that value if need be (so i can look at both at the same time) and paste them into another table and highlight the cells where the mismatches occur.
both tables are in the same database
both tables have the same structure, but the second table might not have all of the values from the first, so i need to find a way to have it match the rows based on the member ID before it starts comparing the rows.
You can compare using DAO pretty easily. Using the .Fields() argument on a recordset you get all the different fields in the actual recordset.
This lets you do something like:
Sub exampleSQLComparison()
Dim rs1 As DAO.Recordset
Dim rs2 As DAO.Recordset
'.... set those recordsets via SQL statements
For Each f In rs1.Fields
If rs1.Fields(f) <> rs2.Fields(f) Then
Debug.Print "Mismatch found for " + f
End If
Next f
End Sub
If your queries are similar and the only thing you are changing is a single field (for example an ID) you should be able to modify the logic accordingly.