Classic ASP - When to close recordset - object

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

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

"At least one object must implement IComparable" exception from LINQ query results

I have not used LINQ very extensively, but I'm trying to read data from a large Excel spreadsheet (14K+ rows) that requires me to make queries from multiple worksheets and even requery the original spreadsheet to filter specific data. Because OleDb queries of Excel can take a relatively long time (500+ms per query for a file on my local machine), I'm doing a couple of these queries at the front of my method, starting a loop through a "base" DataTable, then trying to use LINQ to filter down the data within that loop to put the appropriate data into a more structured DataSet. Here is some code to help explain (VB.NET):
Dim Connection As System.Data.OleDb.OleDbConnection
Dim Command As System.Data.OleDb.OleDbDataAdapter
Dim EXCEL_SHEET_DATA_1 As New DataTable
Dim EXCEL_SHEET_DATA_2 As New DataTable
Dim EXCEL_SHEET_DATA_3 As New DataTable
Dim TapeFile As New FileInfo("C:\TempFolder\tapefile.xls")
Connection = New System.Data.OleDb.OleDbConnection("provider=Microsoft.Jet.OLEDB.4.0; Data Source='" & TapeFile.FullName & "'; Extended Properties=Excel 8.0;")
Command = New System.Data.OleDb.OleDbDataAdapter("SELECT * FROM [SHEET1$] ORDER BY [USER_ID] ASC, [MEMBER_NUMBER] ASC;", Connection)
Command.Fill(EXCEL_SHEET_DATA_1)
Command.Dispose()
Command = New System.Data.OleDb.OleDbDataAdapter("SELECT * FROM [SHEET2$] ORDER BY [USER_ID] ASC, [MEMBER_NUMBER] ASC;", Connection)
Command.Fill(EXCEL_SHEET_DATA_2)
Command.Dispose()
Command = New System.Data.OleDb.OleDbDataAdapter("SELECT * FROM [SHEET3$] ORDER BY [USER_ID] ASC, [MEMBER_NUMBER] ASC;", Connection)
Command.Fill(EXCEL_SHEET_DATA_3)
Command.Dispose()
For Each Row As DataRow In EXCEL_SHEET_DATA_1.Rows
Dim MemberNumber As String = Row("MEMBER_NUMBER").ToString.Trim
Dim UserNumber As String = Row("USER_ID").ToString.Trim
' -- CODE FOR INITIAL PROCESSING OF SHEET1 DATA - NO ERRORS --
Dim CoMemberQuery As IEnumerable(Of DataRow) = From cm In EXCEL_SHEET_DATA_2 Where cm("MEMBER_NUMBER") = MemberNumber And cm("USER_ID") = UserNumber
For Each CoMemberRow As DataRow In CoMemberQuery
' -- CODE FOR PROCESSING OF SHEET2 DATA - NO ERRORS --
Next CoMemberRow
Dim VehicleQuery As IEnumerable(Of DataRow) = From veh In EXCEL_SHEET_DATA_1 Where veh("MEMBER_NUMBER") = MemberNumber And veh("USER_ID") = UserNumber Order By veh("VIN") Ascending
' *******************************************************
' -->> HERE IS WHERE I *SOMETIMES* GET THE EXCEPTION <<--
' *******************************************************
For Each VehicleRow As DataRow In VehicleQuery
' -- CODE FOR SECONDARY PROCESSING OF SHEET1 DATA - NO ERRORS --
Next VehicleRow
Next Row
I don't get the exception every time. The only thing I've noticed as possibly having something to do with it is that for the specific MemberNumber and UserNumber combination that causes the first exception, the first row in the result set would most likely contain a NULL value for the VIN field.
I'm sure the problem has to do with my LINQ query syntax, but I am simply too inexperienced in this regard to know why it's failing. Any assistance would be greatly appreciated. If you require any additional information regarding the code or implementation, let me know and I'll try to add it to the question.
Thank you for your time.
Your VehicleQuery has the following phrase: Order By veh("VIN") Ascending.
So as soon as VehicleQuery gets evaluated (by starting the For loop), LINQ will evaluate all of the items in that query, and then perform a sorting operation, which involves comparing the veh("VIN") values with each other and putting them in order.
When comparing any two items in your query, it tries to see if either value knows how to compare itself with values of the other type (hence implementing the IComparable interface. If they cannot, then it doesn't know which one should go first.
My guess is that veh("VIN") is (sometimes) yielding objects that don't know how to compare themselves with other values returned by this expression. Depending on the kind of data you're using, and how you want it to be compared, you might consider doing some kind of cast or conversion, or simply calling ToString() on the value, to make sure it's comparable: Order By veh("VIN").ToString() Ascending
(Please pardon any syntax errors, as I'm a C# developer.)

How to link two sets of data when generating an extract in Excel?

I have a simply application where I let user add a Client information (Fname, Lname, Dob) to tblClient. Each client is assigned an id such as TalID101, TalID102, TalID103 and so on. On another form, I let the user add child information (Fname, Lname, DOB). This information gets saved to another table, with the same id that the parent was saved with, but this information goes into another table called tblFamilyMember. I created an Excel extract, and here’s my select statement:
Set g_RS = New ADODB.Recordset
g_RS.CursorLocation = adUseClient
g_strSQL = "SELECT TalID, FirstName, LastName, from dbo.tblclients "
g_RS.Open g_strSQL, g_cnDatabase
Debug.Print g_strSQL
g_RS.MoveFirst
xlRow = 1
So I select this code, and make columns for the spreadsheet and it works beautifully. What I’m trying to do is to have the child be below the parent on the spreadsheet using the TALID. So parent with TalID 101, then all kids associated with TalID101, then parent TalID102, and kids, etc. I’m just not really sure how to link the two tables together, I’ve been trying something like this, but I keep seeing errors, something about "could not find stored procedure 'false'".
Not sure if I should do Inner Join or if there's another mistake I'm not seeing?
g_strSQL = "SELECT tblFamilyMember.TalID, tblFamilyMember.FirstName, tblFamilyMember.LastName from tblFamilyMember "
g_strSQL = g_strSQL & "Where tblFamilyMember.TalID = tblClient.TaLID"
Yes, you should use an inner join. Also if you want child records you should add them to select clause e.g tblClient.FirstName etc.
Try something like this:
g_strSQL = "SELECT tblClient.TalID, tblFamilyMember.FirstName, tblFamilyMember.LastName
FROM tblFamilyMember INNER JOIN tblClient
ON tblFamilyMember.TalID = tblClient.TaLID "
If you want to include all clients regardless if they have child members or not the try this:
g_strSQL = "SELECT tblClient.TalID, tblFamilyMember.FirstName, tblFamilyMember.LastName FROM tblClient
LEFT JOIN tblFamilyMember
ON tblFamilyMember.TalID = tblClient.TaLID "

Copy ADO Recordset using loop

I tried and failed to edit records from an ADODB recordset that I populate with an SQL (Original Question. So then I decided to go the old fashioned (and inefficient) way and copy the recordset onto a fresh new one record by record.
I start by setting the field properties equal (Data Type and Size), since I want to make sure I get a correct data match. However, I encounter two errors:
"Non-nullable column cannot be updated to Null"
and
"Multiple-step operation generated errors. Check each status value"
(Which was exactly what I was trying to avoid by looping!)
Here is the code:
'Create recordset
Set locRSp = New ADODB.Recordset
'Copy fields (same data type, same size and all updateable (which is the final goal)
For Each Field In locRS.Fields
locRSp.Fields.Append Field.Name, Field.Type, Field.DefinedSize, adFldUpdatable
Next
'Copy records
locRSp.Open
locRS.MoveFirst
'Loop original recordset
Do While Not locRS.EOF
locRSp.AddNew
'Loop all fields
For Each Field In locRS.Fields
locRSp.Fields(Field.Name) = locRS.Fields(Field.Name)
Next
locRS.MoveNext
Loop
What I dont understand is:
If I am copying the original field properties (Size and Type), why would it give data errors!?
Is there some other property I need to be looking at? How?
For the first problem: Simply, if you want to store Null values, you need to set the attribute to "adFldIsNullable"
So for my example I changed the append call to:
locRSp.Fields.Append Field.Name, Field.Type, Field.DefinedSize, adFldIsNullable
For the second problem: When the query is downloaded to the original recordset the field properties are set I guess depending on the data itself. But in this case, I went one by one investigating what that was and found that the problem column was set to:
Data Type adNumeric
Which needs to have a precision and scale defined. Where precision is how many digits you want, and scale is number of decimals
So in my case I added an IF to the loop that copies the fields:
If Field.Type = 131 Then '131 is the constant value for adNumeric
'Define Scale
locRSp.Fields(Field.Name).NumericScale = 0
'Define Precision
locRSp.Fields(Field.Name).Precision = 4
End If

ADO on Access Lookup field, getting wrong field returned

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.

Resources