ADO on Access Lookup field, getting wrong field returned - excel

I'm in Excel 2010 VBA, using ADO 2.8 to query an Access 2010 database. I don't own the database and don't have any authority to make any changes to it. I've been working with Excel VBA for many years but my Access knowledge is sketchy.
Using the same SQL as one of the queries in the database (copied from the Access query and pasted into Excel VBA), I get different results in some fields than that query in the database gets.
For the affected fields, I see that in Access those fields are defined as lookups. Here's an example lookup from one of the affected fields' Row Source property:
SELECT [Signers].[SignerID], [Signers].[SignerName] FROM Signers ORDER BY [SignerID], [SignerName];
In the Access database, where the SQL statement refers to that field, the query returns SignerName.
But in my ADO code, where the very same SQL statement refers to that field, the query returns SignerID, not SignerName.
Is there something I can do from my ADO code to get SignerName instead of SignerID, from the same SQL statement? Or do I need to modify the SQL statement?
Thanks,
Greg
Update:
On the Access side, I think I see now why only SignerName appears. On the field's Lookup tab, the Column Widths property is:
0";1.2605"
So I guess SignerID is there in the Access query result but with a column width of 0.
Unfortunately that doesn't help me on the ADO side. Any suggestions on getting SignerName instead of SignerID in the ADO query result?
Update2:
Here's a sample SQL statement that returns different fields depending on either it's in Access or in ADO:
SELECT MasterAccount.[SignerKey1]
FROM MasterAccount ;
Per Preet's request, here's the ADO code in Excel VBA:
strDatabasePath = rgDatabasePathCell.Value 'rgDatabasePathCell is a worksheet cell object.
strPWD = DATABASE_PASSWORD
Set cnn = New ADODB.Connection
cnn.Provider = "Microsoft.ACE.OLEDB.12.0"
cnn.ConnectionString = "Data Source='" & strDatabasePath & "';Jet OLEDB:Database Password='" & strPWD & "';"
cnn.Open
Set cmd = New ADODB.Command
cmd.ActiveConnection = cnn
cmd.CommandType = adCmdText
cmd.CommandText = strSQL
Set rst = New ADODB.Recordset
rst.Open cmd.Execute
shMA.Cells(2, 1).CopyFromRecordset rst 'shMA is a worksheet object.
Update 3:
It occurred to me that from what I've said so far, it might seem like I could just change this:
SELECT MasterAccount.[SignerKey1]
FROM MasterAccount ;
to this:
SELECT [Signers].[SignerName]
FROM MasterAccount ;
But there are 13 affected lookup fields, all with exactly the same "Row Source" property text as shown above, and all return different SignerName items for each row. I don't know why they are all returning different items per row; I have not been able to find any difference in the way they are defined. I've been tasked with getting the same result in Excel as that Access query gets.
Update 4:
VBlades -- Thanks, I found the form that has a tab with dropdowns for each of the 13 SignerKey-n fields. If I right-click that form and choose Form Properties, the RecordSource property is:
SELECT MasterAccount.*, Bank.BankRating FROM Bank INNER JOIN MasterAccount ON Bank.BankID = MasterAccount.Bank;
However I don't understand how that would be selecting a different SignerName item for each of the 13 SignerKey-n fields, or what to do with this information to get the same results in ADO as in the Access query. Any suggestions?
Update 5:
I may be close to a workaround. If I do this, I get the SignerName field for SignerKey1:
SELECT Signers.SignerName
FROM Signers RIGHT JOIN MasterAccount ON Signers.SignerID = MasterAccount.SignerKey1.Value;
And if I do this, I get different SignerName items for each field on each row:
SELECT Signers.SignerName, Signers_1.SignerName, Signers_2.SignerName
FROM Signers AS Signers_2 INNER JOIN (Signers AS Signers_1 INNER JOIN (Signers RIGHT JOIN MasterAccount ON Signers.SignerID = MasterAccount.SignerKey1.Value) ON Signers_1.SignerID = MasterAccount.SignerKey2.Value) ON Signers_2.SignerID = MasterAccount.SignerKey3;
That works both in an Access query and in ADO. Next step, I'll try to add these joins to the main SQL statement.
Update 6:
Well, when I try to add even one of those 13 joins to the main SQL statement, it works fine in an Access query, but in ADO I get the error:
Row handles must all be released before new ones can be obtained.
So I'm stuck. Any suggestions?
I've raised the issue with the database owner, but they don't know why the affected fields' Row Source property includes SignerID, so I'm not sure if that's going to help.

