VBA ADO Date Formatting on Mixed Data Input - sql

we are trying to use ADO to read data from a closed workbook, remove any whitespace and convert any incorrectly keyed dates into a valid format. Once the data has been cleansed, it's uploaded into a custom app.
We are using ADO for speed purposes as we have found using VBA to open/manipulate/close takes too long, meaning we miss our upload target time (we have multiple workbooks we need to apply this to).
The problem we have is converting the dates to a valid format. Dates are entered into the workbook either as dd/mm/yy or dd.mm.yy - we have no control over this, the template was created years ago and we are unable to update it and apply data validation.
Ideas We Have Tried: We have a few ideas, but have not been successful, does anyone know if any of these suggestions could work / suggest alternate ideas?
Check for a "." and apply a Replace(): If InStr(rs.Fields("Date").Value, ".") > 0 Then rs.Fields("Date").Value = Replace(rs.Fields("Date").Value, ".", "/")
This works when the column is read into the record set as type 202: adVarWChar, unfortunatly as the majority of the dates are valid, the data in the record set is set as type 7: adDate, when looping, once we get to an invalid date format (with the dots), we get a debug error:
"you cannot record changes because a value you entered violates the settings defined for this table or list (for example, a value is less than the minimum or greater than the maximum). correct the error and try again"
Convert the whole column data type to 202 adVarWChar:
As the above code works for entries when they are formatted as text, we had an idea to see if we could pull the whole column of data in directly as text, we have experimented with Casting and Convert but cannot get it to work - I no longer have the sample code we were trying for that. I recall experimenting adding IMEX=1 to the connection string, but this didn't seem to make any difference.
Apply a Find/Replace query on a whole column:
Instead of retrieving the data and looping through it, we had an idea to apply a find and replace query directly on the column, similar to how we are able to trim a whole column. Again, we were unable to find any code/queries which worked.
Create an empty record set and set the column type to String:
We had an idea to create a blank/empty record set and manually set the date column to a string type, and then loop through the retrieved data and move them into the new record set. We didn't get very far with this as we weren't too sure how to create a blank RS, then we also thought, how would we write this data back to the worksheet - as I don't think you can write back to a closed workbook.
Here is the code I have at the moment:
Sub DataTesting()
On Error GoTo ErrorHandler
'set the workbook path of the file we want to read from
Dim workbookFileName As String
workbookFileName = "C:\Users\xxx\xxx\myWorkbook.xls"
'create a connection string
Dim connectionString As String
connectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" _
& workbookFileName _
& ";Extended Properties=""Excel 12.0 Xml;HDR=YES;"";" 'IMEX=1"";"
'open the connection
Dim conn As ADODB.connection
Set conn = New ADODB.connection
conn.connectionString = connectionString
conn.Open
Dim rs As ADODB.Recordset
Set rs = New ADODB.Recordset
'Convert all data in the date column to a valid date (e.g. replace dates with decimals 1.1.21 to 01/01/2021)
'set query to select all data from the date column
Dim query As String
query = "SELECT * FROM [DATA SHEET$B2:B100]" 'col B is the Date column
With rs
.ActiveConnection = conn
'.Fields.Append "Date", adVarChar, 20, adFldMayBeNull 'NOT WORKING
.CursorType = adOpenDynamic
.LockType = adLockOptimistic
.Source = query
.Open
If Not .BOF And Not .EOF Then
While (Not .EOF)
If InStr(rs.Fields("Date").Value, ".") > 0 Then rs.Fields("Date").Value = Replace(rs.Fields("Date").Value, ".", "/")
.MoveNext
Wend
End If
.Close
End With
conn.Close
GoTo CleanUp
ErrorHandler:
MsgBox Err.Description 'THIS WILL BE WRITTEN TO TXT FILE
CleanUp:
'ensure the record set is equal to nothing and closed
If Not (rs Is Nothing) Then
If (rs.State And adStateOpen) = adStateOpen Then rs.Close
Set rs = Nothing
End If
'ensure the connection is equal to nothing and closed
If Not (conn Is Nothing) Then
If (conn.State And adStateOpen) = adStateOpen Then conn.Close
Set conn = Nothing
End If
End Sub
UPDATE:
I am now able to read the data using the following query:
"SELECT IIF([Date] IS NULL, NULL, CSTR([Date])) AS [Date] FROM [DATA SHEET$B2:B10]"
This will only work if I set IMEX=1, which is only read-only. I am able to loop through each item and print out the value / detect where the dots are, but I cannot then amend them!
As mentioned by #Doug Coats I can move the data into an array, perform the manipulation on the array. But how exactly do I then put that array back into the recordset?
I guess I would need to close the first 'read only' connection, and re-open it as a 'write' connection. Then somehow run an update query - but how do I replace the existing record set values with the values from the array?
Thanks

