Bulk Insert from VBA into SQL Server - sql-server-2008

I'm using SQL Server 2008 R2 and I've created a User Defined Type:
create type dbo.UDT as table (FirstName varchar(50) null, LastName varchar(50) null)
I've create a stored procedure that has this UDT as a parameter:
create procedure dbo.InsertIntoMyTableUsingUDT (#udt dbo.UDT readonly)
as
begin
set nocount on;
insert into dbo.MyTable (FirstName, LastName)
select FirstName, LastName from #udt;
return;
end
I would like to use VBA's ADO (ADO.COM/OLE DB) to bulk upload 10,000+ records using this stored procedure.
I've tried passing an ADODB.Recordset as a parameter in an ADODB.Command:
<code that creates ADODB.Recordset 'rst' here...>
Dim com as ADODB.Command
Set com = new ADODB.Command
With com
.ActiveConnection = "Provider=SQLOLEDB.1;Data Source=localhost;Initial Catalog=TestDB;Integrated Security=SSPI;"
.CommandText = "dbo.InsertIntoMyTableUsingUDT"
.CommandType = adCmdStoredProc
.Parameters.Refresh
.Parameters(1).Value = rst '<----the ADODB.Recordset created above
.Execute
End With
but I get the runtime error:
Arguments are of the wrong type, are out of acceptable range, or are in conflict with one another.
Checking the parameter's type in the Immediate Window I get:
?com.Parameters(1).Type
143
which I can't see in MSFT's list of OLE DB DataTypeEnums:
http://msdn.microsoft.com/en-gb/library/windows/desktop/ms675318(v=vs.85).aspx
So I tried passing in a 2-dimensional array instead of the ADODB.Recordset but then I got the error:
Bad variable type.
Both attempts failed and it's got me stumped.
I know it can be done in VB.NET using ADO.NET and the SqlParameterCollection.AddWithValue method.
I would like to know if there is a way of doing it in VBA using ADO.COM. Has anyone done it?
Alternatively, is there a different way of achieving the same thing (bulk upload rather than calling an ADODB.Command 10,000+ times in a loop)?
Cheers

Related

How to execute and query in VBA in MS Access using the ODBC Connector for MySQl?

The following VBA code will allow you to run a sql query in the current access database
I tried this in a MS Access file that is connected to a MySQL database through the ODBC Connector. However this code does not work, presumably because it's not a normal access DB.
How can I query the database using the existing ODBC connection?
EDIT: I always get a "Runtime error 3024 cannot find file"
Try 1
Dim mydb As Database
Dim myrs As DAO.Recordset
Set mydb = CurrentDb
Set myrs = mydb.OpenRecordset("select from blah blah...")
Try 2
Dim dbMine As DAO.Database
Set dbMine = CurrentDb
Dim qr As String
qr = "select count(*) as `result` from floatinp_appdb2.clientes where cliente_email = 'tiagovalente#float-in.pt';"
'debug.print qr
dbMine.Execute qr, dbFailOnError
Requirements are:
Linked table has a PK defined.
So, this should work:
Set myrs = mydb.OpenRecordset("select from blah blah...",dbOpenDynaset, dbSeeChanges)
Also:
any true/false columns (bit fields) should have default value of 0 set. (don't allow nulls for the true/false columns).
You can and should also consider adding a rowversion column, but above should suffice.
Though the error was "runtime error 3024 cannot find file", my mistake was including the database name before the table
e.g. Select * From DB2.Table1

VBA Code for Transferring ACCESS Table to SQL Server

I was trying to Transfer my Access Table Data over to SQL server but I am getting an error which says
Object Variable or With block variable not set
I've already connected my Access database with SQL server but now facing problem transferring data to SQL server with VBA code.
Can you please help me with it?
Here is my code:
Public Sub ADOtest()
Dim ADOConn As New ADODB.Connection
Dim ADOCom As New ADODB.Command
On Error Resume Next
ADOConn.ConnectionString = "Driver={SQL Server};Server=IT-TEMP2-8470P\SQLEXPRESS2014;Database=Discrepancy;Trusted_Connection=True;"
ADOConn.Open
Debug.Print ADOConn.State
Dim db As Database
Dim Discrepancy As Database
Set db = Discrepancy
Dim ID As Variant
db.Execute "INSERT INTO [ODBC;DRIVER=SQL Server;SERVER=IT-TEMP2-8470P\SQLEXPRESS2014;DATABASE=Discrepancy].SFTransfersDB ( ID, TO ) SELECT ID,TO FROM SFTransfersDB"
End Sub
I am getting error at the 2nd last line:
db.Execute "INSERT INTO [ODBC;DRIVER=SQL Server;SERVER=IT-TEMP2-8470P\SQLEXPRESS2014;DATABASE=Discrepancy].SFTransfersDB ( ID, TO ) SELECT ID,TO FROM SFTransfersDB"
It seems you are attempting an ad hoc distributed query. If server permits it, consider executing this statement after your ADO connection. No need for calling Access objects.
ADOConn.Execute "INSERT INTO dbo.SFTransfersDB ([ID], [TO])" _
" SELECT [ID], [TO] FROM OPENDATASOURCE('Microsoft.ACE.OLEDB.12.0', " _
"'Data Source=""C:\Path\To\Access\Database.accdb""')...SFTransfersDB;"
You declare two Database variables, but don't initialize them (you don't set them to anything):
Dim db As Database
Dim Discrepancy As Database
So this
Set db = Discrepancy
is the same as
Set db = Nothing
and so when you try to use db, you get the error that db is not set, which it isn't.
Thanks for your suggestions.
I got it correct after all.
I used "Set db = CurrentDb" and it worked perfect.

How to I stop a return parameter from a stored procedure from being truncated at 4000 characters

The question asked here:-
How long does Access keep unflushed transactions locally?
is about problems with some invoice generation, which is still causing problems. I am now trying to rewrite the same code to work entirely on the backend SQL Server databases using ADODB, specifically ADODB.Command and a some stored procedures.
One problem I am trying to resolve is that in the old system I created a local table (called tmpUKRepeatInvoices) in the client. I am making a similar table on the server. However the fields of this table are created from a join across two separate databases.
It is easier with my new system to populate the equivalent table on the server associated with the invoicing with a stored procedure and using a FOR XML PATH('') clause in that stored procedure return a comma separated list of customerIDs (the keys to the other database) using a query like so
SELECT #Subs = (SELECT ''''+SubsID+''',' FROM InvoicingData WHERE SessionID = #SessionID FOR XML PATH(''),TYPE).value('.','NVARCHAR(MAX)')
SELECT #Customers = LEFT(#Subs,LEN(#Subs)-1)
#Customers is an output parameter for this stored procedure of type NVARCHAR(MAX) and will contain the text I will use in the next query
I can run the stored procedure in Sql Server Management studio and It returns a string of 42000 characters
The next query then will be like so. and can be used to populate the working table with the number of items against each customer.
SQL = "SELECT COUNT(*) As NoItems, CustomerID FROM CustomerItems WHERE CustomerID IN(" & Customers & " )"
So I create the call in my Access VBA to run the stored procedure like so
Dim Customers As String
With cmd
Set .ActiveConnection = Conn
.CommandType = adCmdStoredProc
.CommandText = "CreateInvoicingData"
.Parameters.Append .CreateParameter("#SessionID", adVarWChar, adParamInput, 25, TempVars!SessionID)
.Parameters.Append .CreateParameter("#InvoiceDate", adDate, adParamInput, , Form_Company.InvoiceDate)
.Parameters.Append .CreateParameter("#Currency", adVarWChar, adParamInput, 15, "Pounds")
.Parameters.Append .CreateParameter("#Customers", adVarWChar, adParamOutput, 1000000000)
.Execute
Customers = .Parameters("#FleetCustomers").Value
End With
The problem that I am having is that the Customers string is truncated to 4000 characters. I tried setting the #Customers parameter to type adLongVarWChar instead but an error was thrown at the .Execute statement that said that "Data type Ox63 is a depreciated large object or LOB, but is marked as an Output parameter. Depreciated Large Objects are not supported as output parameters. Use Current Large Objects instead."
What type should I use for the #Customers Parameter so it won't get truncated or regarded as depreciated?. Note: I tried setting the size of this parameter to -1 but that just failed with an inconsistent parameter error message.

Un-escaping characters present in recordset that is bound to control in Access 2003

I have some data stored in SQL Server that contains apostrophes. Within the SQL Server table to apostrophe is escaped. For example the phrase "Roberts's thesis" is stored as "Robert''s Thesis". I use Access 2003 as a font end for the application and I use ADO to access the data stored in SQL Server using Stored Procedures.
The issue I am having is how to "un-escape" the double apostrophe when the data is retrieved using a recordset and then bound to a control. Here is some sample code.
Dim cnn As ADODB.Connection
Dim rs As ADODB.Recordset
Set cnn = New ADODB.Connection
cnn.ConnectionString = myConnectionString
cnn.Open
Set rs = New ADODB.Recordset
Set rs.ActiveConnection = cnn
rs.Source = "EXEC uspMyStoredProcedureName"
rs.LockType = adLockOptimistic
rs.CursorType = adOpenStatic
rs.CursorLocation = adUseClient
rs.Open
Set ListControl.Recordset = rs
Set rs = Nothing
Set cnn = Nothing
Do I have to "loop" through the recordset manually and un-escape the apostrophe? Thank you.
You don't have to unescape anything. The doubled-apostrophe form is only used for string literals inside SQL statements. The actual value inserted in the database by INSERT... 'Robert''s Thesis'; is Robert's Thesis, and that is the value you'll get out when you read it from a recordset grabbed from a SELECT.
If whatever's inside uspMyStoredProcedureName is doing something weird to cause doubled apostrophes to get returned then it's broken and needs fixing; if you have Robert''s Thesis as an actual value in the database, then you've inserted broken data and you should be looking at fixing the code that's done that, and cleaning up the data.
If you have no possibility to change uspMyStoredProcedureName, then You will have to loop through the resultset.
Otherwise, changing the select statement in the stored procedure from col to Replace(col, '''''', '''') would do the trick.
Yes, there are six single quotes in the second argument and four in the third: Two enclose a string, and within that, each quote is doubled to escape it.
BTW: Why are the data stored with doubled quotes at all?

How to obtain value of auto_increment field in Access linked to MySQL?

I'm trying to modify and existing Access application to use MySQL as a database via ODBC with the minimal amount of recoding.
The current code will often insert a new record using DAO then obtain the ID by using LastModified. This doesn't work with MySQL. Instead I'm trying to use the approach using
SELECT * FROM tbl_name WHERE auto_col IS NULL
Suggested for Access in the MySQL documentation. However if I set up a sample table consisting of just an id and text data field and execute this
CurrentDb.Execute ("INSERT INTO tbl_scratch (DATA) VALUES ('X')")
Set rst = CurrentDb.OpenRecordset("SELECT id FROM tbl_scratch WHERE id IS NULL")
myid = rst!id
Id is returned as null. However if I execute
INSERT INTO tbl_scratch (DATA) VALUES ('X');
SELECT id FROM tbl_scratch WHERE id IS NULL;
using a direct MySQL editor then id is returned correctly, so my database and approach is fine but my implementation inside Access must be incorrect. Frustratingly the MySQL documentation gives the SQL statement to retrieve the id as an example that works in Access (as it states LAST_INSERT_ID() doesn't) but gives no further details.
How might I fix this?
Solved (and blogged) as below
I've been implementing an upgrade for a set of Access databases to replace the Access file database with MySQL linked by ODBC. Everything seems to be going remarkably smoothly except for the common concept of inserting a record into a table with an auto-increment id column and retrieving the value of the id just created. Of course on PHP or the like one would just use the
SELECT LAST_INSERT_ID()
Function to retrieve the ID. However the MySQL documentation itself says that this doesn't work for certain ODBC applications such as Delphi or Access and suggests using
SELECT * FROM tbl WHERE auto IS NULL;
Unfortunately this simply didn't work for me when called from inside the Access application I was working with, and there seems to be several comments around the web that indeed this is unreliable as Access may drop the data connection and reconnect behind the scenes - thus invalidating the call.
As an alternative I decided to use a MySQL function to add a blank record to the table, returning the id which Access would then use to update the record (which fits well with the code-style of the existing application). Unfortunately this apparently straightforward work-around fails to be simple either as long-standing bugs in MySQL make finding valid code that can both send and return a variable something of a challenge. Several examples on the web will work within the limited domain of using either just IN or OUT variables but fail to work with both.
My final solution, which works on the MySQL 5.1 and Access 2003 combination I am deploying, is as follows
MySQL procedure
DELIMITER $$
CREATE
PROCEDURE `alicedata`.`sp_ins_identity`(IN tablename VARCHAR(50))
BEGIN
SET #result = 0;
SET #sqlproc = CONCAT("INSERT INTO ",tablename," () VALUES ();");
PREPARE s1 FROM #sqlproc;
EXECUTE s1;
DEALLOCATE PREPARE s1;
SELECT LAST_INSERT_ID();
END$$
This procedure is useful in that it will insert a row and return the id for any table where a row contains all null fields or non-null fields with defaults defined. To call this I use the following function:
Public Function InsertMySQLIdentityRow(DSN As String, Tablename As String) As Integer
On Error GoTo Err_InsertMySQLIdentity
Dim cnnSQL As New ADODB.Connection
Dim cmdSQL As ADODB.Command
Dim pid As Integer
Dim rs
Dim strSQL As String
' initialize
pid = 0
' set up ADO connection
Set cnnSQL = New ADODB.Connection
cnnSQL.Open DSN
' execute the procedure - note that assembling by parameter fails to handle IN or OUT correctly
Set cmdSQL = New ADODB.Command
cmdSQL.ActiveConnection = cnnSQL
strSQL = "call sp_ins_identity('" & Tablename & "');"
Set rs = CreateObject("ADODB.Recordset")
Set rs = cnnSQL.Execute(strSQL)
If Not rs.EOF Then
pid = rs(0)
End If
' clean up
Set rs = Nothing
Set cmdSQL = Nothing
cnnSQL.Close
Set cnnSQL = Nothing
Exit_InsertMySQLIdentity:
InsertMySQLIdentityRow = pid
Exit Function
Err_InsertMySQLIdentity:
MsgBox Err.Number & Err.Description
Resume Exit_InsertMySQLIdentity
End Function
This code is somewhat unusual in that normally, on MSSQL, you would use a parametrized procedure call, but due to bugs in the MySQL ODBC (or at least incompatibilities with Access) the above seems to be the only way that allows both data to be passed and returned.