Access query not using index - database

I have a table in Access with 1 field called HostName, it is a text field, with 100 char max. I use it to store DNS host names. The field is setup as the primary key. If I do the following query it returns the expected results, but takes about 8 seconds to complete on a table with 1 million records:
SELECT TOP 1 HostsRev.HostName
FROM HostsRev
WHERE (((HostsRev.HostName)>="test"))
ORDER BY HostsRev.HostName;
If I remove the "ORDER BY" part, it returns in less than 1 second, but doesn't always return what I would expect -- not the first record that is >= to "test".
I am doing the query via ADO from a C++ app, but I've tested in Access also, by creating a query, and get the same results.
What I need is to quickly find the first record, if any, that starts with a given string. I also tried using LIKE query but that had the same results. I need to do this because if I search on images.google.com, I need to know if the list contains google.com but not images.google.com (I actually store the host names in reverse string order to make this work correctly, and reverse the strings before doing the lookup).

The issue is that the TOP command on it's own does not apply sorting to the data, so without the ORDER BY it will return in a different order and thus give different results, you could try the following instead:
SELECT Min(HostName) FROM HostsRev WHERE HostName >= "test"
Not sure if this will give any better performance though but worth a go : )

I am not sure if you can do this from C++, not being a C++ programmer, but ADO supports a property .index to allow you to set the index you wish to use and a .seek method to search on that index. here is some code in VB for what it is worth.
Dim conn As ADODB.Connection
Dim rs As ADODB.Recordset
Set conn = New ADODB.Connection
conn.Open ConnectionString
rs.Open "mytable", conn
rs.Index = "primarykey"
rs.Seek "test", adSeekAfterEQ
If rs.EOF Then ' record not found

Related

Alternative to Index + Seek on Access/DAO Using SQL Server/ADO

