I have multiple sql query in column c of worksheets("query") where the number of query can change. When there is a cell containing a sql query i want my code to run them one by one and populate the data in another worksheets("RESULT")
the final outcome would be :
run sql query number 1 and get result with the header in sheet RESULT (result will be sperad from range("A:M")
run sql query number 2 and get the reuslt in sheet RESULT right after the result 1 (whithout the hearder)
run sql query number 3 and get the result in sheet RESULT right after the result 1 &2 ( without the header)
...
...
...
...
...
run sql query number x and get the result in sheet RESULT right after the result 1 to x ( without the header)
Sub DCPARAMS()
Dim DBcon As ADODB.Connection
Dim DBrs As ADODB.Recordset
Set DBcon = New ADODB.Connection
Set DBrs = New ADODB.Recordset
Dim SSDF_SSDF As Workbook
Application.ScreenUpdating = False
Application.DisplayAlerts = False
Dim DBQuery As String
Dim ConString As String
Dim SQL_query As String
Dim User As String
Dim Password As String
Dim RowsCount As Double
Dim intColIndex As Double
DBrs.CursorType = adOpenDynamic
DBrs.CursorLocation = adUseClient
Windows("SSDF MACRO.xlsm").Activate
Set SSDF_SSDF = ActiveWorkbook
User = SSDF_SSDF.Sheets("MACROS").Range("B4").Value
Password = SSDF_SSDF.Sheets("MACROS").Range("B5").Value
'error handling
On Error GoTo err
'I WANT THIS VALUE TO CHANGE BASED ON QUERY SHEETS COLUMN C
**SQL_query = Worksheets("query").Range("C2").Value**
' DELETING OLD VALUES
SSDF_SSDF.Sheets("RESULT").Select
SSDF_SSDF.Sheets("RESULT").Range("A1:Q1000000").Select
Selection.ClearContents
If User = "" Then MsgBox "Please fill in your user ID first"
If User = "" Then Exit Sub
If Password = "" Then MsgBox "Please fill in your Password first"
If Password = "" Then Exit Sub
'Open the connection using Connection String
DBQuery = "" & SQL_query
ConString = "Driver={Oracle in OraClient12Home1_32bit};Dbq=prismastand.world;Uid=" & User & ";Pwd=" & Password & ";"
DBcon.Open (ConString) 'Connecion to DB is made
'below statement will execute the query and stores the Records in DBrs
DBrs.Open DBQuery, DBcon
If Not DBrs.EOF Then 'to check if any record then
' Spread all the records with all the columns
' in your sheet from Cell A2 onward.
SSDF_SSDF.Sheets("RESULT").Range("A2").CopyFromRecordset DBrs
'Above statement puts the data only but no column
'name. hence the below for loop will put all the
'column names in your excel sheet.
For intColIndex = 0 To DBrs.Fields.Count - 1
Sheets("RESULT").Cells(1, intColIndex + 1).Value = DBrs.Fields(intColIndex).Name
Next
RowsCount = DBrs.RecordCount
End If
'Close the connection
DBcon.Close
'Informing user
Worksheets("REUSLT").Select
If Range("A2").Value <> "" Then
MsgBox "ALL GOOD, RUN NEXT MACRO"
Else: MsgBox "DATA IS MISSING IN DB PLEASE CHECK"
Exit Sub
End If
'alerts
Application.ScreenUpdating = True
Application.DisplayAlerts = True
Windows("SSDF MACRO.xlsm").Activate
SSDF_SSDF.Sheets("dc").Select
Exit Sub
err:
MsgBox "Following Error Occurred: " & vbNewLine & err.Description
DBcon.Close
'alerts
Application.ScreenUpdating = True
Application.DisplayAlerts = True
End Sub
I tired to run the code attached but it get only the sql query in cells c2. Iam sorry im very novice
Define a range for the results and offset it by the number of rows returned.
Option Explicit
Sub DCPARAMS()
Const WBNAME = "SSDF MACRO.xlsm"
Dim DBcon As ADODB.Connection, DBrs As ADODB.Recordset
Dim SSDF_SSDF As Workbook, wsResult As Worksheet, wsQuery As Worksheet
Dim rngResult As Range
Dim ConString As String, SQL_query As String
Dim User As String, Password As String
Dim intColIndex As Long, lastrow As Long, r As Long
Application.ScreenUpdating = False
Windows("SSDF MACRO.xlsm").Activate
Set SSDF_SSDF = Workbooks(WBNAME)
With SSDF_SSDF
Set wsResult = .Sheets("RESULT")
Set wsQuery = .Sheets("Query")
End With
'error handling
On Error GoTo err
' connect to db
With SSDF_SSDF.Sheets("MACROS")
User = .Range("B4").Value
Password = .Range("B5").Value
If User = "" Or Password = "" Then
MsgBox "Please fill in your user ID and password", vbCritical
Exit Sub
End If
End With
ConString = "Driver={Oracle in OraClient12Home1_32bit};Dbq=prismastand.world;" & _
"Uid=" & User & ";Pwd=" & Password & ";"
Set DBcon = New ADODB.Connection
DBcon.Open ConString 'Connecion to DB is made
Set DBrs = New ADODB.Recordset
DBrs.CursorType = adOpenDynamic
DBrs.CursorLocation = adUseClient
' delete old results
With wsResult
.Range("A:Q").ClearContents
Set rngResult = .Range("A2")
End With
With wsQuery
lastrow = .Cells(.Rows.Count, "C").End(xlUp).Row
For r = 2 To lastrow
'I WANT THIS VALUE TO CHANGE BASED ON QUERY SHEETS COLUMN C
SQL_query = .Cells(r, "C")
'below statement will execute the query and stores the Records in DBrs
DBrs.Open SQL_query, DBcon
If Not DBrs.EOF Then 'to check if any record then
'Above statement puts the data only but no column
'name. hence the below for loop will put all the
'column names in your excel sheet.
If wsResult.Cells(1, 1) = "" Then
For intColIndex = 0 To DBrs.Fields.Count - 1
wsResult.Cells(1, intColIndex + 1) = DBrs.Fields(intColIndex).Name
Next
End If
' Spread all the records with all the columns
' in your sheet from Cell A2 onward.
rngResult.CopyFromRecordset DBrs
' move down for next query
Set rngResult = rngResult.Offset(DBrs.RecordCount)
End If
DBrs.Close
Next
End With
'Close the connection
DBcon.Close
'Informing user
If wsResult.Range("A2").Value <> "" Then
MsgBox "ALL GOOD, RUN NEXT MACRO", vbInformation, "Rows = " & rngResult.Row - 2
Else:
MsgBox "DATA IS MISSING IN DB PLEASE CHECK", vbCritical
Exit Sub
End If
SSDF_SSDF.Activate
SSDF_SSDF.Sheets("dc").Select
Application.ScreenUpdating = True
Exit Sub
err:
MsgBox "Following Error Occurred: " & vbNewLine & err.Description
DBcon.Close
'alerts
Application.ScreenUpdating = True
End Sub
Related
I am relatively new to VBA and need some assistance. I have been piecing together this application from other bits and samples. This was working on Friday but now it isn't and I don't understand what may be causing the issue. I have a master function that calls the subs in order. I have written the UseADO function to take parameters. The first sub that calls UseADO {copyAllRawData()} does work. However, when it calls the sub cashDiscounts(), I get a compile error: Variable not defined error on Sheet4 (the first variable to be passed to UseADO. There is another sub that creates the sheets and I have verified that Sheet4 does exist and if I comment this one out, I get the same error on the sub for Sheet5 processing. Any help would be greatly appreciated. Thanks!
Public Function UseADO(writeToSheet As Worksheet, writeToStartCell As String, queryString As String)
'Get the Filename
Dim filename As String
filename = ThisWorkbook.Path & Application.PathSeparator & "hdremittance.xlsx"
'Get the Connection
Dim conn As New ADODB.Connection
conn.Open "Provider=Microsoft.ACE.OLEDB.12.0;" & "Data Source=" & filename & ";" & _
"Extended Properties=""Excel 12.0;HDR=Yes;"";"
'Create the SQL Query
Dim query As String
query = queryString
'Query = "Select * from ....
'query = "Select * From [hdremittance$]"
'Get the data from the workbook
Dim rs As New Recordset
rs.Open query, conn
'Write Data
'Dim sht As String
'sht = writeToSheet
writeToSheet.Cells.ClearContents
writeToSheet.Range(writeToStartCell).CopyFromRecordset rs
'Close the Connection
conn.Close
End Function
Sub copyAllRawData()
UseADO Sheet1, "A2", "Select * From [hdremittance$]"
ThisWorkbook.Sheets(1).Range("A1").Value = "Invoice Number"
ThisWorkbook.Sheets(1).Range("B1").Value = "Keyrec Number"
ThisWorkbook.Sheets(1).Range("C1").Value = "Doc Type"
ThisWorkbook.Sheets(1).Range("D1").Value = "Transaction Value"
ThisWorkbook.Sheets(1).Range("E1").Value = "Cash Discount Amount"
ThisWorkbook.Sheets(1).Range("F1").Value = "Clearing Document Number"
ThisWorkbook.Sheets(1).Range("G1").Value = "Payment/Chargeback Date"
ThisWorkbook.Sheets(1).Range("H1").Value = "Comments"
ThisWorkbook.Sheets(1).Range("I1").Value = "Reason Code"
ThisWorkbook.Sheets(1).Range("J1").Value = "SAP Company Code"
ThisWorkbook.Sheets(1).Range("K1").Value = "PO Number"
ThisWorkbook.Sheets(1).Range("L1").Value = "Reference/Check Number"
ThisWorkbook.Sheets(1).Range("M1").Value = "Invoice Date"
ThisWorkbook.Sheets(1).Range("N1").Value = "Posting Date"
ThisWorkbook.Sheets(1).Range("O1").Value = "Payment Number"
End Sub
Sub cashDiscounts()
UseADO Sheet4, "A2", "Select Top 10000 [Invoice Number],[Keyrec Number],[Doc Type],[Transaction Value],[Reason Code] From [hdremittance$] WHERE [Reason Code] Like '*CASH DISCOUNT%' "
'D-4080 (Cash/Trade Discount)
ThisWorkbook.Sheets(4).Range("A1").Value = "Invoice Number"
ThisWorkbook.Sheets(4).Range("B1").Value = "Keyrec Number"
ThisWorkbook.Sheets(4).Range("C1").Value = "Doc Type"
ThisWorkbook.Sheets(4).Range("D1").Value = "Transaction Value"
ThisWorkbook.Sheets(4).Range("E1").Value = "Reason Code"
ThisWorkbook.Sheets(4).Range("F1").Value = "Distribution Account"
Dim LastRow As Long
With ActiveSheet
LastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
End With
ThisWorkbook.Sheets(4).Range(Cells(2, "F"), Cells(LastRow, "F")).Value = "D-4080"
End Sub
Sub buildNameWorksheets()
'Sheets.Add Count:=[10]
Sheets("Sheet1").Name = "rawData"
Sheets.Add(After:=Sheets(Sheets.Count)).Name = "filterCriteria"
'Sheet2
'ThisWorkbook.Sheets(2).Range("A1").Value = "Invoice Number"
'ThisWorkbook.Sheets(2).Range("B1").Value = "Keyrec Number"
'ThisWorkbook.Sheets(2).Range("C1").Value = "Doc Type"
'ThisWorkbook.Sheets(2).Range("D1").Value = "Transaction Value"
'ThisWorkbook.Sheets(2).Range("E1").Value = "Reason Code"
Sheets.Add(After:=Sheets(Sheets.Count)).Name = "invoices"
'Sheet3
Sheets.Add(After:=Sheets(Sheets.Count)).Name = "cashDiscounts"
'Sheet4
Sheets.Add(After:=Sheets(Sheets.Count)).Name = "tradeDiscounts"
'Sheet5
Sheets.Add(After:=Sheets(Sheets.Count)).Name = "earlyPmtFees"
'Sheet6
Sheets.Add(After:=Sheets(Sheets.Count)).Name = "rtvDamagedFees"
'Sheet7
Sheets.Add(After:=Sheets(Sheets.Count)).Name = "rdcComplianceDeductions"
'Sheet8
Sheets.Add(After:=Sheets(Sheets.Count)).Name =
"supplierCollabTeamAnalytics" 'Sheet9
Sheets.Add(After:=Sheets(Sheets.Count)).Name = "newStoreDiscount"
'Sheet10
Sheets.Add(After:=Sheets(Sheets.Count)).Name = "volumeRebate"
'Sheet11
End Sub
Some suggestions below - compiles, but not tested since I don't have your data.
Shows how to skip the whole issue with sheet codenames, and how to use the field names from the recordset as headers in the output.
Option Explicit
'Create one of these for each sheet you create/populate
Const WS_RAW As String = "rawData"
Const WS_FILT As String = "filterCriteria"
Const WS_INVOICES As String = "invoices"
Const WS_CASH_DISC As String = "cashDiscounts"
Const WS_EARLY_PMT As String = "earlyPmtFees"
'etc etc one for each sheet you use
Public Function UseADO(writeToSheet As Worksheet, writeToStartCell As String, queryString As String)
'Get the Filename
Dim filename As String, conn As New ADODB.Connection, rs As New Recordset, i As Long
Dim c As Range
filename = ThisWorkbook.Path & Application.PathSeparator & "hdremittance.xlsx"
conn.Open "Provider=Microsoft.ACE.OLEDB.12.0;" & "Data Source=" & filename & ";" & _
"Extended Properties=""Excel 12.0;HDR=Yes;"";"
writeToSheet.Cells.ClearContents
rs.Open queryString, conn
Set c = writeToSheet.Range(writeToStartCell)
'Write the field names
For i = 0 To rs.Fields.Count - 1 'fields is zero-based
c.Offset(0, i).Value = rs.Fields(i).Name
Next i
'write the data
If Not rs.EOF Then
c.Offset(1).CopyFromRecordset rs
End If
rs.Close 'close the recordset
conn.Close 'Close the Connection
End Function
'example of calling UseADO
Sub cashDiscounts()
'D-4080 (Cash/Trade Discount)
'NOTE: this shows how you can create a new column with a fixed value and a specified name in your recordset
UseADO ThisWorkbook.Sheets(WS_CASH_DISC), "A2", _
"Select Top 10000 [Invoice Number],[Keyrec Number],[Doc Type],[Transaction Value]," & _
" [Reason Code], 'D-4080' As ""Distribution Account"" From [hdremittance$] " & _
" WHERE [Reason Code] Like '*CASH DISCOUNT%' "
End Sub
'create named sheets from array of constants
Sub buildNameWorksheets()
Dim wb As Workbook, nm
Set wb = ThisWorkbook 'ActiveWorkbook?
wb.Sheets("Sheet1").Name = "rawData"
For Each nm In Array(WS_FILT, WS_INVOICES, WS_CASH_DISC, WS_EARLY_PMT) 'add the others...
With wb.Worksheets.Add(after:=wb.Worksheets(wb.Worksheets.Count))
.Name = nm
End With
Next nm
End Sub
UPDATED QUESTION:
I have Update sheet, this sheet contains unique ID that matched the access database ID, I'm trying to update the fields using excel values in "Update" sheet.
The ID is in the Column A the rest of the fields are stored from Column B to R. I'm trying to achieve the below, As follows:
Update the record(values from Column B to R) if Column A (ID) matched existing Access database ID. Then add text in Column S "Updated"
If the Column A (ID) did not found any match in the existing Access database ID, Then add text in Column S "ID NOT FOUND"
Loop to next value
So far, I have the below Sub for Update and Function for Existing ID (Import_Update Module), but I'm getting this error.
Sub Update_DB()
Dim dbPath As String
Dim lastRow As Long
Dim exportedRowCnt As Long
Dim NotexportedRowCnt As Long
Dim qry As String
Dim ID As String
'add error handling
On Error GoTo exitSub
'Check for data
If Worksheets("Update").Range("A2").Value = "" Then
MsgBox "Add the data that you want to send to MS Access"
Exit Sub
End If
'Variables for file path
dbPath = Worksheets("Home").Range("P4").Value '"W:\Edward\_Connection\Database.accdb" '##> This was wrong before pointing to I3
If Not FileExists(dbPath) Then
MsgBox "The Database file doesn't exist! Kindly correct first"
Exit Sub
End If
'find las last row of data
lastRow = Cells(Rows.Count, 1).End(xlUp).Row
Dim cnx As ADODB.Connection 'dim the ADO collection class
Dim rst As ADODB.Recordset 'dim the ADO recordset class
On Error GoTo errHandler
'Initialise the collection class variable
Set cnx = New ADODB.Connection
'Connection class is equipped with a —method— named Open
cnx.Open "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & dbPath
'ADO library is equipped with a class named Recordset
Set rst = New ADODB.Recordset 'assign memory to the recordset
'##> ID and SQL Query
ID = Range("A" & lastRow).Value
qry = "SELECT * FROM f_SD WHERE ID = '" & ID & "'"
'ConnectionString Open '—-5 aguments—-
rst.Open qry, ActiveConnection:=cnx, _
CursorType:=adOpenDynamic, LockType:=adLockOptimistic, _
Options:=adCmdTable
'add the values to it
'Wait Cursor
Application.Cursor = xlWait
'Pause Screen Update
Application.ScreenUpdating = False
'##> Set exportedRowCnt to 0 first
UpdatedRowCnt = 0
IDnotFoundRowCnt = 0
If rst.EOF And rst.BOF Then
'Close the recordet and the connection.
rst.Close
cnx.Close
'clear memory
Set rst = Nothing
Set cnx = Nothing
'Enable the screen.
Application.ScreenUpdating = True
'In case of an empty recordset display an error.
MsgBox "There are no records in the recordset!", vbCritical, "No Records"
Exit Sub
End If
For nRow = 2 To lastRow
'##> Check if the Row has already been imported?
'##> Let's suppose Data is on Column B to R.
'If it is then continue update records
If IdExists(cnx, Range("A" & nRow).Value) Then
With rst
For nCol = 1 To 18
rst.Fields(Cells(1, nCol).Value2) = Cells(nRow, nCol).Value 'Using the Excel Sheet Column Heading
Next nCol
Range("S" & nRow).Value2 = "Updated"
UpdatedRowCnt = UpdatedRowCnt + 1
rst.Update
End With
Else
'##>Update the Status on Column S when ID NOT FOUND
Range("S" & nRow).Value2 = "ID NOT FOUND"
'Increment exportedRowCnt
IDnotFoundRowCnt = IDnotFoundRowCnt + 1
End If
Next nRow
'close the recordset
rst.Close
' Close the connection
cnx.Close
'clear memory
Set rst = Nothing
Set cnx = Nothing
If UpdatedRowCnt > 0 Or IDnotFoundRowCnt > 0 Then
'communicate with the user
MsgBox UpdatedRowCnt & " Drawing(s) Updated " & vbCrLf & _
IDnotFoundRowCnt & " Drawing(s) IDs Not Found"
End If
'Update the sheet
Application.ScreenUpdating = True
exitSub:
'Restore Default Cursor
Application.Cursor = xlDefault
'Update the sheet
Application.ScreenUpdating = True
Exit Sub
errHandler:
'clear memory
Set rst = Nothing
Set cnx = Nothing
MsgBox "Error " & Err.Number & " (" & Err.Description & ") in procedure Update_DB"
Resume exitSub
End Sub
Function to Check if the ID Exists
Function IdExists(cnx As ADODB.Connection, sId As String) As Boolean
'Set IdExists as False and change to true if the ID exists already
IdExists = False
'Change the Error handler now
Dim rst As ADODB.Recordset 'dim the ADO recordset class
Dim cmd As ADODB.Command 'dim the ADO command class
On Error GoTo errHandler
'Sql For search
Dim sSql As String
sSql = "SELECT Count(PhoneList.ID) AS IDCnt FROM PhoneList WHERE (PhoneList.ID='" & sId & "')"
'Execute command and collect it into a Recordset
Set cmd = New ADODB.Command
cmd.ActiveConnection = cnx
cmd.CommandText = sSql
'ADO library is equipped with a class named Recordset
Set rst = cmd.Execute 'New ADODB.Recordset 'assign memory to the recordset
'Read First RST
rst.MoveFirst
'If rst returns a value then ID already exists
If rst.Fields(0) > 0 Then
IdExists = True
End If
'close the recordset
rst.Close
'clear memory
Set rst = Nothing
exitFunction:
Exit Function
errHandler:
'clear memory
Set rst = Nothing
MsgBox "Error " & Err.Number & " :" & Err.Description
End Function
My below code is working fine. I tried to address your above three points in a different way.
##########################
IMPORTANT
1) I have removed your other validations; you can add them back.
2) DB path has been hard coded, you can set it to get from a cells again
3) My DB has only two fields (1) ID and (2) UserName; you will have obtain your other variables and update the UPDATE query.
Below is the code which is working fine to meet your all 3 requests...Let me know how it goes...
Tschüss :)
Sub UpdateDb()
'Creating Variable for db connection
Dim sSQL As String
Dim rs As ADODB.Recordset
Dim cn As ADODB.Connection
Set cn = New ADODB.Connection
cn.Open "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\test\db.accdb;"
Dim a, PID
'a is the row counter, as it seems your data rows start from 2 I have set it to 2
a = 2
'Define variable for the values from Column B to R. You can always add the direct ceel reference to the SQL also but it will be messy.
'I have used only one filed as UserName and so one variable in column B, you need to keep adding to below and them to the SQL query for othe variables
Dim NewUserName
'########Strating to read through all the records untill you reach a empty column.
While VBA.Trim(Sheet19.Cells(a, 1)) <> "" ' It's always good to refer to a sheet by it's sheet number, bcos you have the fleibility of changing the display name later.
'Above I have used VBA.Trim to ignore if there are any cells with spaces involved. Also used VBA pre so that code will be supported in many versions of Excel.
'Assigning the ID to a variable to be used in future queries
PID = VBA.Trim(Sheet19.Cells(a, 1))
'SQL to obtain data relevatn to given ID on the column. I have cnsidered this ID as a text
sSQL = "SELECT ID FROM PhoneList WHERE ID='" & PID & "';"
Set rs = New ADODB.Recordset
rs.Open sSQL, cn
If rs.EOF Then
'If the record set is empty
'Updating the sheet with the status
Sheet19.Cells(a, 19) = "ID NOT FOUND"
'Here if you want to add the missing ID that also can be done by adding the query and executing it.
Else
'If the record found
NewUserName = VBA.Trim(Sheet19.Cells(a, 2))
sSQL = "UPDATE PhoneList SET UserName ='" & NewUserName & "' WHERE ID='" & PID & "';"
cn.Execute (sSQL)
'Updating the sheet with the status
Sheet19.Cells(a, 19) = "Updated"
End If
'Add one to move to the next row of the excel sheet
a = a + 1
Wend
cn.Close
Set cn = Nothing
End Sub
You need to put the query inside the loop
Option Explicit
Sub Update_DB_1()
Dim cnx As New ADODB.Connection
Dim rst As New ADODB.Recordset
Dim qry As String, id As String, sFilePath As String
Dim lastRow As Long, nRow As Long, nCol As Long, count As Long
Dim wb As Workbook, ws As Worksheet
Set wb = ThisWorkbook
Set ws = wb.Sheets("Update")
lastRow = ws.Cells(Rows.count, 1).End(xlUp).Row
sFilePath = wb.Worksheets("Home").Range("P4").Value
cnx.open "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & sFilePath
count = 0
For nRow = 2 To lastRow
id = Trim(ws.Cells(nRow, 1))
qry = "SELECT * FROM f_SD WHERE ID = '" & id & "'"
Debug.Print qry
rst.open qry, cnx, adOpenKeyset, adLockOptimistic
If rst.RecordCount > 0 Then
' Update RecordSet using the Column Heading
For nCol = 2 To 9
rst.fields(Cells(1, nCol).Value2) = Cells(nRow, nCol).Value
Next nCol
rst.Update
count = count + 1
ws.Range("S" & nRow).Value2 = "Updated"
Else
ws.Range("S" & nRow).Value2 = "ID NOT FOUND"
End If
rst.Close
Next nRow
cnx.Close
Set rst = Nothing
Set cnx = Nothing
MsgBox count & " records updated", vbInformation
End Sub
I have a templated Excel file that will be is used to save the cell values to SQL. There will be about a thousand of these all with different names that perform the same function. For this reason I wanted to remove my code from the template into another file allowing for global changes if needed.
The User works from File A and hits the save button executing the following code to run the Macro contained in File B.
Sub Save_Inspection()
Dim wb As Workbook
On Error Resume Next
Set wb = Workbooks("SaveInspectionData.xlsm")
On Error GoTo 0
If wb Is Nothing Then Set wb = Workbooks.Open("\\SERVER\FOLDER\Files\XDomainDocs\SaveInspectionData.xlsm")
Dim FileName As String
FileName = ThisWorkbook.Name
Run "SaveInspectionData.xlsm!sheet1.Save_Inspection", FileName
wb.Close False
Set wb = Nothing
End Sub
Below is the code used to save the data residing on File B. Note - it is undermentioned at this time how many rows or columns there will, so I am looping through to create the SQL query and qty of rows, columns etc. This all works fine.
My problem is when I try to close the workbooks. I want them both to close and regardless or what I try only one of the two will close. The code below reflects the simplest close method, but I have tried several other techniques.
After some searching it may be something to do with my use of "With" statements to reference File A, but I am not sure.
Thanks in advance!
Sub Save_Inspection(FileName As String)
On Error GoTo errH
Dim strUserDomain As String
Dim cn As ADODB.Connection
Dim Server_Name As String
Dim Database_Name As String
Dim User_ID As String
Dim Password As String
Dim InspectionId As Integer 'Will use this Id to associate all results to this Inspection Instance
Dim Query As String
Dim rs As ADODB.Recordset
Set rs = New ADODB.Recordset
'Find proper connection string
strUserDomain = Environ$("UserDomain")
`If strUserDomain = "A" Then
Server_Name = "ServerA"
Database_Name = "Inspection"
User_ID = "xxx"
Password = "xxx"
ElseIf strUserDomain = "B" Then
Server_Name = "ServerB"
Database_Name = "Inspection"
User_ID = "xxx"
Password = "xxx"
ElseIf strUserDomain = "C" Then
Server_Name = "ServerC"
Database_Name = "Inspection"
User_ID = "xxx"
Password = "xxx"
Else
'Something must be wrong
Exit Sub
End If
Workbooks(FileName).Activate
Dim wb As Workbook
Dim ws As Worksheet
Set wb = Workbooks(FileName)
Set ws = wb.Sheets(1)
'Let's Save this stuff!
Dim DateInspected, PartNumber, LotNumber, Revision As String
'Set values
With ws
'DateInspected = .Range("Q5").Value
PartNumber = .Range("K4").Value
LotNumber = .Range("G3").Value
Revision = .Range("Q4").Value
End With
Query = "INSERT INTO InspectionCatalog (DateInspected, PartNumber, LotNumber, Revision) VALUES (GETDATE(), '" & PartNumber & "', '" & LotNumber & "', '" & Revision & "')"
Set cn = New ADODB.Connection
cn.Open "Provider=SQLOLEDB;Server=" & Server_Name & ";Initial Catalog=" & Database_Name & ";Uid=" & User_ID & ";Pwd=" & Password & ";"
cn.Execute (Query)
rs.Open "SELECT ##identity AS InspectionId", cn
InspectionId = rs.Fields("InspectionId")
'MsgBox (InspectionId)'For testing
'Loop through all cells on sheet and save results
Call LoopThroughResults(InspectionId, FileName, strUserDomain)
Exit Sub
errH:
MsgBox Err.Description
End Sub
Sub LoopThroughResults(InspectionId As Integer, FileName As String, strUserDomain As String)
On Error GoTo errH
'Declare Variables
Dim RowCount As Integer
Dim CollCount As Integer
Dim Coll_Count As Integer
Dim wb As Workbook
Dim ws As Worksheet
Set wb = Workbooks(FileName)
Set ws = wb.Sheets(1)
'Find the number of rows in the sheet based on a value in Col U
With ws
RowCount = .Cells(.Rows.Count, "G").End(xlUp).Row
'MsgBox RowCount
End With
'Go through each row and find the number of columns that are filled
'Set CollCount to the longest row - ignoring 1-9 these are header fields
For i = 10 To RowCount
With ws
Coll_Count = .Cells(i, .Columns.Count).End(xlToLeft).Column
If Coll_Count > CollCount Then
'Find the length of the longest row
CollCount = Coll_Count
End If
'MsgBox "Row " & i & " Has " & Coll_Count & " Columns!"
End With
Next i
'MsgBox "The Row with the Most data has " & CollCount & " Columns!"
'Save Col Count to be used for retrieving the data later
Dim Query As String
Query = "UPDATE InspectionCatalog SET CollCount='" & CollCount & "', [RowCount]='" & RowCount & "' WHERE InspectionId='" & InspectionId & "' "
Call SaveResults(Query, strUserDomain)
Dim QueryStart As String
Dim QueryEnd As String
'Loop through each row starting at 2 (Not 10, this time we want to capture all data
For i = 2 To RowCount
'Reset Query String befor hitting next row
QueryStart = "INSERT INTO InspectionResults ("
QueryEnd = " VALUES ("
'Loop through each column to create insert query
For n = 1 To CollCount
QueryStart = QueryStart & "Col" & n & ","
QueryEnd = QueryEnd & "N'" & Workbooks(FileName).Worksheets("Inspection Report").Cells(i, n).Value & "',"
Next n
QueryStart = QueryStart & "InspectionId)"
QueryEnd = QueryEnd & "'" & InspectionId & "');"
'MsgBox QueryStart & QueryEnd
Call SaveResults(QueryStart & QueryEnd, strUserDomain)
Next i
MsgBox "Inspection Data Has Been Saved"
Call CloseWorkBooks(FileName)
Exit Sub
errH:
MsgBox Err.Description
End Sub
Sub SaveResults(Query As String, strUserDomain As String)
On Error GoTo errH
Dim cn As ADODB.Connection
Dim Server_Name As String
Dim Database_Name As String
Dim User_ID As String
Dim Password As String
Dim rs As ADODB.Recordset
Set rs = New ADODB.Recordset
'Find proper connection string
strUserDomain = Environ$("UserDomain")
If strUserDomain = "A" Then
Server_Name = "ServerA"
Database_Name = "Inspection"
User_ID = "xxx"
Password = "xxx"
ElseIf strUserDomain = "B" Then
Server_Name = "ServerB"
Database_Name = "Inspection"
User_ID = "xxx"
Password = "xxx"
ElseIf strUserDomain = "C" Then
Server_Name = "ServerC"
Database_Name = "Inspection"
User_ID = "xxx"
Password = "xxx"
Else
'Something must be wrong
Exit Sub
End If
Set cn = New ADODB.Connection
cn.Open "Provider=SQLOLEDB;Server=" & Server_Name & ";Initial Catalog=" & Database_Name & ";Uid=" & User_ID & ";Pwd=" & Password & ";"
cn.Execute (Query)
Exit Sub
errH:
MsgBox Err.Description
End Sub
Sub CloseWorkBooks(FileName As String)
Workbooks(FileName).Close SaveChanges:=False
Workbooks("SaveInspectionData.xlsm").Close SaveChanges:=False
Exit Sub
End Sub
Note that Application.Run executes the code in the same 'environment' as the current workbook. Basically the workbook executing Application.Run is the one running the code, and the new workbook will be linked to the same session.
This will result in the peculiar situation that you are observing.
Closing the workbook executed by 'Run' will make any macro (sub, function, object, sheet) in this workbook go out of scope, and any code run will stop running in this workbook. In addition as the notebook was closed, the code will not 'finish' in the executed workbook and thus we will not return to the original workbook, effectively halting any execution in the original notebook.
Additionaly as the code will try to return to the original workbook, to finish the original running sub (here Save_Inspection() in the original workbook), the two workbooks are linked to the same session (or environment), and thus closing this workbook first will Halt the original code running (going to the next line in Save_Inspection becomes effectively impossible as the workbook is now closed), and this will end the session as well.
Thus closing all workbooks in a workbook opened and executed by Application.Run is not possible directly. Workarounds can be done. The simplest is closing all workbooks in the original workbook (placing wb.close false: Thisworkbook.close false after application.run). Alternatively making a sub in the second workbook that runs 'Application.Ontime' and saves the filename to a cell for use in the function run by 'ontime' should make certain that the two sessions wont be linked while running code in the second notebook. But this i am less certain is actually the case.
Below is the code in the original notebook. If the original workbook finishes this should close the workbooks in the end.
Sub Save_Inspection()
Dim wb As Workbook
On Error Resume Next
Set wb = Workbooks("SaveInspectionData.xlsm")
On Error GoTo 0
If wb Is Nothing Then
Set wb = Workbooks.Open(ThisWorkbook.Path & "SaveInspectionData.xlsm")
End If
Dim FileName As String
FileName = ThisWorkbook.Name
Run "SaveInspectionData.xlsm!sheet1.CloseBooks", FileName
wb.Close False
ThisWorkbook.Close False
Set wb = Nothing
End Sub
Above Oliver does a great job explaining why my procedures where not working properly. In order to fix this I removed the call's to close the workbooks, the code in workbook A handles that on it's own. I did make some small changes to the above code to handle closing Excel or the workbook based on the qty of instances open.
Sub Save_Inspection()
Dim wb As Workbook
Dim wb2 As Workbook
On Error Resume Next
Set wb = Workbooks("SaveInspectionData.xlsm")
On Error GoTo 0
If wb Is Nothing Then Set wb = Workbooks.Open("\\Server\Cloud9\Files\XDomainDocs\SaveInspectionData.xlsm")
Dim FileName As String
FileName = ThisWorkbook.Name
Run "SaveInspectionData.xlsm!sheet1.Save_Inspection", FileName
If Application.Workbooks.Count > 2 Then
wb.Close False
ThisWorkbook.Close False
Set wb = Nothing
Else
Application.Quit
End If
End Sub
I have a recordset object generated by the following code.
Private Sub GetID_Click()
'first find max id on sheet; used for if no ID is found on sheet
Dim myRange As Range
Dim maxIdOnSheet As Long
Dim clientSheet As Worksheet
Set clientSheet = Sheets("Client Codes")
Set myRange = clientSheet.Range("A1:A1048576")
maxIdOnSheet = WorksheetFunction.max(myRange) + 1
'set up connections with Nina's housing database
Dim cmd As New ADODB.Command
Dim conn As New ADODB.Connection
Dim rs As New ADODB.Recordset
Dim strConn As String
Dim strSQL As String
Dim IDdb As Long
Dim IDwb As Long
'connection string
strConn = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\db\path\here\db.accdb; Persist Security Info=False"
'open connection database
conn.Open strConn
'sql statement
strSQL = "SELECT * FROM Clients WHERE (((Clients.FirstName)='" & FirstName.Value & "') AND ((Clients.LastName)='" & LastName.Value & "'));"
'open connection with the recordset
rs.Open strSQL, conn, adOpenDynamic, adLockOptimistic
'use the late-bound application match method to find out where the firstname and lastname values are in the worksheet, if found
Dim first As Long
Dim last As Long
Dim foundWB As Boolean
Dim foundDB As Boolean
foundWB = False
foundDB = False
Dim base As Long
Dim curRow As Long
base = 1
'First check to make sure if both values are in the worksheet
If Not IsError(Application.Match(FirstName.Value, Range("c" & base & ":c1048576"), False)) And Not IsError((Application.Match(LastName.Value, Range("b" & base & ":b1048576"), False))) Then
'if it is in the worksheet, find where it is
While found = False
first = Application.Match(FirstName.Value, Range("c" & base & ":c1048576"), False)
last = Application.Match(LastName.Value, Range("b" & base & ":b1048576"), False)
If first = last Then
foundWS = True
curRow = curRow + first
IDwb = Cells(curRow, 1)
Else
If first < last Then
base = first + 1
curRow = curRow + first
ElseIf last < first Then
base = last + 1
curRow = curRow + last
End If
End If
Wend
Else
'if its not in the WS, it is now the highest +1
IDwb = WorksheetFunction.max(Range("a1:a1048576")) + 1
End If
'find if its in the database
If rs.EOF Then
'if its not in the database, find the highest number and add 1
rs.Close
strSQL = "SELECT MAX(Clients.[Client ID]) FROM Clients;"
rs.Open strSQL, conn, adOpenDynamic, adLockOptimistic
IDdb = rs.Fields(0) + 1
MsgBox (rs.Properties.Item("Address"))
Else
'if it is, find the first column
IDdb = rs.Fields(0)
foundDB = True
MsgBox (rs.Properties.Item("Address"))
End If
If foundWB = True Then
ClientID.Value = IDwb
ElseIf foundDB = True Then
ClientID.Value = IDdb
Else
If IDdb > IDwb Then
ClientID.Value = IDdb
ElseIf IDwb > IDdb Then
ClientID.Value = IDwb
Else
ClientID.Value = IDwb
End If
End If
End Sub
I have two data sources - the worksheet this macro is located in Excel, and an Access database. I enter client data, and assign it a specific code. The code does that successfully.
I also want to fill out a userform based on that code which is received. The code above successfully queries the database and can get the client ID. I also want things like address, city, state, zip, household income, that is stored in the DB, from the query.
If I was doing this in strictly Excel, I would use a match statement, and if strictly through Access, a SQL query. I'm trying to run this query on both an Excel worksheet and an Access database at the same time, or in the same code. This requires setting up a recordset object: documentation is found here
http://www.w3schools.com/asp/ado_ref_recordset.asp and here
https://msdn.microsoft.com/en-us/library/ms675841(v=vs.85).aspx.
I know that I can get the information with something like
name = rs.fields(1)
address = rs.fields(4)
city = rs.fields(5)
'...
I'd rather get the index dynamically. If people change the database around I'd like for the formula to be stable.
Lets say if the field "Address" could be index 4, 5, 6, 7, until whenever.
How do I dynamically find the index of a specific field in a recordset object?
So, how do I dynamically find the index of a specific field in a recordset object? Lets say if the field "Address" could be index 4,5,6,7, until whenever.
There isn't a direct property in an ADO recordset to get this, but you can find it by looping through the fields and keeping a tally like I do in this function:
Public Function GetRecordsetFieldIndexFromName(rs As adodb.Recordset, ColumnName As String) As Variant
' Pass in an ADODB recordset and a column name and return the column index.
' Returns index in base 0.
' Ben S. - 11/8/2019
On Error GoTo ErrorHandler
Dim i As Long
If rs Is Nothing Then
' Recordset is not loaded
Else
For i = 0 To rs.Fields.count - 1
'Debug.Print i, rs.Fields(i).Name
If rs.Fields(i).Name = ColumnName Then
GetRecordsetFieldIndexFromName = i
Exit For
End If
Next
End If
Exit_Function:
Exit Function
ErrorHandler:
MsgBox "Error #" & err.Number & " - " & err.Description & vbCrLf & "in procedure GetRecordsetFieldIndexFromName"
GoTo Exit_Function
Resume Next
Resume
End Function
Here is a simple test that you can try in Access.
Public Sub TestADOrs()
' BS 11/8/2019 - Test for GetRecordsetFieldIndexFromName
Dim i As Long
Dim strSQL As String
Dim conn As New adodb.Connection
Dim rs As New adodb.Recordset
' Set an object pointing to the current database connection
Set conn = CurrentProject.Connection
strSQL = "Select Top 1 * From MSysObjects"
rs.Open strSQL, conn, adOpenStatic, adLockOptimistic
Debug.Print GetRecordsetFieldIndexFromName(rs, "Flags") ' This should return 4
Set rs = Nothing
Set conn = Nothing
End Sub
I made similar functions to this that work with List Box and Combo Box controls. They will let you return the index or the value from the control by passing the control and the column/field name.
https://stackoverflow.com/a/58773219/1898524 - Reference List Box Column by Field Name
I have created a form in which when I click a button(subMnuPrintStaff), it should open an Excel file(WorkerNames.xls). The Excel file gets its records from my database(Employee.mdb). However, the problem is that when I update my databasefile(Employee.mdb), the records on my Excel file does not get updated. How do I fix this?
I am using flexgrid.
BUTTON CODE:
Private Sub subMnuPrintStaff_Click()
'On Error GoTo er
Dim oExcel As Object
Set oExcel = CreateObject("Excel.Application")
Dim oWorkBook As Object
Dim oWorkSheet As Object
Dim i As Integer, k As Integer
Dim lRow As Long
Dim LastRow As Long
Dim LastCol As Long
oExcel.Visible = False
oExcel.Workbooks.Open App.Path & "\WorkerNames.xls"
Set oWorkSheet = oExcel.Workbooks("WorkerNames.xls").Sheets("WorkerNames")
i = 2 'Row in Excel
LastRow = DataGrid1.Row 'Save Current row
LastCol = DataGrid1.Col 'and column
DataGrid1.Row = 0 'Fixed Row is -1
Do While DataGrid1.Row <= DataGrid1.VisibleRows - 1
For k = 1 To DataGrid1.Columns.Count - 1
DataGrid1.Col = k 'Fixed Column is -1
oWorkSheet.Cells(i, k).Font.Bold = False
oWorkSheet.Cells(i, k).Font.Color = vbBlack
oWorkSheet.Cells(i, k).Value = DataGrid1.Text
Next
i = i + 1
If DataGrid1.Row < DataGrid1.VisibleRows - 1 Then
DataGrid1.Row = DataGrid1.Row + 1
Else
Exit Do
End If
Loop
DataGrid1.Row = LastRow 'Restore original Row
DataGrid1.Col = LastCol 'and Column
oExcel.Workbooks("WorkerNames.xls").Save
oExcel.Workbooks("WorkerNames.xls").Close savechanges:=True
oExcel.Quit
'cmdView.Enabled = True
'er:
'If err.Number = 1004 Then
'Exit Sub
'End If
On Error GoTo ErrHandler
Dim xlApp As Object
Dim xlWB As Object
Set xlApp = CreateObject("Excel.Application")
xlApp.Visible = True
Set xlWB = xlApp.Workbooks.Open("WorkerNames.xls")
Exit Sub
ErrHandler:
MsgBox "There is a problem opening that workbook!", vbCritical, "Error!"
End Sub
FORM LOAD CODE:
Dim oRs As New ADODB.Recordset
Dim adoConn2 As ADODB.Connection
Set adoConn2 = New ADODB.Connection
adoConn2.ConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source = " & App.Path & "\Employee.mdb"
adoConn2.Open
oRs.CursorLocation = adUseClient
oRs.Open "select * from employeeName", adoConn2, adOpenKeyset, adLockPessimistic
Set DataGrid1.DataSource = oRs
DataGrid1.Refresh
Any help would be greatly appreciated. Database and Excel files are in the same directory with the project.
CODE FOR SAVING DATA INTO MY DATABASE - using text boxes
Dim adoConn As New ADODB.Connection Dim constr, curSql As String constr = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source= " & App.Path & "\employee.mdb;Persist Security Info=False"
Set adoConn = New ADODB.Connection
adoConn.ConnectionString = constr adoConn.Open
If txtFirstName.Text = "" Or txtLastName.Text = "" Then
MsgBox "Some fields are empty!", vbInformation + vbOKOnly, "Empty Fields"
Else curSql = "INSERT INTO employeename(Firstname, LastName) VALUES ("curSql = curSql & "'" & Replace(txtFirstName.Text, "'", "''") & "'," curSql = curSql & "'" & Replace(txtLastName.Text, "'", "''") & "')"
adoConn.Execute curSql
adoConn.Close
MsgBox "Data successfully added!", vbOKOnly, "Success!"
txtFirstName.Text = ""
txtLastName.Text = ""