You can do the following (simplest way)
Exclude [Signers].[SignerID] from the Query
SELECT [Signers].[SignerName] FROM Signers ORDER BY [SignerID], [SignerName];
Or, create Composite field containing both [SignerID], [SignerName] and extract any part using VBA that you are familiar with:
SELECT ([SignerID] & "_" & [SignerName]) As Composite FROM Signers ORDER BY [SignerID], [SignerName]
Regards,

Okay, I worked around the problem by getting a separate recordset of the SignerID and SignerName fields from the Signers table.
Then I looped through all the rows of each affected field, looked up SignerID in the 2nd table, and swapped in SignerName for SignerID in the original table.
I tried to do that in ADO, but got the error "An UPDATE or DELETE query cannot contain a multi-valued field". So instead I made the swaps after copying the recordset to the Excel worksheet.
I would have liked to know how to handle it all in ADO, but this works. All is well now.

Related

Getting "Please rebuild this data combination" on a computer but not on another one

This is my first try at using the Power Query... I've build a "dynamic" query in which I can change the retrieved fields as well as the filtering fields and values to be used by the query.
It's working perfectly on my computer but as soon as I try to execute it on another computer, I get the "Please rebuild this data combination" error. I saw some post saying I'll have to kind of split my query but I have not been able to figure it out.
Here is what my 2 tables look like:
Condition and fields selection
and here is my Query with the error:
Query
This might not be very elegant, but it allow me, thru a VBA script, to generate the list of fields to be retrieved and to generate the condition to be used by the SQL.
Any idea why it's not working on the other computers or how to improved the solution I'm using?
Thank you!
Notes:
Hi, all my Privacy Level are already set to 'None'.
I've tried to parametrize my code but I can't figure how. The Where condition is dynamic: it could be Where Number = "1234" but in other condition, the where might be like: 'Where Assignee = "xyz"'.
Here is a simplified example of my code:
let
Source = Sql.Database("xxxx", "yyyy", [Query=
"Select network, testid
from CM3T1M1 "
& paramConditions[Conditions]{0} &
" "])
in
Source
rebuild query, Formula.Firewall
That's a feature to prevent prevent accidentally leaking data. You can change the privacy level to ignore it
See also: docs.microsoft/dataprivacyfirewall
Is the dynamic query inserting those cells into the SQL query ? Report Parameters are nice for letting the user change variables without having to re-edit the query.
Parameterized native SQL queries
from: https://blog.crossjoin.co.uk/2016/12/11/passing-parameters-to-sql-queries-with-value-nativequery-in-power-query-and-power-bi/
let
Source = Sql.Database("localhost", "Adventure Works DW"),
Test = Value.NativeQuery(
Source,
"SELECT * FROM DimDate
WHERE EnglishMonthName=#MonthName AND
EnglishDayNameOfWeek=#DayName",
[
MonthName = "March",
DayName = "Tuesday"
]
)
in
Test
Dynamic Power Query version of SQL Query
To dynamically generate this SQL Query
select NUMBER, REQUESTED_BY from SourceTable
where NUMBER = 404115
Table.SelectRows is your Where.
SelectColumns is your select
let
Source = ...,
filterByNum = 404115,
columnNames = {"NUMBER", "REQUESTED_BY"},
removedColumns = Table.SelectColumns(
Source, columnNames, MissingField.Error
),
// I used 'MissingField.Error' so you know right away
// if there's a typo or bug
// assuming you are comparing Source[NUMBER]
filteredTable = Table.SelectRows(
Source, each [NUMBER] = filterByNum
)
in
filteredTable

Parameterize Source in Power Query when connecting to PostgreSQL DB

