I have a VBA function returning a decimal value based on a WHERE clause.
SELECT * FROM [tbl_break] WHERE [From] < 6,25 AND [To] >= 6,25
I am getting the error...
Syntax error (comma) in query expression '[From] < 6,25 AND [To] >= 6,25'.
The Tabel look like this...
From To Break
0,00 4,00 0,25
4,00 8,00 0,50
8,00 12,00 0,75
How can I deal with decimals in query statements?
Public Function CalculateBreak(Units) As Double
On Error GoTo ErrorHandler
Dim connection As Object, result As Object
Dim sql As String
sql = "SELECT * FROM [tbl_break] WHERE [From] < " & Units & " AND [To] >= " & Units
Debug.Print sql
' Connect to the current datasource of the Excel file
Set connection = CreateObject("ADODB.Connection")
With connection
.Provider = "Microsoft.ACE.OLEDB.12.0"
.ConnectionString = "Data Source=" & ThisWorkbook.Path & "\" & ThisWorkbook.Name & ";" & _
"Extended Properties=""Excel 12.0 Xml;HDR=YES"";"
.Open
End With
' Run the SQL query
Set result = connection.Execute(sql)
CalculateBreak = result(2)
Debug.Print result(2)
Exit Function
ErrorHandler:
CalculateBreak = 0
Debug.Print Err.Description
End Function
The solution works well with integers but I depend on decimal numbers.
Use Str to force a string expression of the decimal value with a dot as the separator which is was SQL expects:
sql = "SELECT * FROM [tbl_break] WHERE [From] < " & Str(Units) & " AND [To] >= " & Str(Units) & ""
Result:
SELECT * FROM [tbl_break] WHERE [From] < 6.25 AND [To] >= 6.25
Related
I am trying to find a way to calculate the median of a dataset in access, that is grouped by two columns, typeA, typeB.
This is a sample of the table:
ID (autonumber)
typeA (large number)
typeB (large number)
total (large number)
1
1
1
15
2
2
1
15
3
1
1
45
4
2
1
44
5
1
2
19
6
1
2
4
7
1
2
34
8
2
2
19
9
2
2
18
Using Access 2016
Currently I am using the following code snippet:
Function fMedian(SQLOrTable, GroupFieldName, GroupFieldValue, GroupFieldName2, GroupFieldValue2, MedianFieldName)
DoCmd.SetWarnings False
Dim rs As DAO.Recordset
Set db = CurrentDb
Set rs1 = db.OpenRecordset(SQLOrTable, dbOpenDynaset)
If IsDate(GroupFieldValue) Then
GroupFieldValue = "#" & GroupFieldValue & "#"
ElseIf Not IsNumeric(GroupFieldValue) Then
GroupFieldValue = "'" & Replace(GroupFieldValue, "'", "''") & "'"
End If
If IsDate(GroupFieldValue2) Then
GroupFieldValue2 = "#" & GroupFieldValue2 & "#"
ElseIf Not IsNumeric(GroupFieldValue) Then
GroupFieldValue2 = "'" & Replace(GroupFieldValue2, "'", "''") & "'"
End If
rs1.Filter = GroupFieldName & "=" & GroupFieldValue
rs1.Sort = MedianFieldName
Set rs = rs1.OpenRecordset()
rs.Move (rs.RecordCount / 2)
If rs.RecordCount Mod 2 = 0 Then
varMedian1 = rs.Fields(MedianFieldName)
rs.MoveNext
fMedian = varMedian1 + rs.Fields(MedianFieldName) / 2
Else
fMedian = rs.Fields(MedianFieldName)
End If
End Function
As it stands, this works great for grouping by one column, but I cannot figure out how to allow it to group by on both typeA and typeB. I have by editing the rs1.filter line but to no avail.
Any help with the code, or a better approach would be appreciated.
Thank you!
NOTE: solved using parfaits solution below. added line medianVBA = fmedian before the end of the function.
Consider an extension of #Fionnuala's great answer to calculate median in MS Access by accommodating an open-ended number of grouping variables.
VBA (save below in a standard module of Access project)
Code builds a dynamic SQL string for DAO recordset call for later median calculation. Special handling required for groupings with 0-2 records and null values for groupings.
Public Function MedianVBA(ParamArray Arr() As Variant) As Double
On Error GoTo ErrHandle
Dim N As Long
Dim tblName As String, numCol As String, grpVals As String
Dim strSQL As String
Dim db As DAO.Database, rs As DAO.Recordset
Dim varMedian As Double, fMedian As Double
'BUILD DYNAMIC SQL
tblName = Arr(0)
numCol = Arr(1)
grpVals = " WHERE " & numCol & " IS NOT NULL "
For N = 2 To UBound(Arr) Step 2
If Arr(N + 1) = "" Or IsNull(Arr(N + 1)) Then
grpVals = grpVals & " AND " & Arr(N) & " IS NULL"
ElseIf IsDate(Arr(N + 1)) Then
grpVals = grpVals & " AND " & Arr(N) & " = #" & Arr(N + 1) & "#"
Else
grpVals = grpVals & " AND CStr(" & Arr(N) & ") = '" & Arr(N + 1) & "'"
End If
Next N
strSQL = "SELECT " & numCol _
& " FROM " & tblName _
& grpVals _
& " ORDER BY " & numCol
'CALCULATE MEDIAN
Set db = CurrentDb
Set rs = db.OpenRecordset(strSQL, dbOpenDynaset)
If rs.RecordCount = 0 Then
MedianAcc = fMedian
GoTo ExitHandle
ElseIf rs.RecordCount = 1 Then
MedianAcc = rs.Fields(numCol)
GoTo ExitHandle
End If
rs.Move (rs.RecordCount / 2)
rs.MovePrevious
If rs.RecordCount Mod 2 = 0 Then
varMedian = rs.Fields(numCol)
If rs.RecordCount = 2 Then
rs.MoveLast
Else
rs.MoveNext
End If
fMedian = (varMedian + rs.Fields(numCol)) / 2
Else
fMedian = rs.Fields(numCol)
End If
rs.Close
MedianAcc = fMedian
ExitHandle:
Set rs = Nothing: Set db = Nothing
Exit Function
ErrHandle:
MsgBox Err.Number & ": " & Err.Description, vbCritical, "RUNTIME ERROR"
Resume ExitHandle
End Function
Do note, above VBA function uses a ParamArray where first argument expects the source table and second column expects the numeric column and the remaining is open-ended for group column name and value pairs. Signature of call is as follows:
=MedianAcc("table_name",
"numeric_col",
"group1_column", "group1_value",
"group2_column", "group2_value",
...)
SQL (stored query that calls above VBA function)
Below runs a one-group and two-group median calculation.
SELECT t.typeA, t.typeB
, MedianVBA('[myTable]', '[total]', '[typeA]', t.typeA) AS MedianGrp1,
, MedianVBA('[myTable]', '[total]', '[typeA]', t.typeA, '[typeB]', t.typeB) AS MedianGrp2
FROM myTable t
GROUP BY t.typeA, t.typeB
Excuse me taking a totally different approach here...
Say you have a Table called Table1 with fields Field1.
To find the median of Field1 the SQL query would look like this:
SELECT TOP 1
((SELECT MAX(B.Field1) AS Field1 FROM
(SELECT TOP 50 PERCENT A.Field1 FROM Table1 A)
B) +
(SELECT MIN(D.Field1) AS Field1 FROM
(SELECT TOP 50 PERCENT C.Field1 FROM Table1 C ORDER BY C.Field1 DESC)
D))
/2 AS MEDIAN FROM Table1
(the above split out to make it more readable, I wrote it as only 2 lines)
From there all you have to do is write the vba to make it dynamic - replace 'Table1' with your variable SQLorTable and Field1 with the field to find the median of.
I am trying to use this code to calculate median from my query which has these criteria:
<[Form]![testForm2]![crit1] And >[Form]![testForm2]![crit2] and <[Form]![testForm2]![Age1] And >[Form]![testForm2]![Age2]
without these criteria function works well and gives for every task median based on "MP", however when I put in there my criteria I receive error:
error - Too few parameters. Expected 4 and then it says 'Object Variable or With block not set'
my input: DMedian("MP";"testForm2";"[TASK]= '" & [TASK] & "'")
*even when the Form is open it end up with the error.
*I probably need to find a different way to filter this query from the form, but I don't know how
Public Function DMedian(FieldName As String, _
TableName As String, _
Optional Criteria As Variant) As Variant
' Created by Roger J. Carlson
' http://www.rogersaccesslibrary.com
' Terms of use: You may use this function in any application, but
' it must include this notice.
'Returns the median of a given field in a given table.
'Returns -1 if no recordset is created
' You use this function much like the built-in Domain functions
' (DLookUp, DMax, and so on). That is, you must provide the
' 1) field name, 2) table name, and 3) a 'Where' Criteria.
' When used in an aggregate query, you MUST add each field
' in the GROUP BY clause into the into the Where Criteria
' of this function.
' See Help for more on Domain Aggregate functions.
On Error GoTo Err_Median
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim strSQL As String
Dim RowCount As Long
Dim LowMedian As Double, HighMedian As Double
'Open a recordset on the table.
Set db = CurrentDb
strSQL = "SELECT " & FieldName & " FROM " & TableName
If Not IsMissing(Criteria) Then
strSQL = strSQL & " WHERE " & Criteria & " ORDER BY " & FieldName
Else
strSQL = strSQL & " ORDER BY " & FieldName
End If
Set rs = db.OpenRecordset(strSQL, dbOpenDynaset)
'Find the number of rows in the table.
rs.MoveLast
RowCount = rs.RecordCount
rs.MoveFirst
'Determine Even or Odd
If RowCount Mod 2 = 0 Then
'There is an even number of records. Determine the low and high
'values in the middle and average them.
rs.Move Int(RowCount / 2) - 1
LowMedian = rs(FieldName)
rs.Move 1
HighMedian = rs(FieldName)
'Return Median
DMedian = (LowMedian + HighMedian) / 2
Else
'There is an odd number of records. Return the value exactly in
'the middle.
rs.Move Int(RowCount / 2)
'Return Median
DMedian = rs(FieldName)
End If
Exit_Median:
'close recordset
rs.Close
Exit Function
Err_Median:
If Err.number = 3075 Then
DMedian = 0
Resume Exit_Median
ElseIf Err.number = 3021 Then
'EOF or BOF ie no recordset created
DMedian = -1
Resume Exit_Median
Else
MsgBox Err.Description
Resume Exit_Median
End If
End Function
The parameter separation character is comma and you are using a semi-colon
CHANGE:
DMedian("MP";"testForm2";"[TASK]= '" & [TASK] & "'")
TO:
DMedian("MP", "testForm2", "[TASK]= '" & [TASK] & "'")
Solution was to refer the text boxes in SQL declaration, Thank you guys
like this:
HAVING (((Data.[REV]< " & Me.crit1 & ") And (Data.[REV])>" & Me.crit2 & ") AND ((Reg.Age)<" & Me.Age1 & " And (Reg.Age)>" & Me.Age2 & " " & SQLcritComplete & "));"
NOT like this:
"HAVING (((Data.[REV]<[Form]![testForm2]![crit1]) And (Data.[REV])>[testForm2]![crit2]) AND ((Reg.Age)<[Form]![testForm2]![Age1] And (Reg.Age)>[Form]![testForm2]![Age2] & SQLcritComplete & "));"
all--
I'm attempting to use an SQL query to pull records from an Access db into an Excel VBA userform listbox using the following code:
Sub FillLBBillIDs()
'build bill ID list box with bill IDs available in database, based on client and/or date range
'<---------------------------------------------------Dimension all variables
Dim con As Object, cmd As Object, rst As Object
Dim Path As String, CName As String
Dim FromDate As Date, ToDate As Date
Dim X As Long, Y As Long
'<---------------------------------------------------Define Default Variables
X = 0
CName = AuditParametersFRM.CBOCxName.Value
FromDate = AuditParametersFRM.DTPFrom.Value
ToDate = AuditParametersFRM.DTPTo.Value
'<---------------------------------------------------Define Access connection
Set con = CreateObject("ADODB.Connection"): Set cmd = CreateObject("ADODB.Command"): Set rst = CreateObject("ADODB.RecordSet"):
Path = Sheets("AuditTool").Range("B2").Value
'<---------------------------------------------------Open Access connection
con.ConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & Path & ";Persist Security Info=False;"
con.ConnectionTimeout = 0: con.CommandTimeout = 0: con.Open: cmd.CommandTimeout = 0: Set cmd.ActiveConnection = con
'<---------------------------------------------------Find all bill IDs in the database which match the selected client and
'<---------------------------------------------------are within the consolidated date range
rst.Open "SELECT DISTINCT AdHocReport.[BillID] FROM AdHocReport WHERE AdHocReport.[CxName] = '" & CName & "' AND AdHocReport.[ConsolidationDate] BETWEEN #" & FromDate & "# AND #" & ToDate & "#", con, 1, 3
On Error Resume Next
rst.MoveLast
rst.MoveFirst
Y = 0
Y = rst.RecordCount
AuditToolFRM.LBBillIDs.Clear
If Not Y = 0 Then
Do Until rst.EOF
'<---------------------------------------------------Build the listbox with the acquired information
With AuditToolFRM.LBBillIDs
.AddItem
.List(X, 0) = rst![BillID]
X = X + 1
End With
rst.MoveNext
Loop
End If
rst.Close
On Error GoTo 0
con.Close
End Sub
This code works just fine if I use a greater than argument, thusly:
rst.Open "SELECT DISTINCT AdHocReport.[BillID] FROM AdHocReport WHERE AdHocReport.[CxName] = '" & CName & "' AND AdHocReport.ConsolidationDate > #" & FromDate & "#", con 1, 3
I've gone through all the pertinent questions on this site and can't find anything that works. Any ideas?
Thanks in advance!
12/08/2017 12:54
I've done more testing and it appears that the greater than query isn't working either; it's pulling all records that meet the first criteria whilst ignoring the second, even when using parentheses to enclose the second. This tells me that the issue is definitely in the date portion of the query somehow. Any help is appreciated greatly!
In Access
DATE_FIELD BETWEEN #2/2/2012# AND #2/4/2012#
is the same as
DATE_FIELD >=#2/2/2012# AND <=#2/4/2012#
When you have another AND put parathesis around the date range syntax.
rst.Open "SELECT DISTINCT AdHocReport.[BillID] FROM AdHocReport WHERE AdHocReport.[CxName] = '" & CName & "' AND (AdHocReport.[ConsolidationDate] BETWEEN #" & FromDate & "# AND #" & ToDate & "#)", con, 1, 3
In ADO you must use the ISO sequence in string expressions for date values:
... "' AND AdHocReport.[ConsolidationDate] BETWEEN #" & Format(FromDate, "yyyy\/mm\/dd") & "# AND #" & Format(ToDate, "yyyy\/mm\/dd") & "#" ...
Every day i need to make a report for salesman, we have 2 dbf files from witch i automatic want to make the report in Excel. Report from 1 dbf file works perfect, but i don't know how to join 2 dbf files in VBA.
I have to following script:
Option Explicit
Sub ReadDBF()
Dim con As Object
Dim rs As Object
Dim DBFFolder As String
Dim FileName As String
Dim FileName1 As String
Dim sql As String
Dim myValues() As String
Dim i As Integer
Dim j As Integer
Application.ScreenUpdating = False
DBFFolder = ThisWorkbook.Path & "\"
FileName = "project1.dbf"
FileName1 = "project2.dbf"
On Error Resume Next
Set con = CreateObject("ADODB.connection")
If Err.Number <> 0 Then
MsgBox "Connection was not created!", vbCritical, "Connection error"
Exit Sub
End If
On Error GoTo 0
con.Open "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & DBFFolder & ";Extended Properties=dBASE IV;"
sql = "SELECT project_id, COUNT(*) AS total, salesman, MAX(date) AS max_date, projectname FROM " & FileName & FileName1 & " where DateValue(datumtijd) = Date() and FileName.project_id = FileName1.project_id " & "group by project_id, salesman"
On Error Resume Next
Set rs = CreateObject("ADODB.recordset")
If Err.Number <> 0 Then
MsgBox "Connection was not created!", vbCritical, "Connection error"
Exit Sub
End If
On Error GoTo 0
rs.CursorLocation = 3
rs.CursorType = 1
rs.Open sql, con
ReDim myValues(rs.RecordCount, 20)
i = 1
If Not (rs.EOF And rs.BOF) Then
'Go to the first record.
rs.MoveFirst
Do Until rs.EOF = True
myValues(i, 1) = rs!project_id
myValues(i, 2) = rs!salesman
myValues(i, 3) = rs!Total
myValues(i, 4) = rs!max_date
myValues(i, 5) = rs!project
rs.MoveNext
i = i + 1
Loop
Else
rs.Close
con.Close
Set rs = Nothing
Set con = Nothing
Application.ScreenUpdating = True
MsgBox "There are no records in the recordset!", vbCritical, "No Records"
Exit Sub
End If
Sheet1.Activate
For i = 1 To UBound(myValues)
For j = 1 To 4
Cells(i + 1, j) = myValues(i, j)
Next j
Next i
rs.Close
con.Close
Set rs = Nothing
Set con = Nothing
Columns("A:D").EntireColumn.AutoFit
Application.ScreenUpdating = True
MsgBox "The values were read from recordset successfully!", vbInformation, "Done"
End Sub
The query doesn't work just by adding the two tables together as the from source. What is it you are trying to actually get? Also, you should never concatenate strings to build a query. They should always be parameterized.
Now, that said, your syntax is simply incorrect. Your variables are referred to as "FileName" and "FileName1", but the underlying TABLES you are querying from are "Project1" and "Project2" respectively. You should learn about aliases to help simplify queries too, and learn proper JOIN syntax.
By listing the tables one after the other with no comma will cause it to fail. Here is a more accurate syntax and formatted for readability. Then I have edited your original query to match the context. You should also always qualify the field names from the respective table so others trying to help know where things are coming from. In the sample below, I have only GUESSED at which table has which columns by using the alias "P1." and "P2." respectively. You will probably need to change them. Also, since "Date" could be interpreted as a reserved word, I have wrapped it in [], but may need to be changed to tick characters (next to number 1) ``
select
P1.Project_ID,
COUNT(*) as Total,
P1.SalesMan,
MAX( P2.[Date] ) as Max_Date,
P1.ProjectName
from
Project1 P1
JOIN Project2 P2
on P1.Project_ID = P2.Project_ID
where
DateValue( P2.datumtijd ) = date()
group by
P1.Project_ID,
P1.SalesMan
The JOIN clause identifies the relation BETWEEN the two tables on respective columns. The WHERE clause is additional criteria you are looking for.
sql = "SELECT project_id, COUNT(*) AS total, salesman, " & _
"MAX(date) AS max_date, projectname " & _
" FROM " & FileName & " P1 " & _
" JOIN " & FileName1 & " P2 ON P1.Project_ID = P2.Project_ID" & _
" where DateValue(datumtijd) = Date() " & _
" group by project_id, salesman"
I'm querying an Excel 2010 workbook using the following ADO:
Function WorksheetRecordsetSQL(workbookPath As String, sheetName As String, selectSQL As String) As ADODB.Recordset
Dim objconnection As New ADODB.Connection
Dim objrecordset As New ADODB.Recordset
Const adOpenStatic = 3
Const adLockOptimistic = 3
Const adCmdText = &H1
objconnection.CommandTimeout = 99999999
objconnection.Open "Provider=Microsoft.ACE.OLEDB.12.0;" & _
"Data Source=" & workbookPath & ";" & _
"Extended Properties=""Excel 12.0 Xml;HDR=YES;IMEX=1"";"
objrecordset.Open selectSQL, _
objconnection, adOpenStatic, adLockOptimistic, adCmdText
Set WorksheetRecordsetSQL = objrecordset
End Function
When I call it with this SQL:
Select * FROM [someWorksheet$]
the function executes successfully. But if I call it with:
Select *,cdate(someField) FROM [someWorksheet$]
then I get this error:
Too many fields defined.
I found that the select * query was producing a recordset with 255 fields (the maximum that the Access engine can have in a query), even though there are only 58 columns in the sheet. So that one extra cdate() column overloaded the engine and produced the error.
Is there a way that I can keep a select * query from picking up blank columns from an Excel sheet? Or some parameters that I can set that will allow more than 255 fields?
You can specify the range which should be read and so reduce the number of columns entering into the query. Here from cell A1 to column BF (58) all rows:
FROM [Source_sheet$A1:BF]
Sub main()
Dim reultingRecordset As ADODB.Recordset
Set reultingRecordset = WorksheetRecordsetSQL( _
"C:\Temp\VBA\ReadWithADOSource.xlsx", _
"Source_sheet", _
"Select * FROM [Source_sheet$]")
Debug.Print "Select * FROM [Source_sheet$] >"
Debug.Print "Fields: " & reultingRecordset.Fields.Count & " Records: " & reultingRecordset.RecordCount
Set reultingRecordset = WorksheetRecordsetSQL( _
"C:\Temp\VBA\ReadWithADOSource.xlsx", _
"Source_sheet", _
"Select *,cdate(Col2) FROM [Source_sheet$A1:BF]")
Debug.Print "Select *,cdate(Col2) FROM [Source_sheet$A1:BC] > "
Debug.Print "Fields: " & reultingRecordset.Fields.Count & " Records: " & reultingRecordset.RecordCount
End Sub
Output:
Select * FROM [Source_sheet$] >
Fields: 255 Records: 8
Select *,cdate(Col2) FROM [Source_sheet$A1:BC] >
Fields: 59 Records: 8