I need to create a pass-through query to SQL Server. I have a functioning pass-through query which only runs from the navigation bar or query design in Access, and it only creates a single record. I have dozens of records to insert into a SQL Server table based on values in an Access table. Thus, I need to loop through the Access table in code, and for each record there create the SQL string and execute it. I've got the looping working, just need the proper code to connect to the SQL database and execute the SQL string. Here's the ODBC Connect String in the pass-through query that works, with the PW and DB starred out: "ODBC;DSN=jobboss32; UID=support; PWD=****;DATABASE=****". The code below gives a Data Type Conversion Error.
Dim strFuturePTOSource, strPassThruInsert, strEmpName, strPTODate, strUpdated As String
Dim datPTODate As Date, lngPTOHours As Long
Dim rs As New ADODB.Recordset, q As QueryDef
strFuturePTOSource = "qryROBERT" 'temporary data set to test, change to actual after working
rs.Open strFuturePTOSource, CurrentProject.Connection, adOpenDynamic, adLockOptimistic
With rs
If Not .BOF And Not .EOF Then
.MoveLast
.MoveFirst
While (Not .EOF)
strEmpName = rs.Fields("EmpName")
strEmpName = "'" & strEmpName & "', "
datPTODate = rs.Fields("PTODate")
strPTODate = "'" & Format(datPTODate, "mm/dd/yyyy") & "'"
lngPTOHours = rs.Fields("PTOHrs") * 60
strUpdated = "'" & Format(Now(), "mm/dd/yyyy") & "'"
strPassThruInsert = "INSERT INTO Attendance (Attendance, Employee, Work_Date, Regular_Minutes, Attendance_Type, Lock_Times, Source, Last_Updated) VALUES (NewID(), "
strPassThruInsert = strPassThruInsert & strEmpName & strPTODate & ",lngPTOHours,2,-1,0, '" & Now() & "');"
Set q = CurrentDb.CreateQueryDef("", strPassThruInsert)
CurrentDb.QueryDefs(q).Execute
.MoveNext
Wend
End If
.Close
End With
First, you not given ANY valid reason to insert with a pass-through query.
And in fact, since EACH insert will be a 100% and separate and “discrete” insert?
Well, the time for the query processor to setup, parse the syntax, and do the insert?
Well, you not achieve ANY gains in performance, and in fact it will run no faster than a plain Jane and standard insert using DAO recodsets.
As a result:
You are knowingly as a developer wasting all kinds of time pursuing this approach and this is AFTER having been given this advice on places such as UA.
And if you knowingly are wasting developer time, and seeking an approach that costs more developer time, does not speed up your insets, then you are with full knowledge seeking an approach that costs more time, and more resources than necessary.
I suppose if you are paying for this time, and don’t care, or perhaps you are doing this as a learning exercise, then fine. But as for a working solution, the approach does not make sense, takes more code and time to develop and fix, and you are doing this will full knowledge as to the increased cost and time without any benefits I can see, or think of.
So then this begs the question?
Why would you attempt a solution will FULL knowledge that costs more time, costs more effort, and does not increase performance?
You do not given any reason as to the added benefits of this approach.
The following code is far less, far more simple, will run FASTER than your given approach. And FAR better is the date conversions that you have (are WRONG), are a non-issue.
So, you have to cook up a VERY good reason as to WHY you are perusing this course of action when:
It will not run faster than using the code I post here
(In fact, such a PT query will run slower – and by A WIDE margin).
You have errors in your date formats – you have to use ISO sql server format
(But, if you use ODBC and let the record set do the translation, then ZERO formatting and ZERO re-formatting of the date and date time values you have is NOT required).
So, until such time you explain why you are willing with full knowledge to write more code, cost more time, and cook up a solution that wastes time, and will not run faster?
Then I see little if any reason to pursue your approach?, right?
So, you need to come up with a VERY good reason as to why you are insisting on this pass-through query approach, and an approach that will run NOT run faster than what I am posting here... And in fact, as I point out, the posted solution will run MUCH faster. (Not I said a wee bit faster – but MUCH faster – in fact on a multiple order (not %, but factors of times faster). I can explain WHY this is the case (better speed) if you are wondering why.
In the meantime, you are going to have to figure out how to sleep with full knowledge of pursuing a solution that is going to cost more in developer time, and not yield any benefits in terms of speed, or even maintenance and writing of such code.
This code eliminates the date error conversions you have, and WILL RUN faster than your posted code and solution:
Dim strFuturePTOSource As String
Dim rstFrom As DAO.Recordset ' from table
Dim rstTo As DAO.Recordset ' SQL server linked table.
Set rstFrom = CurrentDb.OpenRecordset("qryROBERT", dbOpenDynaset, dbSeeChanges)
Set rstTo = CurrentDb.OpenRecordset("Attendance", dbOpenDynaset, dbSeeChanges)
Do While rstFrom.EOF = False
With rstTo
.AddNew
!Attendance = NewID()
!Employee = rstFrom!EmpName
!Work_Date = rstFrom!PTODate
!Regular_Minutes = 600
!Attendance_Type = 2
!Lock_Times = -1
!Source = 0
!Last_UPdated = Now
.Update
End With
rstFrom.MoveNext
Loop
rstTo.Close
rstFrom.Close
Edit
Given that the poster HAS made a good case that a PT query is to be used?
then this code should work:
We assume that you ALREADY created a working PT query. (and it has return records set = false). I tend to create ONE PT query in Access, and then any and all places in code can re-use that PT query at well. Also if NewID() is a scalar function (t-sql), as noted, it MUST be prefixed with dbo. So, we have to use dbo.NewID()
So, the code that is "close" or that I would suggest is this:
Dim rs As DAO.Recordset
Dim strSQL As String
Dim strSQLS As String
strSQLS = "INSERT INTO Attendance (Attendance, Employee, Work_Date, Regular_Minutes, " & _
"Attendance_Type, Lock_Times, Source, Last_Updated) " & _
"VALUES (dbo.NewID(),"
Set rs = CurrentDb.OpenRecordset("qryROBERT")
With rs
Do While .EOF = False
strSQL = strSQLS
strSQL = strSQL & qu(!EmpName) & "," & quDate(!PTODate) & _
"," & quDate(Date) & "," & (!PTOHrs * 60) & ",2,-1,0," & quDate(Now())
With CurrentDb.QueryDefs("MyPassQuery")
.SQL = strSQL
.Execute
End With
.MoveNext
Loop
rs.Close
End With
In addtion to the above, I used two helper functions to format the date, as it is a pain to do this "in-line" code, so, I have qu() (for strings), and qudate() for dates.
Public Function qudate(myDate As Variant) As String
' returns a formatted string of date, ISO format, for sql sesrver.
' format is yyyy-mm-dd regardless of local date settings
If IsNull(myDate) = True Then
qudate = ""
Else
' use ISO date format
qudate = "'" & Format(myDate, "yyyy-mm-dd HH:NN:SS") & "'"
End If
End Function
And our qu() function
Function quS(vText As Variant) As String
' takes a string and surrounds it with single quotes
If IsNull(vText) = False Then
If InStr(vText, Chr(34)) > 0 Then
vText = Replace(CStr(vText), Chr(34), "'")
End If
End If
quS = "'" & vText & "'"
End Function
Edit 2
So, the steps are:
From sql studio, get a example working that inserts data. Once you have a working insert command, then take that same (and known) working command and cut + paste it into an access query - but just ensure that the query on the Access side is created and set as pass-though. Once again, test running this PT query. If the existing sql worked in SSMS, then it will work in Access 100% exactly with the SAME query. Once that is working?
so, whatever name you gave this query is what you use in place of MyPassQuery. You can give it any name you want, but you have to use the correct (same) name in your VBA code that going to use/set the sql for that PT query. So, each loop run will in fact overwrite what is in the query, and then you do the .Execute to run it.
Well, then you run your code. And on this line for testing?
.SQL = strSQL
Do this:
debug.print strSQL
.SQL = strSQL
So, as you single step though that code, the sql the VBA is creating will have to match the known working sql you had. So, if the string output has any syntax errors or does not look 100% the same as that known working sql? Well, then you have to tweak the VBA code until such time it spits out the SAME sql string that you know works.
I have created a macro/some VBA to UPDATE a SQL Server table which works fine.
In short the code pulls a defined amount of records from the the table to excel and then the end user updates some specific information and then clicks update. A connection is created to the table and an SQL update statement runs which updates the relevant records.
The problem is where the user has not had to update a NULL field (NULL is in the SQL Server table but shows as 'empty' in Excel), when the use clicks update the SQL statement is forcing the NULL to an 'empty' entry.
To get round this I would like my code in the For Each statement to check if the cell/record is NULL or Empty and to skip to the NEXT row so the SQL Execute command is not carried out.
Here is the VBA in question:
cnn.Open cnnstr
Dim row As Range
For Each row In [tbl_data].Rows
uSQL = "UPDATE BREACH_DATA SET [VAL_BREACH_REASON] = '" & (row.Columns(row.ListObject.ListColumns("VAL_BREACH_REASON").Index).Value) _
& "' ,[VAL_BREACH_DETAIL] = '" & (row.Columns(row.ListObject.ListColumns("VAL_BREACH_DETAIL").Index).Value) _
& "' ,[VAL_VALID] = '" & (row.Columns(row.ListObject.ListColumns("VAL_VALID").Index).Value) _
& "' ,[VAL_NOTES] = '" & (row.Columns(row.ListObject.ListColumns("VAL_NOTES").Index).Value) _
& "' WHERE [ATD_NUMBER] = '" & (row.Columns(row.ListObject.ListColumns("ATD_NUMBER").Index).Value) & "'"
'Debug.Print uSQL
cnn.Execute uSQL
Next
cnn.Close
Set cnn = Nothing
Any suggestions
Kind Regards
Dino
You are updating SQL Server data directly with strings from a cell. This is a classic example of opening a door for injection attacks - users can do all kinds of bad, bad things to your database. But given that you fix that here is a way to check that each cell is not empty or null (I assume if one of the fields are not empty or null you want to update...):
if not
(
(isempty(row.Columns(row.ListObject.ListColumns("VAL_BREACH_REASON").Index).Value)
and isnull(row.Columns(row.ListObject.ListColumns("VAL_BREACH_REASON").Index).Value)
and do same for the other cell values....
)
then update....
This code currently is using a DROP TABLE to transfer data from Access to SQL Server, that is when it was intended for one person to use. Now it is going to be used by multiple people so I need to alter the code to APPEND. I am trying to figure out how to get it to check the destination table to see if the record already exists, if it does it will skip over it, if not it will write that employees data to the table. The SQL table is prebuilt and it has a ROW ID column and TIME STAMP column that the access table does not have in order to keep track of the records being entered.
The code currently looks like:
Public Function Update()
Dim cdb As DAO.Database, qdf As DAO.QueryDef
Dim err As DAO.Error
Const DestinationTableName = "AC_CDData"
Const ConnectionString = _
"ODBC;" & _
"Driver={SQL Server Native Client 10.0};" & _
"Server=SERVER;" & _
"Database=DB;" & _
"UID=ID;" & _
"PWD=PW;"
Set cdb = CurrentDb
Set qdf = cdb.CreateQueryDef("")
qdf.Connect = ConnectionString
qdf.SQL = _
"IF EXISTS " & _
"(" & _
"SELECT * FROM INFORMATION_SCHEMA.TABLES " & _
"WHERE TABLE_NAME='" & DestinationTableName & " '" & _
") " & _
"DROP TABLE [" & DestinationTableName & "]"
qdf.ReturnsRecords = False
On Error GoTo Update_qdfError
qdf.Execute dbFailOnError
On Error GoTo 0
Set qdf = Nothing
Set cdb = Nothing
DoCmd.TransferDatabase _
acExport, _
"ODBC Database", _
ConnectionString, _
acTable, _
"CDData", _
DestinationTableName, _
False
Exit Function
Update_qdfError:
For Each err In DAO.Errors
MsgBox err.Description, vbCritical, "Error " & err.Number
Next
End Function
The DoCmd.TransferDatabase does a complete transfer of the data from the Access table, to the SQL Server database. This function can not do partial inserts based on existing records.
What you can do, however, is export the data into a temporary new table (without dropping the existing table), followed by executing an SQL MERGE statement, to incorporate the new records of the temporary table, with the existing table.
You'll need to know how to perform SQL queries from VBA against your SQL database, and you'll need to know how to use the SQL MERGE statement. A google search will quickly give you the answer to both.
Just setup a simple standared linked table to SQL server in Access.
Since the column of the existing record is a primary key (or simply an index set = unique), then you only need ONE LINE of code to append your data.
This will work:
CurrentDb.Execute "INSERT INTO AC_CDData SELECT * FROM CDData;"
Any row that already exists will be ignored due to key (or index) violation. Thus only new non existing records will be appended with the above one line of code.
Edit:
As for the target table being a SQL linked table? Your application on startup does not care if you have some linked tables. Your application does not use nor care about such linked tables unless you use them.
The assuming here is you have a local data table. The linked table ONLY comes into play when you going to do the export. As noted you cannot export unless you are connected to SQL server. The “code” or testing if you are connected is not dealt with in this question but even if it was part of the question in both cases you still have to test for a connection and once again when you do finally determine you have a connect and you decide to export then again the linked table as per above will work.
So there is really no reason I can think of as to why having a linked table that is ONLY used during the append to the SQL table will be any kind of issue or problem.
As stated, then the one line of VBA code should suffice here.
In case of concurrent usage the process need to be changed:
DestinationTableName must be unique per session (thread) and need to be changed (from constant to variable)
Tabel with DestinationTableName neame need and need to be dropped before the export data (as it was in current solution).
DoCmd.TransferDatabase will not change - the data will be exported into the unique temp table.
For simplifying the Merge process the stored procedure can be written on the SQL Server side (with all needed to APSERT logic) with the source table name as parameter. This SP need to be called after the DoCmd.TransferDatabase completion
In this case the flow will be the following:
DestinationTableName unique name generation
Dropping this table on the SQL Server side (qdf.Execute ...)
Export data to the temp table (DoCmd.TransferDatabase ...)
Merge data in the SQL Server side (call to created SP with needed logic of update / insert)
I am trying to read data from an Excel table into a DataSet however I get an error: "No value given for one or more required parameters." I've done some research and learned that this error usually occurs when a required value is empty ("" in the case of a string) or null. Below is my code.
cnExcel = New System.Data.OleDb.OleDbConnection( _
"provider=Microsoft.Jet.OLEDB.4.0; " & _
"data source=" & FileName & "; " & _
"Extended Properties=Excel 5.0;")
cdImport = New System.Data.OleDb.OleDbDataAdapter( _
"select * from [" & cSheetName & "$] order by StoreID, ItemID", cnExcel)
dsImport = New System.Data.DataSet
cdImport.Fill(dsImport)
cnExcel.Close()
The error is occuring on cdImport.Fill(dsImport) presumably because there is nothing to fill dsImport with.
After doing some debugging I find that the Server Version field of cnExcel is: error: an excpetion of type: {System.InvalidOperationException} occured
What am I doing wrong?
Just in case anyone was wondering about how this got resolved:
I wound up just sending the excel file as a csv to my server. The server then runs a stored procedure on the file which returns a table to my application. A dataset is then filled from this table.
You may need to put additional quotes around the Extended Properties=Excel 5.0; part of your connection string (see connectionstrings.com for more information)
Started getting this error it seems since we upgraded to SQL Server 2008.
When inserting into the db and then returning the identity i get a 'Item cannot be found in the collection corresponding to the requested name or ordinal' error.
Here is the code:
SQL = "INSERT INTO PageFeatures(nPageFeatureFlagId,nPageFeatureFeatureId,nPageFeaturePageId) VALUES(" & nTemplateFlagId & "," & nFeatureId & "," & nPageId & "); SELECT SCOPE_IDENTITY() As nPageFeatureId;"
objrs.open SQL,objConn,1,1
nPageFeatureId = objrs("nPageFeatureId")
objrs.close
The insert is working as the record is in the db. It's not returning the id for some reason. It works fine and returns the id when running in SSMS. But ASP can't see the returned id so some reason.
Any help would be greatly appreciated!
You may have to try moving the recordset on? e.g.
SQL = "INSERT INTO PageFeatures(nPageFeatureFlagId,nPageFeatureFeatureId,nPageFeaturePageId) VALUES(" & nTemplateFlagId & "," & nFeatureId & "," & nPageId & "); SELECT SCOPE_IDENTITY() As nPageFeatureId;"
objrs.open SQL,objConn,1,1
objrs.NextRecordset
nPageFeatureId = objrs("nPageFeatureId")
objrs.close
Raj's comment about SQL injection is still relevant. :)
EDIT: Elaboration is that there are two recordsets in play here. The first is an empty one created by the insert statement, the second one is caused by everything after the semi-colon ;. This is the field you want. So initially, your objrs is attached to the first of its collection of recordsets (not rows in a recordset), you move to the NextRecordset - and you can then deal with that how you please.
Sorted:
SQL = "INSERT INTO PageFeatures(nPageFeatureFlagId,nPageFeatureFeatureId,nPageFeaturePageId) VALUES(" & nTemplateFlagId & "," & nFeatureId & "," & nPageId & ");"
objConn.execute(SQL)
Set oReturnValueRS = objConn.Execute("SELECT SCOPE_IDENTITY()")
nPageFeatureId = oReturnValueRS(0).Value
oReturnValueRS.close : set oReturnValueRS = nothing