i'm trying to use the code below to copy data from a sql (2008 r2) table to multiple sheets in excel 2003 - there are currently c420000 records, expanding at around 1000 a week. this is the requirement, i have no option to use access or later versions of excel for the output. i have been searching for some time and can find many threads on different forums relating to the same or similar issues but nothing specific enough to meet my requirements or help me resolve the issue.
what happens is the code will work but slows noticeably after around 30000 rows. i think the issue is the fact there are over 100 columns - i tested the code by selecting 6 or 7 columns and it returns a full dataset as required within an acceptable time period.
the code slows/hangs at the copyfromrecordset stage. if i break out of the code an error (-2147467259; Method 'CopyFromRecordset' of object 'Range' failed) is given but the code hasn't actually failed (yet), ie it can be continued without major issues.
i have not been able to complete the code for the full recordset and the longest i have let it run (2 hours) only completed around 50% - 60%.
can anybody shed any light on how i might be able to negate the problem with the process as it stands grinding to a painfully slow pace or suggest another method i might use? any help/suggestions gratefully appreciated
Sub DATA_Import(Frequency As String)
Dim sCon As String ' building string for the connection property
Dim sSQL As String ' building string for the SQL property
Dim rsData As ADODB.Recordset ' reference made to latest ADO library - 2.8
Dim cnxEWMS As ADODB.Connection ' reference made to latest ADO library - 2.8
Dim lWScount As Long
Dim lRow As Long, lCol As Long ' holders for last row & col in data
Dim c As Range ' identifies where flags data begins - should be constant but you never know!
Dim Cx As Long ' for looping through the flags columns to change blanks to 0
Dim wbNew As Workbook ' the final destination file!
Dim sFileDate As String ' the date for naming the output file
Dim wsNotes As Worksheet ' notes sheets for product
Dim wsCover As Worksheet ' cover sheet for product
Worksheets("Headings").Cells.Delete
' using windows authentication
' won't work where user is not listed on SQL server
sCon = "Provider=SQLOLEDB;" & _
"Data Source=SOMESERVER;" & _
"Initial Catalog=SomeDatabase;" & _
"Integrated Security=SSPI"
' identify frequecy for reporting and build SQL
' daily data is live records only
If Frequency = "daily" Then
sSQL = "SELECT * " & _
"FROM tblMainTabWithFlagsDaily " & _
"WHERE status='LIVE';"
Else
'weekly - all records split over multiple sheets
sSQL = "SELECT *" & _
"FROM tblMainTabWithFlagsDaily;"
End If
' create and open the connection to the database
Set cnxEWMS = New ADODB.Connection
With cnxEWMS
.Provider = "SQLOLEDB;"
.ConnectionString = sCon
.Open
End With
' create and open the recordset
Set rsData = New ADODB.Recordset
rsData.Open sSQL, cnxEWMS, adOpenForwardOnly, adLockReadOnly
With Application
' if construct used for debugging/testing when called from module1
If Not TestCaller Then
.ScreenUpdating = False
End If
.Calculation = xlCalculationManual
End With
If Not rsData.EOF Then
' create header row 'dummy' sheet
For lCol = 0 To rsData.Fields.Count - 1
With Worksheets("Headings").Range("A1")
.Offset(0, lCol).Value = rsData.Fields(lCol).Name
End With
Next
Set c = Worksheets("Headings").Rows("1:1").Cells.Find("warrflag_recno")
' copy data into workbook and format accordingly
Do While Not rsData.EOF
If wbNew Is Nothing Then
' create the new "product" workbook
Worksheets("Headings").Copy
Set wbNew = ActiveWorkbook
Else
lWScount = wbNew.Worksheets.Count
ThisWorkbook.Worksheets("Headings").Copy after:=wbNew.Worksheets(lWScount)
End If
With wbNew.Worksheets(lWScount + 1)
.UsedRange.Font.Bold = True
If Frequency = "daily" Then
.Name = "Live" & Format(lWScount + 1, "0#") ' shouldn't need numerous sheets for live data - ave 15k - 16k records
Else
.Name = "Split" & Format(lWScount + 1, "0#")
End If
' THE REASON WE'RE ALL HERE!!!
' copy from recordset in batches of 55000 records
' this keeps hanging, presumably because of number of columns
' reducing columns to 6 or 7 runs fine and quickly
.Range("A2").CopyFromRecordset rsData, 55000
' the remainder of the code is removed
' as it is just formatting and creating notes
' and cover sheets and then saving
' tidy up!
With Application
.DisplayAlerts = True
.ScreenUpdating = True
.Calculation = xlCalculationAutomatic
End With
rsData.Close
Set rsData = Nothing
cnxEWMS.Close
Set cnxEWMS = Nothing
Set c = Nothing
Set wsNotes = Nothing
Set wsCover = Nothing
End Sub
You can usually get quite a reasonable speed with ADODB like so:
''The data source z:\docs\test.accdb is not used, it is only there to get a
''working string.
strCon = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=z:\docs\test.accdb"
cn.Open strCon
''This selects into an existing workbook with a new sheet name, any name that does
''not already exist will work. The ODBC connection to SQL Server is whatever you
''use for ODBC connection.
ssql = "SELECT * INTO [Excel 8.0;HDR=YES;DATABASE=Z:\Docs\Test.xlsx].[Sheet7] " _
& "FROM [ODBC;DRIVER=SQL Server Native Client 11.0;SERVER=localhost\SQLEXPRESS; " _
& "DATABASE=MyDB;Trusted_Connection=Yes;].MyTable"
cn.Execute ssql
Related
I am trying to pass over 37K values in the WHERE clause using IN operator through Excel VBA macro. However, due to resource limitation in Microsoft SQL Server, the query won't execute. I have tried to pass the values in an array in VBA as well as tried creating a list in in SQL. Neither are able to accommodate these many values. Can somebody help? Sharing the code I have tried below.
Dim i as Long
Dim str As String
Dim dict_pn
Set dict_pn = CreateObject("Scripting.Dictionary")
i = 1
With InputS
For i = 1 to 50000
str = Trim(.Cells(i, PN_Col).Value)
dict_pn.Add str, 0
Next
With rsPubs
' Assign the Connection object.
.ActiveConnection = cnPubs
.Open Worksheets("SQL Script").Range("A1") & "('" & Join(dict_pn.keys, "','") & "') " & Worksheets("SQL Script").Range("A2") & "('" & Join(dict_pn.keys, "','") & "') " & Worksheets("SQL Script").Range("A3") & "('" & Join(dict_pn.keys, "','") & "') "
ActiveWorkbook.Worksheets("SQL Results").Range("A3").CopyFromRecordset rsPubs
You should transmit data to SQL Server like in related post
Sub UpdateTable()
Dim cnn As Object
Dim wbkOpen As Workbook
Dim objfl As Variant
Dim rngName As Range
Workbooks.Open "C:\your_path_here\Excel_to_SQL_Server.xls"
Set wbkOpen = ActiveWorkbook
Sheets("Sheet1").Select
Set rngName = Range(Range("A1"), Range("A1").End(xlToLeft).End(xlDown))
rngName.Name = "TempRange"
strFileName = wbkOpen.FullName
Set cnn = CreateObject("ADODB.Connection")
cnn.Open "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & strFileName & ";Extended Properties=""Excel 12.0 Xml;HDR=Yes"";"
nSQL = "INSERT INTO [odbc;Driver={SQL Server};Server=Server_Name;Database=[Your_Database].[dbo].[TBL]]"
nJOIN = " SELECT * from [TempRange]"
cnn.Execute nSQL & nJOIN
MsgBox "Uploaded Successfully"
wbkOpen.Close
Set wbkOpen = Nothing
End Sub
and then you can do any you want - use operator IN or LEFT JOIN whith check for null on SQL server side.
You can use temporary table if you will not close connection - temp table exists only in session level.
This is way from VBA code.
Second way - you can use SSIS package to work with excel files. In package you will have same steps - data frlow to transmit data from excel to server and then run sql code to join data. Or you can use join block inside SSIS package.
Third way that you can implement if data in SQL server less than excel row count limit. You can import data from SQL into Excel sheet, and than use VLOOKUP() function to join tables and find related data.
I have an Access application (Access Front-end, SQL backend using Linked tables) and I'm having this issue:
The user enters in a part number. Then they enter in a quantity. New business logic says I have to check this part number against a table, to see if we have quoted it to a customer within the past year. If we have, we can quote it now; if not, we have to reject the part.
When the program was originally written, they used a datasheet format to allow the user to copy from Excel a list of parts, paste them into the datasheet, and then copy and paste in a list of quantities. Once the quantity is entered/pasted for a line, that line is processed.
If I manually enter in a part number, then the quantity, the needed code checks to see if the part number has been quoted within the last year, and everything is peachy.
But if I copy and paste in a list of parts, the first time through the process it works fine; but every other time fails.
Here's the basic code:
Dim cn21 As ADODB.Connection
Set cn21 = New ADODB.Connection
Dim strsql21 As String
Dim cm21 As New ADODB.Command
Dim rs21 As New ADODB.Recordset
' gblODBCString = "ODBC;Description=PartsPortalsSql;DRIVER=SQL Server;SERVER=db-TEST-partsptl-primary;Trusted_Connection=Yes;APP=Microsoft Office 2010;DATABASE=PartsPortalSQL;"
cn21.Open gblODBCString
cm21.ActiveConnection = cn21
' All I want to know is how many records there are for this part within the last year...
strsql21 = "Select count(*) from tblQuoteDetail tqd INNER JOIN tblQuotes tq on tqd.quoteid = tq.quoteid WHERE " _
& " tqd.qdetailpartno = '" & Me.QDetailPartNo & "' AND tq.quotesentdate >= '" & OneYearAgo & "' AND tqd.qdetailunitprice > 0"
cm21.CommandText = strsql21
Set rs21 = New ADODB.Recordset
Set rs21 = cn21.Execute(strsql21, varparams, adCmdText)
If rs21(0) = 0 Then ' nothing found! can't be escalated...
blah blah blah...
end if
' done with this part... clean up
Set cm21 = Nothing
rs21.Close
Set rs21 = Nothing
Set cn21 = Nothing
Then it will return here when it gets to the next part...
But if i do it manually, it works fine. But when it is running through the loop of parts, it gets to the:
Set rs21 = cn21.Execute(strsql21, varparams, adcmdtext)
line
and takes about 30 seconds to 'process/time out', and then the rs21(0) returns "Run-time error '3265': Item cannot be found in the collection corresponding to the requested name or ordinal."
I've verified every field in the sql query is correctly populated. I've never run into this issue before.
Other things I've attempted - use DAO instead of ADODB... no luck...
If anyone has any suggestions I'm all ears... short of blowing it up... I'm in the testing phase of a replacement version, that doesn't use that copy/paste functionality. But that's still a month or two away from production.
Thanks
Access 2016, Linked tables to Microsoft SQL Server 2016 I believe...
Windows 10 64 bit
Also running on a VPN, connected to a VDI.
I htink youre using the wrong commands to check for recordsets. I could be wrong as I dont use ADO as much as I should, but here is my template for it if you thin itll help
'needs the MSO AtiveX Data Objects Library
Dim vbSql As String, cnnstr as string
Dim cnn As ADODB.Connection
Dim rs As New ADODB.Recordset
vbSql = "SELECT ;"
Set cnn = New Connection
cnnstr = ""
cnn.Open cnnstr
rs.CursorLocation = adUseClient
rs.Open vbSql, cnn
cnn.Close
Set cnn = Nothing
I have the below VBA query used in Excel 2016 that exacutes a MS Sql stored procedure, sometimes it executes smoothly and returns the recordset, but more often I get an error [Microsoft][ODBC SQL Server Driver] query timeout expired.
At the same time when we go to SSMS and execute the query it runs without issues.
This assumes the issue is rather caused by Excel/VB than by SQL or the query itself.
Searching for this error results in checking network firewalls, but we tried on other machines without firewalls, problems persists.
Here is the VB code:
Public Sub GetDataset2()
Dim cn As ADODB.Connection
Dim cm As Object
Dim rs As ADODB.Recordset
Dim UID, PWD, DB As String
UID = "userId"
PWD = "passworD"
DB = "192.168.1.1"
Set cn = New ADODB.Connection
Set cm = CreateObject("ADODB.Command")
cm.CommandTimeout = 0
cn.Open ("Driver={SQL Server};Server=" & DB & ";Database=myDatabaseName;Trusted_Connection=no;Timeout=900;Uid=" & UID & ";Pwd=" & PWD)
Set rs = cn.Execute("Get_dataset2 '" & Format(Range("dateFrom"), "yyyy-mm-dd") & "' ,'" & Format(Range("dateTo"), "yyyy-mm-dd") & "' ")
Dim lRow As Long
'Find the last non-blank cell in column A(1)
lRow = Sheets("data").Cells(Rows.Count, 1).End(xlUp).Row
lr = "A" & lRow + 1
Sheets("data").Range(lr).CopyFromRecordset rs 'insert data
cn.Close
End Sub
Any suggestion is appreciated.
Joel
After some more thought about the question and the comments on my prior answer, here are some additional points. To BitAccesser, cn.CommandTimeout is the same as Connection.CommandTimeout since the originally submitted code had already dimensioned and set the cn object as an ADODB.Connection. Also worth noting is the difference between ConnectionTimeout and CommandTimeout. The connection timeout is network level, while the command timeout is SQL Server level. In this case, even though a ADODB.Command object is instantiated, it isn't used. Another point relates to the connection string. The connection timeout could be referenced in the connection string, but often, is not used. The connection will be defaulted to 15 seconds. So, its worth resetting those attributes explicitly.
Cn.CommandTimeout = 50
Cn.ConnectionTimeout = 50
One possible solution is to lengthen the connection command timeout value. Your current script has the value set to 0. This could be increased. Running the query in SSMS should give you a rough idea of the time needed to complete the query. Then, adjust the value accordingly.
cm.CommandTimeout = 100
After weeks of testing various code changes, we found that when changing the SQL call to QueryTable method instead of CopyFromRecordset method, it is working fine.
So I am pasting the code if anyone needs it in future.
Sub GetDataset3()
Dim cn As ADODB.Connection
Dim Rs As ADODB.Recordset
Dim UID, PWD, SRV As String
UID = "userId"
PWD = "passworD"
SRV = "192.168.1.1"
If Sheets("data").QueryTables.Count = 0 Then
Sheets("data").Cells.Select
Selection.ClearContents
Dim Str As String 'adds backround query
Str = ""
For Each cell In Range("A1:A10").Cells
Str = Str & Chr(10) & cell
Next
With Sheets("data").QueryTables.Add(Connection:="ODBC;UID=;PWD=;DRIVER=SQL
Server;SERVER=SRV", Destination:=Range("a2"))
.CommandText = "select 1"
'BackgroundQuery = True
'.Refresh BackgroundQuery = True
.FieldNames = False
.AdjustColumnWidth = False
End With
End If
With Sheets("data").QueryTables(1)
.Connection = "ODBC;DRIVER=SQL Server;SERVER=" & SRV &
";database=myDatabaseName;UID=" & UID & ";Pwd=" & PWD &
";Trusted_Connection=no;APP=Microsoft Office"
.CommandText = ("Get_dataset2 '" & Range("dateFrom") & "' ,'" &
Range("dateTo") & "' ")
BackgroundQuery = True
.Refresh BackgroundQuery:=False
End With
End Sub
I am working writing out reports from our company database using VBA and MS Access 2010. I have almost 3000 employees and I need to write out 10 different reports for each employee and then combine those 10 reports into one pdf per employee. These files are then saved in directories that are sorted by their duty station.
The code I have written works great and does the job intended to do EXCEPT after writing out 1024 reports I receive an error. Err.Number 3014, Cannot open anymore tables.
According to what I could find on the internet this has to do with the Jet table references and is tricky to troubleshoot. I have followed what advice I could find I believe I have properly closed out everything after use. I thought maybe the problem arose in the merge pdf files routine but even if you comment that out it still fails at 1024 reports.
I would like this code to be able to process about 30,000 reports without failing. Any ideas or thoughts would be appreciated.
Public Function combined_report(EmployeeSelectionQuery As String)
Dim DefaultPdfDir As String ' contains path to where pdf files will be written on local computer
Dim rst As Recordset ' recordset object for set of selected plots from query:Employees_COMBINED
Dim n_employees As Integer ' Number of employees selected by query:Employees_COMBINED
Dim current_employee_number As Variant ' current employee number, used when writing combined reports
Dim current_duty_station As Variant ' current duty station, used when writing combined reports
Dim strWhere As String ' String containing the where clause for the combined openreport WhereCondition
Dim arrayReport(0 To 9) As Variant ' Array containing all the reports to be processed in combined
Dim strReport As Variant ' String containing prefix to reports
Dim tempOutputPdfFile As String ' Individual report before they are combined
Dim combinedOutputPdfFile As String ' Combined report composed of individual reports REQUIRES that adobe acrobat - full version be installed.
Dim intCounter As Integer ' A iteration counter used to update the status bar
Dim combOutputPdfFile As String ' Combined Output Pdf File Path
On Error GoTo error_handler
Set rst = CurrentDb.OpenRecordset(EmployeeSelectionQuery)
'Force Access to accurately update .RecordCount property
rst.MoveLast
rst.MoveFirst
n_employees = rst.RecordCount
If n_employees = 0 Then
Call MsgBox("No employees selected by query: " & EmployeeSelectionQuery, vbCritical + vbOKOnly + vbDefaultButton1, "No Employees Selected")
combined_report = False
Else
DoCmd.Hourglass True
'Set HomeDir and create output folder
DefaultPdfDir = "C:\temp"
MakeDir DefaultPdfDir
arrayReport(0) = "REPORT_1"
arrayReport(1) = "REPORT_2"
arrayReport(2) = "REPORT_3"
arrayReport(3) = "REPORT_4"
arrayReport(4) = "REPORT_5"
arrayReport(5) = "REPORT_6"
arrayReport(6) = "REPORT_7"
arrayReport(7) = "REPORT_8"
arrayReport(8) = "REPORT_9"
arrayReport(9) = "REPORT_10"
'Set counter to zero
intCounter = 0
Do While (Not (rst.EOF))
'Get employee number and duty station to name the files and sort by directory
current_employee_number = rst!EN
current_duty_station = rst!DUTY_STATION
'Make the output directory if it doesn't exist and specify the output file path
MakeDir "C:\Final\" & current_duty_station
combOutputPdfFile = "C:Final\" & current_duty_station & "\" & current_employee_number & ".pdf"
'Increment counter by one for each employee processed
intCounter = intCounter + 1
'Where statement used by DoCmd.OpenReport to run the report for one employee only
strWhere = "[EN] = " & current_employee_number & " OR [en] = " & current_employee_number
'Process each report
For Each strReport In arrayReport
'Specify the file path and name for the report
tempOutputPdfFile = DefaultPdfDir & "\" & current_employee_number & "_" & strReport & ".pdf"
'Update Status Bar
Status ("Processing " & intCounter & " of " & n_employees & ": " & tempOutputPdfFile)
'Open the report and write it to a pdf file
DoCmd.OpenReport strReport, acViewPreview, "", strWhere, acHidden
DoCmd.OutputTo acOutputReport, strReport, acFormatPDF, tempOutputPdfFile, False
DoCmd.Close acReport, strReport, acSaveNo
'Merge the files
MergePdfFiles combOutputPdfFile, tempOutputPdfFile, combOutputPdfFile
Next strReport
'Delete the last temp file before moving on to the next employee
DeleteFile tempOutputPdfFile
rst.MoveNext
Loop
'Close everything up
Status ("")
rst.Close
Set rst = Nothing
DoCmd.Hourglass False
combined_report = True
End If
Exit Function
error_handler:
MsgBox "Error: " & Err.Number & vbNewLine & _
"Description: " & Err.Description, vbCritical, "combined_report function error"
DoCmd.Hourglass False
combined_report = False
Status ("")
End Function
Try commenting out the "DoCmd.OutputTo" statement, see if it errors. I am guessing this command is opening a report in addition to the report being opened in the prior DoCmd.OpenReport line.
(I would have just added this as a comment, but SO wouldn't let me)
I was using DoCmd.OpenReport because it had the WHERE clause functionality built in. After reading JayRO-GreyBeard's comment I realized that it did seem redundant to use both the OpenReport and OutputTo methods. So, I rewrote the code, removing the OpenReport call and modified the QueryDef for each report prior to calling OutputTo.
For some reason this worked around the problem.
Thanks for the help!
I had the same issue with the 3014 Error. I was outputting the report to PDF, while displaying the report on the screen for the user (using both Docmd.OpenReport and Docmd.OutputTo, which worked fine for single reports. However, when I would batch run reports and export/display the reports. (The tool auto generates Purchase Orders) The 3014 error would occur around 100 or so reports.
When I turned off the DoCmd.OpenReport for the batch run of reports to PDF. The 3014 error went away. I have retested, and can not run report batches in the 1000s with no problem.
I have an excel sheet with some data:
1. Inventory item number
2. Description
3. Inventory Database ID (PRIMARY KEY)
I have about 1000 rows. I want to delete the item numbers in the database that match the item number in my excel list. I can write an application to do that in .NET, but that just seems overly complicated.
Is there an easy way through excel or SQL Server to run a sql statement to delete item numbers in my excel sheet with out the trouble of creating an application?
For quick updates. I find this to be the best method.
Add a column to Excel and construct your update statement as a formula, ie:
="DELETE Table1 WHERE ItemNumber='"&A1&"' AND InventoryId = "&C1
Copy the formula down, and copy/paste the result into an SQL window and run it.
Pro tip, if you have a lot of apostrophes to deal with, it might be worth it to do a global find/replace beforehand. Or you can deal with them from the formula. ie:
="DELETE Table1 WHERE ItemNumber='"&SUBSTITUTE(A1,"'","''")&"' AND InventoryId = "&C1
If you do not want to go through a SQL interface, you can run the attached code from excel, after updating the connection string obviously.
Sub ExecuteSQLCommand()
'Execute the SQL string passed through
Dim conn As ADODB.Connection, strConn As String, sSQLCommand As String
Dim cmd As ADODB.Command, lLoop As Long, lLastRow As Long
Set conn = New ADODB.Connection
conn.ConnectionString = "Provider=SQLOLEDB;" & _
"Data Source=DCC08SQL;" & _
"Initial Catalog=HarvestPress;" & _
"Integrated Security=SSPI;" & _
"Database=HarvestPress;" & _
"Trusted_Connection=True;"
conn.Open
Set cmd = New ADODB.Command
lLastRow = Cells(Rows.Count, 1).End(xlUp).Row
With cmd
.ActiveConnection = conn
.CommandType = adCmdText
For lLoop = 1 To lLastRow
sSQLCommand = "DELETE FROM Table1 WHERE ItemNumber='" & Cells(lLoop, 1) & "' AND InventoryId = " & Cells(lLoop, 1)
.CommandText = sSQLCommand
.Execute
Next lLoop
End With
conn.Close
Set conn = Nothing
Set cmd = Nothing
End Sub