Using the .Net data provider for postgresql, I would like to create an excel workbook that loads some tables from a given schema. The schema is set using a named range in the excel workbook, the table names are the same for each schema.
I tried the following:
Define a query "from other sources" / "blank query" named SchemaIdParam as
let
rng= Excel.CurrentWorkbook(){[Name="schemaid"]}[Content]
in
rng
(the name "schemaid" is defined in the workbook.)
Define a query "from PostgreSQL db" named mytable as
let
src = PostgreSQL.Database("xxx.myhost.com:5235", "my_database"),
tbl = src{[Schema=SchemaIdParam,Item="mytable"]}[Data]
in
tbl
Now this does not work. The error message states: "[Expression.Error]: no match between key and rows in table" (own translation). Yet it works if I replace SchemaIdParam by a literal value in quotation marks. Then the correct table is delivered.
Any hints how I can resolve this are very appreciated!
The reason why I want to use a named range for the schema name is that I want to programmatically, outside from excel, set the schema name. I am very open to suggestions how to do this in another way.
It could be a type error. Try converting the SchemaIdParam value to text as part of that query or else try
tbl = src{[Schema=Number.ToText(SchemaIdParam),Item="mytable"]}[Data]
After a lot of trying, I found the answer. I had a problem defining SchemaIdParam. A working definition is:
let
rng= Excel.CurrentWorkbook(){[Name="jobid"]}[Content],
value = rng{0}[Column1]
in
value
i.e., I had to reference a specific cell in the named range.

Overwrite Existing Data in Access Using transferspreadsheet

I have a macro that saves monthly data to an Access database in Access 2013 using the DoCmd.TransferSpreadsheet method. A possible situation I want to account for is if data is saved to the database and then someone realizes that the data was wrong and they want to rearchive the data for the month in question after they fix the data. In other words, is there a way for me to change my code to overwrite data for the same month as the data that will be archived?
One of the fields in the data that is archived is the date, and it also has a field name as well. Another important thing to know is that the data that needs to be overwritten would all be in the same month, but not necessarily the same day. Here's a simplified example of what the "my_data" array would look like in excel:
Sub excel_export()
Dim xls_path As String
xls_path = "C:\mywbk.xlsm"
Dim db_path As String
Dim db_obj As Access.Application
db_path = "C:/mydb.accdb"
Set db_obj = New Access.Application
Call DoCmd.TransferSpreadsheet(acImport, acSpreadsheetTypeExcel12Xml, "TableName", _
xls_path, True, "my_data")
db_obj.CloseCurrentDatabase
Set db_obj = Nothing
End Sub
Don't import the spreadsheet, link it as a table.
Then, first, create a simple select query that filters the linked table and converts, say, string dates to real dates.
Now, use this query as source for a combined update and append query for your Access table as described here:
Update and Append Records with One Query

Classic ASP - When to close recordset