You could try an update query
Const SQL = " UPDATE [DATA SHEET$] " & _
" SET [Date] = REPLACE([Date],""."",""/"")" & _
" WHERE INSTR([Date],""."") > 0 "
Dim n
conn.Execute SQL, n
MsgBox n & " records updated"
Sub testdata()
Dim wb, ws, i
Set wb = Workbooks.Add
Set ws = wb.Sheets(1)
ws.Name = "DATA SHEET"
ws.Cells(1, 2) = "Date"
For i = 2 To 10
If Rnd() > 0.5 Then
ws.Cells(i, 2) = "27.07.21"
Else
ws.Cells(i, 2) = "27/07/21"
End If
Next
wb.SaveAs "c:\temp\so\dates.xls"
wb.Close
End Sub

Related

#Value Error in a vba function to return a recordset

I wrote the below vba function aiming to return a SQL record set into a cell. I get #Value error when I use this function. It should get two arguments, namely two cells, and simply return Yes or No. I checked the SQL query and it works fine when written as a subroutine. So pardon for replacing some sql commands with *
Public Function TrRe(No1 As String, No2 As String) As ADODB.Recordset
Dim cn As ADODB.Connection
Dim rs As ADODB.Recordset
Dim SourceText As String
Set cn = New ADODB.Connection
cn.ConnectionString = _
"Provider=MSOLEDBSQL;Server=****;Database=*****;Integrated Security=SSPI"
cn.Open
Set rs = New ADODB.Recordset
SourceText = " SELECT " & _
" CASE " & _
" WHEN ***** LIKE 'TEST%' OR *****=1 THEN 'Yes' " & _
" ELSE 'No " & _
" END " & _
" FROM *** "
" WHERE ****=" & No1 & _
" AND ****=" & No2
rs.ActiveConnection = cn
rs.Source = SourceText
rs.CursorType = adOpenForwardOnly
rs.LockType = adLockReadOnly
rs.Open
Set TrRe= rs
rs.Close
cn.Close
Set cn = Nothing
End Function
I would appreciate your comments/helps.
Update: a) The query always returns a 1x1 recordset and this is Yes or No.
b) The code should be written as a Function since I want to let the user to choose the inputs.
AS #Rory stated, you can't write a recordset into a cell. A RecordSet is an object, containing all kind of information, eg the field names and -types, the state, the connection, somehow the information about the current row that was read and so on. You can't (and you don't want to) write all that into a cell.
Your sql statement will return one row with exactly one field (containing either the string yes or no). In that case, you can simply write (note that this statement is without Set)
TrRe = rs(0)
That is a shortcut, fetching the first field of the current row. Current row is the first row, assuming that the sql statement was executed successfully and returns at least one row. To move to the next row, you would use rs.MoveNext. To check if there are any more rows, use If rs.EOF Then.
If you issue an sql that returns more than that, there are two handy commands:
(1) To write the complete data that was returned into Excel, use Range.CopyFromRecordset
ThisWorkbook.Sheets(1).Range("A2").CopyFromRecordset rs
(2) To read all data into a 2-dimensional array use RecordSet.GetRows
Dim data as Variant
data = rs.GetRows
dim row as long, col as long
For row = 0 To UBound(data, 2) ' Index for rows = 2
For col = 0 to UBound(data, 1) ' Index for fields (columns) = 1
... (do your stuff here)
next col
Next row

VB6 ADODB.Recordset Record.Count doesn't work / EOF & BOF not available