I'm converting an Excel app from a local MS Access backend (with DAO) to SQL Server running on Azure (with ADO).
A common task I perform with DAO is the index + seek method to scan a large amount of input rows (~10,000, and using multi field indexes), check for matching records in the database, and update or add new records as required. The NoMatch property of the Seek method works very nicely when deciding to add or update.
This seems like it should be pretty simple with SQL Server, but I can't seem to find a good solution that lets me check for matches, add or update, and use multi column indexes.
Downloading the table to memory then doing a batch update would be fine, but ADO's Find method doesn't seem as good as index + seek, and it can't use multi columns. Connecting to SQL Server with an ADO provider that supports index seek would also work (Jet 4.0?) but I can't find examples of that either.
Am I missing something obvious? What is the best way to check and add or update large number of rows to SQL Server? Thanks
Edit:
Here's a simple example of the operation I'm doing currently in Access/DAO:
Set rs = db.OpenRecordset("TableName", dbOpenTable)
With rs
.Index = "MultiFieldIndex"
'Loop through the input data
For i = 1 To 10000
.Seek "=", Criteria1, Criteria2
If Not .NoMatch Then
'Found a match, just update specific fields
!Field1 = a
!Field2 = b
Else
'No match found, add a new record then populate
.AddNew
!Field3 = c
!Field4 = d
End If
.Update
Next i
End With
Whats the best way to do something like this only with SQL? I'd still probably start with loading a disconnected recordset of the full target table, but not sure how to update a few thousand records when I don't know if I'll need to update or add new, or the values of the input criteria. How do I find the rows I need to update/check without index + seek?
OR can I create a temp table in memory with only the input data then somehow just 'merge' that table to the database and the db will figure out how to update or add?
I feel like this should be a pretty basic procedure, but maybe I'm just missing some fundamental SQL concept?
Thanks for all the help!
The solution would be to write an SQL query
create procedure dbo.prc_TableNameUpdIns (#parm1 int, #parm2 int)
AS
IF EXISTS (SELECT * from MyTable WHERE A = #parm1 AND B = #parm2)
UPDATE ...
ELSE
INSERT....
GO
(you could use merge but I advise against it)
And you call the SP by creating an AdoConnection (in VBA)
dim myAdoConnection as ADOConnection
dim X as Integer, Y as Integer
X = 7
Y = 8
'SET the myAdoConnection
'Calling the proc
myAdoConnection.prc_TableNameUpdIns X, Y
Processing as much code as possible in the SQL is much preferable. The only time you need to do this type of ADO manipulation is when you need to update the form only and not necessarily the database itself.
About ADO connection
And research the subject some more, this is only a pointer

Using VB6 to update info in database

I'm being taught VB6 by a co-worker who gives me assignments every week. I think this time he's overestimated my skills. I'm supposed to find a line in a text file that contains Brand IDs and their respective brand name. Once I find the line, I'm to split it into variables and use that info to create a program that, via an inserted SQL statement, finds the brand, and replaces the "BrandName" in the item description with the "NewBrandname".
Here's what I'm working with
Dim ff as integer
ff = freefile
Open "\\tsclient\c\temp\BrandNames.txt" For Input as #ff
Do Until EOF(ff)
Dim fileline as string,linefields() as string
line input #ff, fileline
linefields = split(fileline,",")
brandID = linefields(0)
BrandName = linefields(1)
NewBrandName = linefields(2)
I want to use the following line in the text file, since It's the brand I'm working with:
BrandID =CHEFJ, BrandName=Chef Jay's NewBrandName=Chef Jays
That's what 'fileline' is- just don't know how to select just that one line
As for updating the info, here's what I've got:
dim rs as ADODB.Recordset, newDesc1 as String
rs = hgSelect("select desc1 from thprod where brandID='CHEFJ'")
do while not rs.eof
if left(rs!desc1,len(BrandName)) = BrandName then
dim newDesc1 as string
newDesc1 = NewBrandname & mid(rs!desc1, len(BrandName)+1)
hgExec "update thprod set desc1=" & adoquote(NewBrandName) & "+right(desc1,len(BrandName))" where brandId=CHEFJ and desc1 like 'BrandName%'"
end if
rs.movenext
loop
end while
How do I put this all together?
Just to give you some guidelines;
Firstly you need to read the Text file, which you are already doing.
Then, once you get the data, you need to spot the format and SPLIT the data to retrieve only the parts you need.
For example, if the data read from textfile gives you BrandID=CHEFJ, BrandName=Chef Jay's, NewBrandName=Chef Jays, you will see that the data are delimited by commas ,, and the property values are preceded by equal signs.
Follow LINK for more info of how to split.
Once you've split the data, you can easily use them to proceed with your database update. To update your db, first of all you will need to create the connection. Then your query to update using the data you've fetched from the Text file.
Finally you need to execute your query using ADODB. This EXAMPLE can help.
Do not forget to dispose the objects used, including your connection.
Hope it helps.

Unable to query nVarChar(Max) field in Access 2010

have used Stack Overflow as a resource hundreds of times, but my first time posting a question for some help!
I've got a table in SQL Server 2005 which contains 4 nVarChar(Max) fields.
I'm trying to pull out the data from an Access (2010) VBA Module using ADO 2.8
I'm connecting using SQL driver SQLNCLI10
(I can't use a linked table, as the 'table' I will ultimately be querying is a Table-Valued Function)
When I then print / use the recordset, the data is getting jumbled and concatenated with other fields in the same record - with a bunch of obscure characters thrown in.
The VBA: (various other methods were tried with the same result)
Sub TestWithoutCasting()
Dim cn As New ADODB.Connection
Dim rs As New ADODB.Recordset
Dim i As Integer
cn.Open "Data Source=ART;DataTypeCompatibility=80;MARS Connection=True;"
Set rs = cn.Execute("SELECT * FROM JobDetail WHERE JobID = 2558 ORDER BY SeqNo ASC")
Do While Not rs.EOF
For i = 1 To rs.Fields.Count
Debug.Print rs.Fields(i).Name & ": " & rs.Fields(i).Value
Next i
rs.MoveNext
Loop
End Sub
Example Output:
SeqNo: 1
CommandID: 2
Parameter1: 2 Daily Report é [& some other chars not showing on here]
Parameter2: [Null]
Parameter3: [Null]
Parameter4: [Null]
Description: Daily Report
Active: False
Expected Output:
SeqNo: 1
CommandID: 2
Parameter1: SELECT Day_Number ,Day_Text ,Channel_Group_ID [...etc]
Parameter2: [Null]
Parameter3: [Null]
Parameter4: [Null]
Description: Daily Report
Active: False
So, it's grabbing bits of data from other fields instead of the correct data (in this case, it's an SQL statement)
I then tried casting the nvarchar(max) fields as text at source
View Created:
CREATE VIEW TestWithCast
AS
SELECT jd.JobID, jd.SeqNo, jd.CommandID
,cast(jd.Parameter1 as text) as Parameter1
,cast(jd.Parameter2 as text) as Parameter2
,cast(jd.Parameter3 as text) as Parameter3
,cast(jd.Parameter4 as text) as Parameter4
,jd.[Description]
,jd.Active
FROM JobDetail jd
Now, I initially had some luck here - using the same code as above does bring back data - but when I use this code in my main code (which jumps in & out of other procedures); as soon as I've queried the first result of the recordset, it appears to wipe the rest of the records / fields, setting them to Null. I also tried setting the value of each field to a variable whilst the rest of the vba runs before getting the next record - but this doesn't help either.
It almost feels like I need to dump the recordset into a local Access table, and query from there - which is a bazaar workaround for what is already a workaround (by casting as text).
I there something I'm completely missing here, or do I indeed need to cast as text and load to a local table?
Thanks for any help - it's driving me mad!
ps. Hope I've given the right level of detail / info - please let me know if I missed anything key.
EDIT:
Yikes, I think I've done it / found the issue...
I changed the driver to SQLSRV32 (v6.01) - and seems to work fine directly against the text casted field.
So... why would it work with an older driver but not the newer 'recommended' (by various sources I read) as the one to use.
And... will there be a significant drawback in using this over the native client?
EDIT 2:
Ok, I've tried a few drivers on a few machines, in each case with both the TEXT CASTING and Directly to VARCHAR MAX..
[On my windows 7 machine w/ SQLSMS 2008]
SQL Native Client 10.0 - Neither method works reliably with this driver
SQL Server 6.01 - BOTH methods appear to work reliably - further testing needed though
[On our production server w/ SQLS 2005]
SQL Native Client (v2005.90) - Does not work at all with varchar(max), but DOES work with text casting
SQL Server (v2008.86) - BOTH methods appear to work reliably - further testing needed though
This should make deployment interesting!
It's not a real answer, because I did not test it, but ... You are using a "DataTypeCompatibility=80" parameter in your connection. As far as I know, DataTypeCompatibility=80 refers to SQL Server 2000, where the nvarchar(max) field type was still not implemented.
I had the same problem, solved it by converting the field to an nvarchar(1000). Would be an easy, compatible solution for your problem if 1000 chars is enough.

VB.NET - What is wrong with my SQLinsert? Access database

I have a database with a bunch of stuff in it, and right now I'm reading in data, doing some processing on it, and then sticking it in a new database. My code generates this string:
query_string = "INSERT INTO OrgPhrase (EXACT_PHRASE,Org_ID) VALUES (HELLO,123)"
Then it's used this way:
Dim InsertCmd = New System.Data.OleDb.OleDbCommand(query_string, connection)
InsertCmd.ExecuteNonQuery()
The associated database (OLEdb connection) exists and opens fine, with all the tables and columns it's trying to work with already existing. The error message I get is "No value given for one or more required parameters"
Am I missing something? Did I spell something wrong? I don't have a ton of experience with database work, but I've never had this trouble inserting before.
I believe the query should be
query_string = "INSERT INTO OrgPhrase (EXACT_PHRASE,Org_ID) VALUES ('HELLO',123)"
Also, it may happen that the table has more than 2 columns that are NOT NUll and the values to them are required.
Consider parameterizing the query string. There are a couple of reasons for this. First, you can pass in the values without having to worry about whether or not you need single quotes. Second, you prevent SQL injection.
query_string = "INSERT INTO OrgPhrase (EXACT_PHRASE,Org_ID) VALUES (#ExactPhrase,#OrgId)"
You then create parametes based on the parameter names in the string. Unless, of course, your query string is always the same values, but that sounds a bit too hardcoded to be good.

Copy data from lookup column with multiple values to new record Access 2007

I am copying a record from one table to another in Access 2007. I iterate through each field in the current record and copy that value to the new table. It works fine until I get to my lookup column field that allows multiple values. The name of the lookup column is "Favorite Sports" and the user can select multiple values from a dropdown list.
I believe the values of a multivalued field are stored in an array but I cannot access the values in VBA code! I've tried myRecordset.Fields("myFieldName").Value(index) but it didn't work. I don't understand how Access stores multiple values in one field.
I saw something about ItemsSelected on another forum but I don't know what Object is associated with that method.
Thanks for any help!
I would recommend against using multivalue fields for precisely the reason you're running into, because it's extremely complex to refer to the data stored in this simple-to-use UI element (and it's for UI that it's made available, even though it's created in the table design).
From your mention of "ItemsSelected," you seem to be assuming that you access the data in a multivalue field the same way you would in a multiselect listbox on a form. This is not correct. Instead, you have to work with it via a DAO recordset. The documentation for working with multivalue fiels explains how to do it in code, something like this:
Dim rsMyField As DAO.Recordset
Set rsMyField = Me.Recordset("MyField").Value
rsChild.MoveFirst
Do Until rsChild.EOF
Debug.Print rsChild!Value.Value
rsChild.MoveNext
Loop
rsChild.Close
Set rsChild = Nothing
Now, given that you can usually access the properties of a recordset object through its default collections, you'd expect that Me.Recordset("MyField").Value would be returning a recordset object that is navigable through the default collection of a recordset, which is the fields collection. You'd think you could do this:
Me.Recordset("MyField").Value!Value.Value
This should work because the recordset returned is a one-column recordset with the column name "Value" and you'd be asking for the value of that column.
There are two problems with this:
it doesn't actually work. This means that Me.Recordset("MyField").Value is not reallly a full-fledged recordset object the way, say, CurrentDB.OpenRecordset("MyTable") would be. This is demonstrable by trying to return the Recordcount of this recordset:
Me.Recordset("MyField").Value.Recordcount
That causes an error, so that means that what's being returned is not really a standard recordset object.
even if it did work, you'd have no way to navigate the collection of records -- all you'd ever be able to get would be the data from the first selected value in your multivalued field. This is because there is no way in this shortcut one-line form to navigate to a particular record in any recordset that you're referring to in that fashion. A recordset is not like a listbox where you can access both rows and columns, with .ItemData(0).Column(1), which would return the 2nd column of the first row of the listbox.
So, the only way to do this is via navigating the child DAO recordset, as in the code sample above (modelled on that in the cited MSDN article).
Now, you could easily write a wrapper function to deal with this. Something like this seems to work:
Public Function ReturnMVByIndex(ctl As Control, intIndex As Integer) As Variant
Dim rsValues As DAO.Recordset
Dim lngCount As Long
Dim intRecord As Integer
Set rsValues = ctl.Parent.Recordset(ctl.ControlSource).Value
rsValues.MoveLast
lngCount = rsValues.RecordCount
If intIndex > lngCount - 1 Then
MsgBox "The requested index exceeds the number of selected values."
GoTo exitRoutine
End If
rsValues.MoveFirst
Do Until rsValues.EOF
If intRecord = intIndex Then
ReturnMVByIndex = rsValues(0).Value
Exit Do
End If
intRecord = intRecord + 1
rsValues.MoveNext
Loop
exitRoutine:
rsValues.Close
Set rsValues = Nothing
Exit Function
End Function
Using that model, you could also write code to concatenate the values into a list, or return the count of values (so you could call that first in order to avoid the error message when your index exceeded the number of values).
As cool as all of this is, and as nice as the UI that's presented happens to be (it would be really nice if they'd added selection checkboxes as a type for a multiselect listbox), I'd still recommend against using it precisely because it's so much trouble to work with. This just takes the problem of the standard lookup field (see The Evils of Lookup Fields in Tables) and makes things even worse. Requiring DAO code to get values out of these fields is a pretty severe hurdle to overcome with a UI element that is supposed to make things easier for power users, seems to me.
For a quick and dirty way of getting the values out of a multivalued ('complex data') column, you can use an ADO Connection with the Jet OLEDB:Support Complex Data connection property set to False e.g. the connection string should look something like this:
Provider=Microsoft.ACE.OLEDB.12.0;
Data Source=C:\dbs\TestANSI92.accdb;
Jet OLEDB:Engine Type=6;
Jet OLEDB:Support Complex Data=False
The multivaled type column will now be of type MEMO (adLongVarWChar) with each value separated by a semicolon ; character.
But that's only half the problem. How to get data into a multivalued column?
The Access Team seem to have neglected to enhance the Access Database Engine SQL syntax to accommodate multivalued types. The 'semicolon delimiter' trick doesn't work in reverse e.g.
INSERT INTO TestComplexData (ID, weekday_names_multivalued)
VALUES (5, 'Tue;Thu;Sat');
fails with the error, "Cannot perform this operation", ditto when trying to update via ADO recordset :(

Resources