I would like to know, which of the following examples is the best for closing a recordset object in my situation?
1)
This one closes the object inside the loop but opens a new object when it moves next. If there were 1000 records, this opens an object 1000 times and closes it 1000 times. This is what I would normally do:
SQL = " ... "
Set rs1 = conn.Execute(SQL)
While NOT rs1.EOF
SQL = " ... "
Set rs2 = conn.Execute(SQL)
If NOT rs2.EOF Then
Response.Write ( ... )
End If
rs2.Close : set rs2 = Nothing
rs1.MoveNext
Wend
rs1.Close : Set rs1 = Nothing
2)
This example is what I want to know about. Does saving the object closure (rs2.close) until after the loop has finished, gains or reduces performance? If there were 1000 records, this would open 1000 objects but only closes it once:
SQL = " ... "
Set rs1 = conn.Execute(SQL)
While NOT rs1.EOF
SQL = " ... "
Set rs2 = conn.Execute(SQL)
If NOT rs2.EOF Then
Response.Write ( ... )
End If
rs1.MoveNext
Wend
rs1.Close : Set rs1 = Nothing
rs2.Close : set rs2 = Nothing
I hope I've explained myself well enough and it's not too stupid.
UPDATE
To those who think my query can be modified to avoid the N+1 issues (2nd query), here it is:
This is for an online photo library. I have two tables; "photoSearch" and "photos". The first, "photoSearch", has just a few columns and contains all searchable data for the photos, such as "photoID", "headline", "caption", "people", "dateCaptured" and "keywords". It has a multi-column full-text index on (headline, caption, people, keywords). The second table, "photos", contains all of the photos data; heights, widths, copyrights, caption, ID's, dates and much more. Both have 500K+ rows and the headline and caption fields sometimes return 2000+ characters.
This is approximately how the query looks now:
(things to note: I cannot use joins with fulltext searching, hence keywords being stored in one column - in a 'de-normalized' table. Also, this kind of pseudo code as my app code is elsewhere - but it's close )
SQL = "SELECT photoID FROM photoSearch
WHERE MATCH (headline, caption, people, keywords)
AGAINST ('"&booleanSearchStr&"' IN BOOLEAN MODE)
AND dateCaptured BETWEEN '"&fromDate&"' AND '"&toDate&"' LIMIT 0,50;"
Set rs1 = conn.Execute(SQL)
While NOT rs1.EOF
SQL = "SELECT photoID, setID, eventID, locationID, headline, caption, instructions, dateCaptured, dateUploaded, status, uploaderID, thumbH, thumbW, previewH, previewW, + more FROM photos LEFT JOIN events AS e USING (eventID) LEFT JOIN location AS l USING (locationID) WHERE photoID = "&rs1.Fields("photoID")&";"
Set rs2 = conn.Execute(SQL)
If NOT rs2.EOF Then
Response.Write ( .. photo data .. )
End If
rs2.Close
rs1.MoveNext
Wend
rs1.Close
When tested, having the full-text index on its own table, "photoSearch", instead of the large table, "photos", seemed to improve speed somewhat. I didn't add the "photoSearch" table, it was already there - this is not my app. If I try joining the two tables to lose the second query, I lose my indexing all together, resulting in very long times - so I can't use joins with full-text. This just seemed to be the quickest method. If it wasn't for the full-text and joining problems, I would have combined both of these queries already.
Here is the thing. First, get your photo ids and make mysql thinks that is an actual table that hold the photo ids only, and then make your actual statement, no need any extra recordset connections...
And do not forget to start from the end to do this. Here is the sample code with explanations:
Step 1 Create photo ids lookup table and name it: This will our PhotoId Lookup Table so name it as "PhotoIds"
SELECT photoID FROM photoSearch
WHERE MATCH (headline, caption, people, keywords)
AGAINST ('"&booleanSearchStr&"' IN BOOLEAN MODE)
AND dateCaptured BETWEEN '"&fromDate&"' AND '"&toDate&"' LIMIT 0,50) AS PhotoIds
Step 2 Now we have photo ids, so get the informations from it. We will insert the above statement just before WHERE clause the same way as we do with real tables. Note that our "fake" table must be between parantheses.
SQL = "SELECT p.photoID, p.setID, p.eventID, p.locationID, p.headline, p.caption, + more FROM
photos AS p,
events AS e USING (p.eventID),
location AS l USING (p.locationID),
(SELECT photoID FROM photoSearch WHERE MATCH (headline, caption, people, keywords)
AGAINST ('"&booleanSearchStr&"' IN BOOLEAN MODE) AND dateCaptured BETWEEN
'"&fromDate&"' AND '"&toDate&"' LIMIT 0,50) AS PhotoIds
WHERE p.photoID=PhotoIds.photoID;"
Note: I just write these codes here and never tested. There may be some spelling errors or smt. Please let me know if you have troubles.
Now getting your primary question
No need to close the executed queries, especially if you are using execute method. Execute method closes itself after the execution unless its not returning any recordset data (thats the purpose of execute command at the first place) like: "INSERT", "DELETE", "UPDATE". If you didnt open a recordset object, so why try to close something never opened? Instead you can use Set Rs=Nothing to unreference the object and send to the garbage collection to free up some system resources (and thats nothing to do with mysql itself). If you are using "SELECT" queries, (the queries that will return some data) you must Open a recordset object (ADODB.Recordset) and if you opened it, you need to close it as soon as it finishes its job.
The most important thing is to close the "main connection to mysql server" after each page load. So you may consider to put your connection close algorithm (not recordset close) to an include file and insert it at the end of everypage you make the connection to the database. The long talk short: You must use Close() if you used Open()
If you show us your SQL Statements, maybe we can show you how to combine them into a single SQL statement so you only have to do one loop, otherwise, double looping like this really takes a toll on the servers performance. But before I learned Stored Procedures and Joins, I would have probably done it like this:
Set Conn = Server.CreateObject("Adodb.Connection")
Conn.Open "ConnectionString"
Set oRS = Server.CreateObject("Adodb.Recordset")
oRS.Open "SQL STATEMENT", Conn
Set oRS2 = Server.CreateObject("Adodb.Recordset")
oRS2.ActiveConnection = Conn
Do Until oRS.EOF
oRS2.Open "SQL STATEMENT"
If oRS2.EOF Then ...
oRS2.Close
oRS.Movenext
Loop
oRS.Close
Set oRS = Nothing
Set oRS2 = Nothing
Set Conn = Nothing
I tried putting this in a comment because it doesn't directly answer your original question, but it got too long.. :)
You could try using a sub-query instead of a join, nesting the outer query inside the second one. " ... where photoID in(select photoID from photoSearch ... )". Not sure if it would get better results, but it may be worth trying. That being said, the use of the full-text search does change how the queries would be optimized, so it may take more work to figure out what the appropriate indexes are (need to be). Depending on your existing performance, it may not be worth the effort.
Do you know for sure that this existing code/query is the current bottleneck? Sometimes we spend time optimizing things that we think are the bottleneck when that may not be the case... :)
One additional thought - you may want to consider some caching logic to reduce the amount of redundant queries you may be making - either at the page level or at the level of this method. The search parameters could be concatenated together to form the key for storing the data in a cache of some sort. Of course you would need to handle appropriate cache invalidation/expiry logic. I've seen systems speed up 100x with very simple caching logic added to bottlenecks like this.
It's simple ask about the state of your RecordSet is 1 or 0 , It means open or close
like this
If RS.State = 1 Then RS.Close
the connection to database (CN) will still up but you can reopen the RS (RecordSet) again with any values