I've got a question regarding ADODB recordset in VB6, which has confused me for a few weeks. I've written the recordset into worksheets to achieve some results that I can't get from recordset directly.
But as the data set builds up, writing the recordset into worksheet would slow the program down, I wonder if someone could resolve the recordset puzzle for me.
Below is the problem I have -
1) xRst.Recordcount always returns -1
2) Error message, "Arguments are of the wrong type, are out of acceptable range, or are in conflict with one another", pops up on set (A) .cursorlocation to either adUseClient or adUseServer, and (B) .LockType
3) Unable to .getrows on recordset => I believe it's the same cause as xRst.Recordcount returning -1 ?
Below is part of my code. Could above issues caused by limitation of the provider?
xConnstring = "Provider=Microsoft.ACE.OLEDB.12.0; Extended Properties='Excel 12.0'; Data Source =" & Thisworkbook.fullname
xCnn.Open xConnstring
xSqlstring = " SELECT * FROM [APRI$] "
Set xRst = xCnn.Execute(xSqlstring)
Msgbox(xRst.RecordCount)
Do Until xRst.EOF
......
xRst.MoveNext
Loop
For the recordset, I've also tried two open methods, but doesn't work either.
Set xRst.ActiveConnection = xCnn
xRst.Source = xSqlstring
xRst.CursorType = adOpenDynamic
------Error Message Occurs On Below Two Lines------
xRst.CursorLocation = adUseServer
xRst.LockType = adLockOptimistic
xRst.Open
Below code will encounter an error, but it will pass through when the two last parameters are removed
xRst.Open xSqlstring, xCnn, adOpenKeyset, adUseServer, adLockoptimistic
Could someone please kindly advise how I can get the 1) recordset.recordcount, 2) recordset.movenext work?
Thanks heaps in advance.
Default cursortype is adOpenForwardOnly. With adOpenForwardOnly or adOpenUnspecified the record count is always returned as -1. Use either adOpenKeySet or adOpenStatic. ie: (I assume sheet name APRI is correct and not APRIL - and there is a worksheet named Dummy to list the results for test):
Dim xCnn As ADODB.Connection
Dim xRst As ADODB.Recordset
Dim xConnString As String
xConnString = "Provider=Microsoft.ACE.OLEDB.12.0; Extended Properties='Excel 12.0'; Data Source =" & ThisWorkbook.FullName
Set xCnn = New ADODB.Connection
xCnn.Open xConnString
xSqlstring = " SELECT * FROM [APRI$] "
Set xRst = New ADODB.Recordset
xRst.Open xSqlstring, xCnn, adOpenStatic
MsgBox (xRst.RecordCount)
Dim row As Integer
row = 1
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Dummy")
Do Until xRst.EOF
'...
ws.Cells(row, 1).Value = xRst.Fields(0).Value
row = row + 1
xRst.MoveNext
Loop

Excel VBA Recordset not returning all columns from SQL Server View

Strange issue and not really sure where to begin.
Trying to get SQL Server data into Excel so the user can refresh the query and it'll pull the latest data. I have a View created that links a few tables and smartens up the field names.
I have the VBA to copy from recordset into my sheet, but after the 11th column, it doesn't return anything. Half the fields are missing essentially.
If I amend the query to return just the column that is missing, it returns about 30 records out of 12,000 and spaced with about 140 cells of blanks (each gap is different) between each record it does return.
Not really sure what I'm doing wrong here.
VBA code here:
Public Function dbString() As String
dbString = "DRIVER={SQL Server};SERVER=<<SERVERNAME>>;DATABASE=<<DATABASENAME>>;Integrated_Security=SSPI"
End Function
Sub getSQLData_QA()
Dim db As Object
Dim rs As Object
Set db = CreateObject("ADODB.Connection")
Set rs = CreateObject("ADODB.Recordset")
Set qa = Sheets("QA_Listing")
Application.Calculation = xlCalculationManual
db.Open dbString
With rs
.ActiveConnection = db
.Open " SELECT * " & _
" FROM SRV_QBA__13W; "
End With
qa.Range("A2:Z100000").Delete xlUp
qa.Range("A2").CopyFromRecordset rs
rs.Close
db.Close
Set db = Nothing
Set rs = Nothing
Application.Calculation = xlCalculationAutomatic
End Sub

Excel manufacturing dashboard with vba

