Example data
Dim strFileName As String
strFileName = "Acquired last 30 days.xlsx"
Dim lDays As Long
lDays = 2
Dim chkDate As Date
chkDate = Date - lDays
Dim cn As Object, cmd As Object, rs As Object
'---Connecting to the Data Source---
Set cn = CreateObject("ADODB.Connection")
Set cmd = CreateObject("ADODB.Command")
With cn
.Provider = "Microsoft.ACE.OLEDB.12.0"
.ConnectionString = "Data Source=" & ThisWorkbook.Path & "\" & strFileName & ";Extended Properties=""Excel 12.0 Xml;HDR=NO;IMEX=1"";"
.Open
End With
' Assemble paramaters and run
With cmd
.ActiveConnection = cn
.CommandType = 1
.CommandText = "SELECT [F1],[F3],[F4] FROM [Sheet 1$] WHERE F4>?"
.Parameters.Append .CreateParameter("pDate4", 7, 1, 30, chkDate)
Set rs = .Execute
End With
If Not rs.EOF Then
Do
Debug.Print rs(0) & "-" & rs(1) & "-" & rs(2)
rs.Movenext
Loop Until rs.EOF
End If
I've tried with and without parameters, with and without #, with and without BETWEEN, with and without using the same date format, with and without CDate() in the the where and select clauses (get an invalue Null error in the where clause - likely because of the blank lines at the top of the table). My basic test results return all the values or none of the values. Some examples:
All values including header:
"SELECT [F1],[F3],[F4] FROM [Sheet 1$] WHERE F4>#2018/01/05#"
"SELECT [F1],[F3],[F4] FROM [Sheet 1$] WHERE F4>#01/05/2018#"
Just header:
"SELECT [F1],[F3],[F4] FROM [Sheet 1$] WHERE F4>#05/01/2018#"
Nothing:
"SELECT [F1],[F3],[F4] FROM [Sheet 1$] WHERE F4 BETWEEN #01/01/2019# AND #01/01/2021#"
The data starts around row 15, and the date in the column 4 is stored as a date in Excel but formatted dd-mmm-yyyy. I'd like to avoid opening the file and reformatting it.
I'm at a loss. The only thing I can think of is to pull all the data and then evaluate it line by line in the recordset before writing, but I'd REALLY like to avoid that as well.
What am I doing wrong????
This worked for me:
With cmd
.ActiveConnection = cn
.CommandType = 1
.CommandText = "SELECT [F1],[F3],[F4] FROM [Sheet1$] WHERE F4 > ?"
.Parameters.Append .CreateParameter("pDate4", 5, 1, 30, CLng(chkDate)) '5=adDouble
Set rs = .Execute
End With
If Not rs.EOF Then
Do
Debug.Print rs(0) & "-" & rs(1) & "-" & Format(rs(2).Value, "mm/dd/yyyy")
rs.Movenext
Loop Until rs.EOF
End If
It's not what I was looking for, but this was the only thing I could get to work.
I ended up writing a loop to put together the strings so that they match the results I was seeing from the recordset. (The column names are slightly different as I paired back the table for my original question, but you get the idea.)
Dim i As Long
lDays = lDays - 1
Dim strDate As String
For i = 0 To lDays
strDate = CStr(Format(Date - i, "dd-mmm-yyyy"))
strTemp = AHK_Append(strTemp, "F9='" & strDate & "' OR F15='" & strDate, "' OR ")
Next
strTemp = " WHERE F8='Acquired' AND (" & strTemp & "')"
I really wish there was a way to convert a value prior to evaluating it in the WHERE clause, but I'm moving on.
Related
I tried the code in this link to push and retrieved the data between Excel and Access. I modified the code based on my file path as following:
EDITED NEW CODE BLOCK
Sub UpdateMDB()
Dim accConn As Object, accRST As Object
Dim accFile As String, accStr As String
Dim lastrow As Long, i As Long
lastrow = Workbooks(1).Sheets(1).Cells(Workbooks(1).Sheets(1).Rows.Count, "A").End(xlUp).Row
accFile = "Z:\Documents\Database\Database1.mdb"
accStr = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & accFile & ";"
Set accConn = CreateObject("ADODB.Connection")
Set accRST = CreateObject("ADODB.Recordset")
accConn.Open accStr
accRST.Open "SELECT * FROM Table1", accConn, adOpenKeyset, adLockOptimistic, adCmdTableDirect
If Not (accRST.BOF And accRST.EOF) Then
accRST.MoveFirst
Else
MsgBox "No records in Access table.", vbInformation
accRST.Close: accConn.Close: Set accRST = Nothing: Set accConn = Nothing
Exit Sub
End If
Do While Not accRST.EOF
For i = 1 To lastrow
If accRST!ID = Workbooks(1).Sheets(1).Range("A" & i) _
And accRST!Field1 <> Workbooks(1).Sheets(1).Range("B" & i) Then
accRST!Field1.Value = Workbooks(1).Sheets(1).Range("B" & i)
End If
Next i
accRST.Update
accRST.MoveNext
Loop
accRST.Close: accConn.Close
Set accRST = Nothing: Set accConn = Nothing
End Sub
INITIAL CODE BLOCK
Sub GetMDB()
Dim cn As Object
Dim rs As Object
strFile = "Z:\Documents\Database\Database1.mdb"
strCon = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & strFile & ";"
Set cn = CreateObject("ADODB.Connection")
Set rs = CreateObject("ADODB.Recordset")
cn.Open strCon
strSQL = "SELECT * FROM Table1"
rs.Open strSQL, cn
With Worksheets(1)
For i = 0 To rs.Fields.Count - 1
.Cells(1, i + 1) = rs.Fields(i).Name
Next
rs.MoveFirst
.Cells(2, 1).CopyFromRecordset rs
End With
End Sub
Sub UpdateMDB()
Dim cn As Object
Dim rs As Object
''It would probably be better to use the proper name, but this is
''convenient for notes
strFile = Workbooks(1).FullName
''Note HDR=Yes, so you can use the names in the first row of the set
''to refer to columns
strCon = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & strFile _
& ";Extended Properties=""Excel 8.0;HDR=Yes;IMEX=1"";"
Set cn = CreateObject("ADODB.Connection")
Set rs = CreateObject("ADODB.Recordset")
cn.Open strCon
''Selecting the cell that are different
strSQL = "SELECT * FROM [Sheet1$] s " _
& "INNER JOIN [;Database=Z:\Documents\Database\Database1.mdb;].Table1 t " _
& "ON s.id=t.id " _
& "WHERE s.Field1<>t.Field1"
rs.Open strSQL, cn, 1, 3 ''adOpenKeyset, adLockOptimistic
''Just to see
''If Not rs.EOF Then MsgBox rs.GetString
''Editing one by one (slow)
rs.MoveFirst
Do While Not rs.EOF
rs.Fields("t.Field1") = rs.Fields("s.Field1")
rs.Update
rs.MoveNext
Loop
''Batch update (faster)
strSQL = "UPDATE [;Database=Z:\Documents\Database\Database1.mdb;].Table1 t " _
& "INNER JOIN [Sheet1$] s " _
& "ON s.id=t.id " _
& "SET t.Field1=s.Field1 " _
& "WHERE s.Field1<>t.Field1 "
cn.Execute strSQL
End Sub
Reading data from Access to Excel GetMDB() macro works fine, But when I tried to update the data from Excel to Access, code gives me following error:
Run-time error '3021':
Either BOF or EOF is True, or the current record has been deleted.
Requested operation requires a current record.
I checked the mdb, xlsx and sheet path and names are correct. Anyone got a similar problem as well and how to overcome? Thanks.
You cannot run UPDATE queries using Excel workbook sources as any SQL queries using workbooks are read-only from last saved instance and cannot be updated. Excel simply is not a database to do such transactions with no record-level locking mechanism, read/write access, or relational model. Though you can run append (INSERT INTO ... SELECT *) and make-table queries (SELECT * INTO FROM ...), you cannot run UPDATE that aligns to live values.
However, you can read in an Access recordset and iterate through the Excel cells aligning by ID matches. Below assumes the Excel Sheet's ID column is in Column A and Field1 is in Column B.
Dim accConn As Object, accRST As Object
Dim accFile As String, accStr As String
Dim lastrow As Long, i As Long
Const adOpenKeyset = 1, adLockOptimistic = 3, adCmdTableDirect = 512
lastrow = Workbooks(1).Sheets(1).Cells(Workbooks(1).Sheets(1).Rows.Count, "A").End(xlUp).Row
accFile = "Z:\Documents\Database\Database1.mdb"
accStr = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & accFile & ";"
Set accConn = CreateObject("ADODB.Connection")
Set accRST = CreateObject("ADODB.Recordset")
accConn.Open accStr
accRST.Open "SELECT * FROM Table1", accConn, adOpenKeyset, adLockOptimistic, adCmdTableDirect
If Not (accRST.BOF And accRST.EOF) Then
accRST.MoveFirst
Else
Msgbox "No records in Access table.", vbInformation
accRST.Close: accConn.Close: Set accRST = Nothing: Set accConn = Nothing
Exit Sub
End If
Do While Not accRST.EOF
For i = 1 to lastrow
If accRST!ID = Workbooks(1).Sheets(1).Range("A" & i) _
And accRST!Field1 <> Workbooks(1).Sheets(1).Range("B" & i) Then
accRST!Field1.Value = Workbooks(1).Sheets(1).Range("B" & i)
End If
Next i
accRST.Update
accRST.MoveNext
Loop
accRST.Close: accConn.Close
Set accRST = Nothing: Set accConn = Nothing
Notes:
If IDs between Excel worksheet and Access table are not one-to-one (i.e., Excel has multiple rows of same ID), the last Field1 value following the If logic will be inserted to corresponding Access row.
Above may be extensive processing if database rows and Excel cells are large. The best option is simply to use Access for all data entry/management and avoid the update needs. Since Excel is a flatfile, consider using it as the end use application and Access as central data repository.
I have queried a excel using ADODB and have a set of data which should be copied to the second worksheet, but when ever i try to insert data it always sits on the 2nd row ignoring the 1st one. I think this is because the 1st row is always treated as header. Are there any ways to insert data into the excel by creating 1st row as header and subsequently with values(with respect to the code i have written below).
Exceldb = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\VAS_AUTOMATION_v1.1\Book1.xlsx;Extended Properties=""Excel 12.0;HDR=Yes;"";"
Dim sDBConnectionString, i, iCol
Set con = CreateObject("ADODB.Connection")
sDBConnectionString = Exceldb
con.ConnectionString = sDBConnectionString
con.Open
If Err.Number <> 0 Then
For i = 1 to 4
Err.Clear
wait 5
con.Open
If Err.Number = 0 Then
msgbox "opened"
End If
Next
else
msgbox "opened"
End If
strROW = "UNIXOPERATIONS_16"
strROWID = "'" & strROW & "'"
strQuery = "INSERT INTO [Sheet3$A1:A1] (" & strROWID1 & ") Values(" & strROWID & ")"
Set strRecordSet = CreateObject("ADODB.Recordset")
Dim objCmd 'As New ADODB.Command
Set objCmd = CreateObject("ADODB.Command")
Set objCmd.ActiveConnection = con
objCmd.CommandType = 1
objCmd.CommandText = strQuery
Set strRecordSet = objCmd.Execute
con.Close
I'm stuck for a formula. Essentially what i want to do is count the number of times a particular value appears in one sheet, based on data pulled from another sheet.
E.g. I have three sheets. One sheet has a list of jobs and a code associated with a client. The second sheet has a list of the clients and details on the client. The third sheet is my results sheet.
I want to count the How Heards for each company. Eg, in sheet 1 below Apple has 3 customers. If we use the Client Code Id's and go to Sheet 2, we can see that it will total 2 Online and 0 Facebook. This result displays on Sheet 3. The results sheet.
Sheet 1 Example
Sheet 2 Example
Sheet 3 Example (What i want the results to look like from calculation)
In the interest of actually providing a suitable answer to this question, here is some VBA code that allows a workbook to create an ADO connection to itself and generate a report using SELECT, DISTINCT, WHERE, INNER JOIN, GROUP BY and ORDER BY clauses.
Sub Inner_Join()
Dim cnx As Object, rs As Object
Dim sWS1 As String, sWS2 As String, sWB As String, sCNX As String, sSQL As String
Dim ws1TBLaddr As String, ws2TBLaddr As String
'Collect some string literals that will be used to build SQL
ws1TBLaddr = Worksheets("Sheet1").Cells(1, 1).CurrentRegion.Address(0, 0)
sWS1 = Worksheets("Sheet1").Name
ws2TBLaddr = Worksheets("Sheet2").Cells(1, 1).CurrentRegion.Address(0, 0)
sWS2 = Worksheets("Sheet2").Name
sWB = ThisWorkbook.FullName
'Build the connection string
'The first is for 64-bit Office; the second is more universal
sCNX = "Provider=Microsoft.Jet.OLEDB.12.0;Data Source=" & sWB _
& ";Extended Properties=""Excel 8.0;HDR=Yes;IMEX=1"";"
sCNX = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & sWB _
& ";Extended Properties=""Excel 8.0;HDR=Yes;IMEX=1"";"
'Debug.Print sCNX
'Create the necessary ADO objects
Set cnx = CreateObject("ADODB.Connection") 'late binding; for early binding add
Set rs = CreateObject("ADODB.Recordset") 'Microsoft AxtiveX Data Objects 6.1 library
'Open the connection to itself
cnx.Open sCNX
With Worksheets("Sheet3")
'Clear the reporting area
.Cells(1, 1).CurrentRegion.ClearContents
'get [Business Name] list from Sheet1
sSQL = "SELECT DISTINCT w1.[Business Name]"
sSQL = sSQL & " FROM [" & sWS1 & "$" & ws1TBLaddr & "] w1"
sSQL = sSQL & " ORDER BY w1.[Business Name]"
'Debug.Print sSQL
'Populate Sheet3!A:A
rs.Open sSQL, cnx
Do While Not rs.EOF
'Debug.Print rs.Fields("Business Name")
.Cells(.Rows.Count, 1).End(xlUp).Offset(1, 0) = rs.Fields("Business Name")
rs.MoveNext
Loop
rs.Close
'get [How Heard] list from Sheet2
sSQL = "SELECT DISTINCT w2.[How Heard]"
sSQL = sSQL & " FROM [" & sWS2 & "$" & ws2TBLaddr & "] w2"
sSQL = sSQL & " WHERE w2.[How Heard] NOT LIKE 'None'"
sSQL = sSQL & " ORDER BY w2.[How Heard]"
'Debug.Print sSQL
'Populate Sheet3!1:1
rs.Open sSQL, cnx
Do While Not rs.EOF
'Debug.Print rs.Fields("How Heard")
.Cells(1, .Columns.Count).End(xlToLeft).Offset(0, 1) = rs.Fields("How Heard")
rs.MoveNext
Loop
rs.Close
'start by seeding zeroes for all
With .Cells(1, 1).CurrentRegion
With .Resize(.Rows.Count - 1, .Columns.Count - 1).Offset(1, 1)
.Cells = 0
End With
End With
'get the counts for the [Business Name]×[How Heard] combinations
sSQL = "SELECT COUNT(w1.[Business Name]), w1.[Business Name], w2.[How Heard]"
sSQL = sSQL & " FROM [" & sWS1 & "$" & ws1TBLaddr & "] w1"
sSQL = sSQL & " INNER JOIN [" & sWS2 & "$" & ws2TBLaddr & "] w2 ON w1.[Client Code] = w2.[Client Code]"
sSQL = sSQL & " WHERE w2.[How Heard] <> 'None'"
sSQL = sSQL & " GROUP BY w1.[Business Name], w2.[How Heard]"
'Debug.Print sSQL
'Populate Sheet3 data matrix area
rs.Open sSQL, cnx
With .Cells(1, 1).CurrentRegion
Do While Not rs.EOF
'Debug.Print rs.Fields(0) & ":" & rs.Fields(1) & ":" & rs.Fields(2)
.Cells(Application.Match(rs.Fields(1), .Columns(1), 0), _
Application.Match(rs.Fields(2), .Rows(1), 0)) = rs.Fields(0)
rs.MoveNext
Loop
End With
rs.Close
End With
Final_Cleanup:
Set rs = Nothing
cnx.Close: Set cnx = Nothing
End Sub
Results should be similar to the following.
Ok, so I am really impressed with the answer by #Jeeped
My answer is not as flexible as being able to use arbitrary SQL but it doesn't use VBA so it might be useful as well in some contexts.
So my answer basically:
creates an array from Sheet1 that contains the client code for each matching cell(or 0 for non matching cells)
X = ((Sheet1!$B$2:$B$1000=$A2)*Sheet1!$A$2:$A$1000)
creates an array from Sheet2 that contains the client code for each matching cell(or 0 for non matching cells)
Y = ((Sheet2!$B$2:$B$2000=B$1)*Sheet2!$A$2:$A$2000)
compares every cell in the two arrays where the value of the first array isn't 0
Z = (X<>0)*(X=TRANSPOSE(Y))
and then sums up the number of matches:
=SUM(Z)
So the final formula for Sheet3!B2 is:
=SUM((((Sheet1!$B$2:$B$1000=$A2)*Sheet1!$A$2:$A$1000)<>0)*(((Sheet1!$B$2:$B$1000=$A2)*Sheet1!$A$2:$A$1000)=TRANSPOSE(((Sheet2!$B$2:$B$2000=B$1)*Sheet2!$A$2:$A$2000))))
It is an array formula so you need to press Control-Shift-Enter instead of just Enter. Then you need to copy it from B2 to C2, B3 and so on.
Obviously, You will have to change the 1000 to something bigger than the largest rwo on Sheet1 and the 2000 to something bigger than the largest row on Sheet2.
I am trying to use ACE sql to return array from recordset. The function works well with table ranges.
Problem: the function returns the right number of records if the query returns 2 or more records. However if only one record is found, all rows are filled repetitively with this one row. This is wrong but I cannot find the reason why.
In addition, I wish my function would return column names from Recordset. I have no idea how to glue it together with the array returned from recordset.
Here is the code, credits are due to the author of another solution that I am trying to adapt to my needs: Performing SQL queries on an Excel Table within a Workbook with VBA Macro
Function SQL(dataRange As Range, CritA As String) As Variant
Application.Volatile
Dim cn As ADODB.Connection
Dim rs As ADODB.Recordset
Dim currAddress As String
currAddress = ActiveSheet.Name & "$" & dataRange.Address(False, False)
strFile = ThisWorkbook.FullName
strCon = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & strFile _
& ";Extended Properties=""Excel 12.0;HDR=Yes;IMEX=1"";"
Set cn = CreateObject("ADODB.Connection")
Set rs = CreateObject("ADODB.Recordset")
cn.Open strCon
strSQL = "SELECT * FROM [" & currAddress & "]" & _
"WHERE [A] = '" & CritA & "' " & _
"ORDER BY 1 ASC"
rs.Open strSQL, cn
'SQL = rs.GetString
SQL = Application.Transpose(rs.GetRows)
Set rs = Nothing
Set cn = Nothing
End Function
If you want the field names, you will have to loop through the array from the recordset too:
With rs
If Not .EOF Then
vData = .getrows()
ReDim vDataOut(LBound(vData, 2) To UBound(vData, 2) + 1, LBound(vData, 1) To UBound(vData, 1))
' get headers
For i = 1 To .Fields.Count
vDataOut(0, i - 1) = .Fields(i - 1).Name
Next i
' Copy data
For x = LBound(vData, 2) To UBound(vData, 2)
For y = LBound(vData, 1) To UBound(vData, 1)
vDataOut(x + 1, y) = vData(y, x)
Next y
Next x
End If
End With
for example.
I have an Excel worksheet with around 100 cols. Does anyone know of an easy way to write the contents of each column to a csv or txt file?
I don't have Excel in front of me, but I think this code is approximately what you need, give or take some syntax errors. It should write each column into a separate file, with each cell on a different row. It will work for arbitrary column heights, though the number of columns is in a variable (for now).
dim fso as FileSystemObject
dim ts as TextStream
dim i as Integer
dim myCell as Range
set fso = FileSystemObject
for i = 0 to TotalColumnNumber
' last argument, True, says to create the text file if it doesnt exist, which is
' good for us in this case
Set ts = fso.OpenTextFile("column_" & i, ForWriting, True)
' set mycell to the first cell in the ith column
set myCell = SheetName.cells(1,i)
' continue looping down the column until you reach a blank cell
' writing each cell value as you go
do until mycell.value = ""
ts.writeline mycell.value
set myCell = myCell.offset(1,0)
loop
ts.close
next
set ts = nothing
set fso = nothing
Let me know if that helps or not, I can take another look later if you would like
Perhaps
Dim cn As Object
Dim rs As Object
Dim strFile As String
Dim strCon As String
Dim strSQL As String
Dim i As Integer
''This is not the best way to refer to the workbook
''you want, but it is very conveient for notes
''It is probably best to use the name of the workbook.
strFile = ActiveWorkbook.FullName
''Note that if HDR=No, F1,F2 etc are used for column names,
''if HDR=Yes, the names in the first row of the range
''can be used.
''This is the Jet 4 connection string, you can get more
''here : http://www.connectionstrings.com/excel
strCon = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & strFile _
& ";Extended Properties=""Excel 8.0;HDR=Yes;IMEX=1"";"
''Late binding, so no reference is needed
Set cn = CreateObject("ADODB.Connection")
Set rs = CreateObject("ADODB.Recordset")
cn.Open strCon
''WHERE 1=1 = headers only, note hdr=yes above
strSQL = "SELECT * " _
& "FROM [Sheet1$] " _
& "WHERE 1=1"
''Open the recordset for more processing
''Cursor Type: 3, adOpenStatic
''Lock Type: 3, adLockOptimistic
''Not everything can be done with every cirsor type and
''lock type. See http://www.w3schools.com/ado/met_rs_open.asp
rs.Open strSQL, cn, 3, 3
''Output including nulls. Note that this will fail if the file
''exists.
For i = 0 To rs.Fields.Count - 1
strSQL = "SELECT [" & rs.Fields(i).Name & "] " _
& "INTO [Text;HDR=YES;FMT=Delimited;IMEX=2;DATABASE=C:\Docs\]." _
& rs.Fields(i).Name & ".CSV " _
& "FROM [Sheet1$] "
''To skip nulls and empty cells, add a WHERE statement
''& "WHERE Trim([" & rs.Fields(i).Name & "] & '')<>'' "
cn.Execute strSQL
Next
''Tidy up
rs.Close
Set rs = Nothing
cn.Close
Set cn = Nothing
A very quick line to get you started ...
for i = 1 to 100
open "file" & i & ".txt" as #1
for each c in columns(i).cells
print #1, c.value
next c
close #1
next i