Excel Will not Import a Table From Access

I'm Using Excel/Access 2007. I made an Access query in Access, and I tried to import it into Excel using The Data Tab -> Get External Data Subtab -> From Access. I chose my Database, and chose the Query I wanted to import. However, only the headers of the table as well as 2 blank lines show up (as evidenced by their being formatted as a table). There is definitely data that is returned in the query, that I can verify by checking Access. Any help would be greatly appreciated
As an aside, can this be done programatically?
EDIT: Here is the SQL query in all it's glory
SELECT [Meter#], [LDC#], [ESCO#], [Brand], [LDCName], [RateClass], [RateSubClass], [CustName],
[DemandZone], [Type], dbo_Forecasts.Name AS ForecasForecastType,
Min(IntervalMeterConsumption.[DateFrom]) AS ConsumptionStart, IntermediateLog.[MaxOfDateRead] AS ConsumptionEnd,
Sum([kWh])/(Sum([Interval])/365) AS AverageAnnual
FROM (IntermediateLog
INNER JOIN (
(Premise INNER JOIN Meters ON Premise.PremiseCt = Meters.PremiseCt)
INNER JOIN IntervalMeterConsumption
ON Meters.Meterid = IntervalMeterConsumption.MeterID)
ON IntermediateLog.[LDC#] = Premise.CustomerPremiseNo)
INNER JOIN dbo_Forecasts ON Meters.ForecastID = dbo_Forecasts.ForecastID
WHERE ((([MaxOfDateRead]-[DateFrom])<=380))
GROUP BY IntermediateLog.[Meter#], IntermediateLog.[LDC#], IntermediateLog.[ESCO#],
IntermediateLog.Brand, IntermediateLog.LDCName, IntermediateLog.RateClass,
IntermediateLog.RateSubClass, IntermediateLog.CustName, IntermediateLog.DemandZone,
IntermediateLog.Type, dbo_Forecasts.Name, IntermediateLog.MaxOfDateRead;
You could try removing the where clause just temporarily to see if that makes a difference.
When you run the query in Access does it ask for a parameter?
The code to export a query to Excel from Access is as follows:
DoCmd.TransferSpreadsheet acExport, acSpreadsheetTypeExcel12, "Query/Table Name", "c:\export.xls"
thanks Mark

Resources