I have an excel sheet detailing a list of equipment (about 12000 rows in the sheet). I want to make a dashboard whereby I enter the name of the equipment and it returns the Manufacturer, Date of manufacture and description.
What would be the best way to go about this? I was thinking of writing VBA code in order to match an input to an object type and return the required value however I have not coded in VBA and I'm unsure how to type it out.
Forgive me in suggesting a different approach but consider the scalable, relational advantage of SQL (Structured Query Language) that can take the Equipment Name as parameter and query your data into a filtered table resultset. If using Excel for PC, Excel can run SQL using the Jet/ACE SQL Engine (Windows .dll files), the very engine that powers its sibling, MS Access. This approach avoids array formulas, loops, if/then logic, complex multiple index/matching, and vlookups.
Below example prompts user for the Equipment Name using an InputBox which is then passed as a parameter to the WHERE clause of SQL query. We hate for a malicious user to run SQL injection in input box, something like: 1; DELETE FROM [DATA];. Example assumes data exists in a tab called DATA with column headers in first row and an empty tab called RESULTS. Adjustments can be made.
Sub RunSQL()
Dim conn As Object, rst As Object, cmd As Object
Dim equipmentVar As String, strConnection As String, strSQL As String
Dim i As Integer
Const adcmdText = 1, adVarChar = 200, adParamInput = 1
Set conn = CreateObject("ADODB.Connection")
Set rst = CreateObject("ADODB.Recordset")
' RECEIVE USER INPUT
equipmentVar = InputBox("Enter name of equipment.", "EQUIPMENT SEARCH")
If equipmentVar = "" Then Exit Sub
' CONNECTION STRINGS (TWO VERSIONS)
' strConnection = "DRIVER={Microsoft Excel Driver (*.xls, *.xlsx, *.xlsm, *.xlsb)};" _
' & "DBQ=C:\Path\To\Workbook.xlsm;"
strConnection = "Provider=Microsoft.ACE.OLEDB.12.0;" _
& "Data Source='C:\Path\To\Workbook.xlsm';" _
& "Extended Properties=""Excel 8.0;HDR=YES;"";"
strSQL = " SELECT [DATA$].Manufacturer, [DATA$].Equipment, " _
& " [DATA$].[Date of Manufacturer], [DATA$].[Description] " _
& " FROM [DATA$]" _
& " WHERE [DATA$].[Equipment] = ?;"
' OPEN DB CONNECTION
conn.Open strConnection
' SET CMD COMMAND
Set cmd = CreateObject("ADODB.Command")
With cmd
.ActiveConnection = conn
.CommandText = strSQL
.CommandType = adcmdText
.CommandTimeout = 15
End With
' BINDING PARAMETER
cmd.Parameters.Append cmd.CreateParameter("equipParam", adVarChar, adParamInput, 255)
cmd.Parameters(0).Value = equipmentVar
' EXECUTING TO RECORDSET
Set rst = cmd.Execute
' COLUMN HEADERS
For i = 1 To rst.Fields.Count
Worksheets("RESULTS").Cells(1, i) = rst.Fields(i - 1).Name
Next i
' DATA ROWS
Worksheets("RESULTS").Range("A2").CopyFromRecordset rst
rst.Close: conn.Close
Set rst = Nothing: Set cmd = Nothing: Set conn = Nothing
End Sub
Sounds like a simple loop through the cells looking for a match, because you have not given the layout I can't tell you the exact code but you are asking for an approach so here it goes.
1) If you want it to work when you enter a value in the cell then you use worksheet_Change i.e.
Options Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Address = "$A$1" Then
' Your code here
End If
End Sub
The quickest way to code a loop through your 12000 rows but could be any length in future (assuming they start at row 5)
Dim cell As Range
For Each cell In Range("a5", Range("a" & Cells.Rows.Count).End(xlUp))
'Your if statement to determin if it is a match goes here
'Your copy code goes here
Next cell
2)You then put in the If statement to check for a match and do any copy display you want when you find one.
Good luck
Use the filter feature / button in the Ribbon.

Select query to Access from VBA not returning duplicate values

Any help with this issue is greatly appreciated.
I am trying to retrieve from Access, by means of Select, multiple values with the same ID and have it pasted into an Excel sheet. I am running the code from Excel VBA.
The query I am using to retrieve said values is:
SELECT Role
FROM Roles
WHERE App_ID=(SELECT ID FROM Apps WHERE NAME='app name');
which is assigned to a variable and afterwards executed by using Set variable = cn.Execute(variable). The problem is that this query, executed from Excel VBA, returns only the first value found. Now, if I run this query from Access it returns every value with the ID for the specified app.
I have tried tried using INNER JOIN, IN, HAVING, etc. but it just wont retrieve all of the values into Excel. Like I said, the query works fine when used in Access so I know this must be limitation in Excel.
Thank you for any help you guys can provide.
Assuming you are using ADODB in Excel, keep in mind that the Execute function returns a Recordset. You can loop through the Recordset to see the additional rows.
Set rng = ActiveSheet.Range("A2")
Set rst = cn.Execute(strSQL)
With rst
Do While Not .EOF
rng = CStr(!Role)
Set rng = rng.Offset(1)
.MoveNext
Loop
End With
'Applies to Access 2010
'Reference Microsoft ActiveX Data Objects 6.1 Library
Dim strSQL As String
Dim strDBPathName As String
Dim strConProvider As String
Dim strPersist As String
Dim conADODB As ADODB.Connection
Dim rsADODB As ADODB.Recordset
Set conADODB = New ADODB.Connection
strConProvider = "Provider=Microsoft.ACE.OLEDB.12.0;"
'Database path name
strDBPathName = "Data Source=C:\Temp\Database.accdb;"
strPersist = "Persist Security Info=False;"
With conADODB
.ConnectionString = strConProvider & strDBPathName & strPersist
.Open
End With
strSQL = "SELECT Role FROM Roles WHERE App_ID=(SELECT ID FROM Apps WHERE NAME='app name')"
Set rsADODB = New ADODB.Recordset
With rsADODB
.Open strSQL, conADODB, adOpenStatic, adLockPessimistic
If Not (.EOF And .BOF) Then
'Range of spreadsheet to paste records
Cells(1, 1).CopyFromRecordset rsADODB
End If
.Close
End With
Set rsADODB = Nothing
conADODB.Close
Set conADODB = Nothing