This code is written in Excel2010 VBA and queries PostGreSQL tables
I have the following code in VBA that creates a variable that I would like to use in my SQL query, but I cannot get the SQL query to accept the VBA variable using the IN clause
This code creates the variable I want to use and works fine. It allows me to select specific cells I need to query
Dim StaffID As Range
Dim ID As Range
Dim LR As Long
Dim SelectedID As String
'Count number of rows to search
LR = Worksheets("Sheet1").Range("A" & Rows.Count).End(xlUp).Row
On Error Resume Next 'if only 1 row
'Store Data from here
Set StaffID = ThisWorkbook.Sheets("Sheet1").Range("B2:B" & LR)
'Loop through each cell in Range and look for any character in column A
'then store offset cell value using comma delimiter
For Each ID In Worksheets("Sheet1").Range("A2:A" & LR).Cells
If ID.Value > 0 Then
SelectedID = SelectedID & "," & ID.Offset(, 1).Value
End If
Next ID
'Remove first delimiter from string (,)
SelectedID = Right(SelectedID, Len(SelectedID) - 1)
OUTPUT EXAMPLE of SelectedID = 6,7,8,6452
I now want to add this to my query using the IN clause, but it just doesn't work. Does anyone have a solution or workaround.
Sheets("Sheet2").Select
Range("A1").Select
Dim rs As Recordset
Dim t As String
t = "SELECT DISTINCT s.entity_id, u.login_name, s.role " _
& "FROM staff s INNER JOIN user u ON s.entity_id=u.staff_id " _
& "WHERE u.staff_id IN (SelectedID) " _
Set rs = conn.Execute(t)
With ActiveSheet.QueryTables.Add(Connection:=rs, Destination:=Range("A1"))
.Refresh
End With
rs.Close
At the moment you're putting the string 'SelectedID' directly into your query. You'll need something like this in your VBA:
t = "SELECT DISTINCT s.entity_id, u.login_name, s.role " _
& "FROM staff s INNER JOIN user u ON s.entity_id=u.staff_id " _
& "WHERE u.staff_id IN (" & SelectedID & ")"
Rows.Count should be fully qualified and With ThisWorkbook.Worksheets("Sheet1") and fully qualifying will make your code read better.
Function getSelectedIDSQL() As String
Dim ID As Range, StaffID As Range
Dim SelectedID As String
With ThisWorkbook.Worksheets("Sheet1")
For Each ID In .Range("A2" & .Range("A" & .Rows.Count).End(xlUp))
If ID.Value > 0 Then SelectedID = SelectedID & "," & ID.Offset(, 1).Value
Next
End With
If Len(SelectedID) Then getSelectedIDSQL = Right(SelectedID, Len(SelectedID) - 1)
End Function
Related
Good morning,
I am helping to develop an interface via a Form in MS Access. We have a list box with various user values and the user should be able to select multiple values in the ListBox and then press the button to execute a query, returning only the rows whose Car Name is what was selected.
UPDATE - thanks to some great feedback on this forum, the primary issue was resolved. My secondary issue is now not being able to execute the query. When I try, I get the error that the query cannot be executed.
My code (as event procedure) for the button is:
Option Explicit
Private Sub btnSearchCars_Click()
MsgBox "Starting Sub"
Call QueryCars.myQuery
MsgBox "Ending Sub"
End Sub
Then, my QueryCars module looks like this:
Sub myQuery()
Dim strWhere As String
Dim strSQL As String
Dim varItem As Variant
For Each varItem in Forms!FormSelect!listCarID.SelectedItems
strWhere = strWhere & "'" & Forms!FormSelect!listCarID.ItemData(varItem) & "',"
Next
strWhere = Left(strWhere, Len(strWhere) -1)
strSQL = "SELECT tblBig.* FROM tblCars INNER JOIN tblBig ON tblCars.Car_ID = tblBig.Car_ID WHERE tblCars.Car_ID IN (" & strWhere & ");"
DoCmd.RunSQL strSQL
End Sub
My error is an "A RunSQL requires an argument of an SQL statement" error on the line.
DoCmd.RunSQL strSQL
I would really appreciate it if someone could help. All I am trying to do is take the values from the list box the user selects and use them as WHERE criteria in my query. I have searched various SO and Access forums all morning and have not found anything to help.
Thank you. Please let me know if you have any questions.
This isn't the perfect answer I was hoping to give you - but can't figure out how to use parameter queries in an IN command.
I'll assume that your listbox contains two columns of data and the CarID values are in the first column.
The main function is called ProcessQuery and accepts a reference to the listbox as an argument:
Public Sub ProcessQuery(myList As ListBox)
You can then call your code from the event on the listbox and pass it the listbox reference.
Private Sub btnSearchCars_Click()
ProcessQuery Me.listCarID
End Sub
The ProcessQuery procedure then looks at the first column to get the index numbers, constructs the SQL, opens the resulting recordset and pulls the info from each record.
Public Sub ProcessQuery(myList As ListBox)
Dim vItem As Variant
Dim IDList As String
Dim qdf As dao.QueryDef
Dim rst As dao.Recordset
For Each vItem In myList.ItemsSelected
'Column 0 is first column in listbox.
IDList = IDList & "'" & myList.Column(0, vItem) & "',"
Next vItem
'Removes the final ,
IDList = Left(IDList, Len(IDList) - 1)
'Create a temporary query definition & open the recordset.
Set qdf = CurrentDb.CreateQueryDef("", _
"SELECT tblBig.* FROM tblCars INNER JOIN tblBig ON tblCars.Car_ID = tblBig.Car_ID WHERE tblCars.Car_ID IN (" & IDList & ")")
Set rst = qdf.OpenRecordset
'Move through the recordset and output the first two fields from each record
'to the Immediate window.
With rst
If Not (.BOF And .EOF) Then
.MoveFirst
Do While Not .EOF
Debug.Print .Fields(0) & " - " & .Fields(1)
.MoveNext
Loop
End If
End With
End Sub
To display the query result as a datasheet you could use the following, but I'd prefer to use a stored query with a parameter for the IN. I'll try and figure that bit out.
Public Sub ProcessQuery(myList As ListBox)
Dim vItem As Variant
Dim IDList As String
Dim qdf As dao.QueryDef
Dim rst As dao.Recordset
For Each vItem In myList.ItemsSelected
'Column 0 is first column in listbox.
IDList = IDList & "'" & myList.Column(0, vItem) & "',"
Next vItem
'Removes the final ,
IDList = Left(IDList, Len(IDList) - 1)
'Create a temporary query definition & open the recordset.
Set qdf = CurrentDb.CreateQueryDef("TempQDF", _
"SELECT tblBig.* FROM tblCars INNER JOIN tblBig ON tblCars.Car_ID = tblBig.Car_ID WHERE tblCars.Car_ID IN (" & IDList & ")")
DoCmd.OpenQuery "TempQDF", acViewNormal
End Sub
I would suggest first taking a look at the actual WHERE clause being generated...keep a separate string variable to store it, and then dump it to the Immediate Window when it's generated.
I would also suggest creating a separate function to return values selected in a list box as an array. Something like:
Public Function getListBoxSelection(ctl As Access.ListBox) As Variant
Dim arr() As Variant
Dim varItem As Variant, i As Long
If ctl.ItemsSelected.Count > 0 Then
ReDim arr(0 To ctl.ItemsSelected.Count - 1)
i = 0
For Each varItem In ctl.ItemsSelected
arr(i) = ctl.ItemData(varItem)
i = i + 1
Next varItem
End If
getListBoxSelection = arr
End Function
Then, you would call it in SQL generation. Something like
whereClause = join(getListBoxSelection(me.listCarID), " AND ")
debug.Print whereClause
qdf.SQL = _
"select tblBig.* " & _
"from tblCars " & _
"inner join tblBig on tblCars.Cat_ID = tblBig.Car_ID " & _
"where tblCars.Card_ID in (" & whereClause & ")"
Let's say I have a single table called "Customers". It contains 2 fields:
Name
Address
I want users to be able to select multiple records by selecting their names. For example something like having a list box containing all the names of the records in the database. I want users to be able to select multiple items like:
Dave Richardson
Bob Smith
Sophie Parker
And then only display records with these names in the report.
You can use the WhereCondition option of the DoCmd.OpenReport Method to filter your report as needed.
Const cstrReport As String = "YourReportNameHere"
Dim custNames As String
Dim lItem As Variant
Dim strWhereCondition As String
With Me.yourListBoxName
For Each lItem In .ItemsSelected
custNames = custNames & ",'" & Replace(.ItemData(lItem), "'", "''") & "'"
Next
End With
If Len(custNames) > 0 Then
custNames = Mid(custNames, 2)
strWhereCondition = "[Name] IN (" & custNames & ")"
End If
DoCmd.OpenReport ReportName:=cstrReport, View:=acViewPreview, _
WhereCondition:=strWhereCondition
Note this approach has features in common with PaulFrancis' answer. In fact, I copied his code and modified it. The key difference is that this approach does not require you to revise a saved query in order to filter the report.
The setup I would I have is, a Form with a SubForm and a ListBox, the Listbox will have the names of all your customers. So the RowSource would be,
SELECT
customerNameFieldName
FROM
yourTableName;
The Multi Select property will be setup to Extended. Then a button will have the following code that will generate the SQL for the SubForm's recordsource.
Private Sub buttonName_Click()
Dim lItem As Varaint, strSQL As String
Dim custNames As String, whereStr As String
Dim dbObj As dao.Database
Dim tmpQryDef As QueryDef
Set dbObj = CurrentDb()
For Each lItem In Me.yourListBoxName.ItemsSelected
custNames = custNames & "'" & Me.yourListBoxName.ItemData(lItem) & "', "
Next
Id Len(custNames) <> 0 Then
custNames = Left(custNames, Len(custNames) - 2)
whereStr = "WHERE customerNameFieldName IN (" & custNames & ")"
End If
strSQL = "SELECT someFields FROM someTable " & whereStr
Set tmpQryDef = dbObj.QueryDefs("Summary 3 Q1")
tmpQryDef.SQL = strSQL
DoCmd.OpenReport "yourReportName", acViewNormal
Set dbObj = Nothing
Set tmpQryDef = Nothing
End Sub
So now the SubForm will have the RecordSource based on all the information you have selected in the ListBox.
VBA noob here (as of this mourning),
In MS Access I wrote a test function to find the value of a record base on some criteria you pass in.
The function seems to work fine except in cases where there is a lookup in the column that I am searching.
Basically it might return "19" and 19 corresponds to some other table value.
It seems that the RowSource of the column is what Im after so I can do a second query to find the true value.
Can someone point me in the right direction on finding the RowSource assuming I know the column name and then utilizing it to find the value Im after?
Edit: It seems that Im not explaining myself clearly, Here is a picture of what I trying to get programatically
Try this -- I think I finally understand why you are looking for the RowSource -- sorry I didn't "get" it at first. The field you're trying to pull is a foreign key into a description table.
This function should work as a general solution for all such fields (assuming the RowSource always has the primary key first, and the description second). If there is no RowSource, it will just pull the value of the field.
It's based on your original code, rather than the changes proposed by #ron, but it should set you in the right direction. You should fix it to make it parameterized, and allow for variant data types, as ron suggests (+1 ron)
As an aside, use the ampersand (&) to join strings together in VBA to avoid things like this: abc = "1" + 1, where abc is now equal to 2 instead of "11" as you would expect if both items were intended to be strings.
Public Function lookUpColumnValue(Database As Database, column As String, table As String, lookUpColumn As String, lookUpValue As String) As String
Dim sql As String
Dim recordSet As DAO.recordSet
Dim result As String
lookUpColumnValue = "" 'Return a blank string if no result
On Error Resume Next
sql = "SELECT [" & table & "].[" & column & "] FROM [" & table & "] WHERE [" & table & "].[" & lookUpColumn & "] = '" & lookUpValue & "'"
Set recordSet = Database.OpenRecordset(sql)
If Not recordSet.EOF Then
Dim td As DAO.TableDef
'this gives your number - say, 19
result = recordSet(column)
Set td = Database.TableDefs(table)
'Get the rowsource
Dim p As DAO.Property
For Each p In td.Fields(column).Properties
If p.Name = "RowSource" Then
RowSource = Replace(td.Fields(column).Properties("RowSource"), ";", "")
Exit For
End If
Next
If Not RowSource = "" Then
Dim rs2 As DAO.recordSet
Dim qd As DAO.QueryDef
Set qd = Database.CreateQueryDef("", RowSource)
Set rs2 = Database.OpenRecordset(RowSource)
If rs2.EOF Then Exit Function
PKField = rs2.Fields(0).Name
rs2.Close
qd.Close
sql = "SELECT * FROM (" & RowSource & ") WHERE [" & PKField & "]=[KeyField?]"
Set qd = Database.CreateQueryDef("", sql)
qd.Parameters("KeyField?").Value = result
Set rs2 = qd.OpenRecordset()
If Not rs2.EOF Then
'NOTE: This assumes your RowSource *always* has ID first, description 2nd. This should usually be the case.
lookUpColumnValue = rs2.Fields(1)
End If
Else
'Return the field value if there is no RowSource
lookUpColumnValue = recordSet.Fields(column)
End If
End If
End Function
If I understand your question correctly, I think using a parameter query will solve your problem. Using parameters is good practice since they will perform implicit data type casts and also prevent injection attacks.
Notice in the following function, I changed the lookupValue to a Variant type, which allows you to pass any type of value to the function.
Public Function lookUpColumnValue( _
database As DAO.database, _
column As String, _
table As String, _
lookUpColumn As String, _
lookUpValue As Variant) As String
Dim sql As String
Dim recordSet As DAO.recordSet
Dim result As String
Dim qd As QueryDef
Set qd = database.CreateQueryDef("")
sql = "SELECT [" + table + "].[" + column + "] FROM [" + table + "] " & _
"WHERE [" + table + "].[" + lookUpColumn + "] = [parm1];"
qd.sql = sql
qd.Parameters![parm1] = lookUpValue
Set recordSet = qd.OpenRecordset()
result = recordSet(column)
EDIT
lookUpColumnValue = DLookup("Space Use Description", "Space Use Codes", result)
End Function
I have two tables and I have a form linking to one of them. I want to check a value and if it is true, add the record the other table by using VBA.
Can anyone help me, please?
This is my code, but it does not work:
Dim rec1 As DAO.Recordset
Dim rec2 As DAO.Recordset
Set rec1 = CurrentDb.OpenRecordset("TotalTPAq")
Set rec2 = CurrentDb.OpenRecordset("Visi")
rec1.MoveFirst
Do Until rec1.EOF
If rec1!Date = PlanDate.Value Then ' planDate is a text box
rec2.AddNew
rec2![Planing Date History] = PlanDate.Value
rec2.Update
rec2.Close
End If
rec1.MoveNext
Loop
rec1.Close
Set rec2 = Nothing
Set rec1 = Nothing
DoCmd.Close
This should provide a start for you:
'Run query to fill table
Private Sub btnRnQry_Click()
'No value entered
If IsNull(Me.txtEntry) Or Me.txtEntry = "" Then
MsgBox ("Is null or empty")
Else
'Assign value to variable
Dim entry As String
entry = Me.txtEntry
Dim sql As String
sql = "INSERT INTO tableTwo ([First Name],Surname,[Phone Number] )" & _
"SELECT * " & _
"FROM tableOne " & _
"WHERE [First Name] = '" & entry & "';"
'Run the SQL
DoCmd.RunSQL sql
End If
End Sub
Ok so a guy at work has a little access database he uses to keep track of things. He has this form that he uses that already queries what he needs and produces the results on a form and that is really all he needs.
One thing is that he has duplicates for every record that comes up with a different "Type" as a field "indentifier" (what I call it)...here is an example:
ID Name Price Type
1 Prodcut A $10 A1
1 Product A $10 A2
1 Product A $10 A3
2 Product B $12 A1
etc
naturally this is supposed to occur and he wants to see all the types but given it ends up being a mile long, he asked me if there was a way to concatenate the "types" so the following would be displayed:
ID Name Price Type
1 Prodcut A $10 A1, A2, A3
1 Product B $12 A1, A2, A3
1 Product C $14 A1, A2, A3
2 Product D $7 A1, A2, A3
...on the form. Can anyone please help me with this? Thanks!
OK, i found a function created in the VBA, which can be used in the query to retrieve the data for the form.
function is
Public Function ConcatRelated(strField As String, _
strTable As String, _
Optional strWhere As String, _
Optional strOrderBy As String, _
Optional strSeparator = ", ") As Variant
On Error GoTo Err_Handler
'Purpose: Generate a concatenated string of related records.
'Return: String variant, or Null if no matches.
'Arguments: strField = name of field to get results from and concatenate.
' strTable = name of a table or query.
' strWhere = WHERE clause to choose the right values.
' strOrderBy = ORDER BY clause, for sorting the values.
' strSeparator = characters to use between the concatenated values.
'Notes: 1. Use square brackets around field/table names with spaces or odd characters.
' 2. strField can be a Multi-valued field (A2007 and later), but strOrderBy cannot.
' 3. Nulls are omitted, zero-length strings (ZLSs) are returned as ZLSs.
' 4. Returning more than 255 characters to a recordset triggers this Access bug:
' http://allenbrowne.com/bug-16.html
Dim rs As DAO.Recordset 'Related records
Dim rsMV As DAO.Recordset 'Multi-valued field recordset
Dim strSql As String 'SQL statement
Dim strOut As String 'Output string to concatenate to.
Dim lngLen As Long 'Length of string.
Dim bIsMultiValue As Boolean 'Flag if strField is a multi-valued field.
'Initialize to Null
ConcatRelated = Null
'Build SQL string, and get the records.
strSql = "SELECT " & strField & " FROM " & strTable
If strWhere <> vbNullString Then
strSql = strSql & " WHERE " & strWhere
End If
If strOrderBy <> vbNullString Then
strSql = strSql & " ORDER BY " & strOrderBy
End If
Set rs = DBEngine(0)(0).OpenRecordset(strSql, dbOpenDynaset)
'Determine if the requested field is multi-valued (Type is above 100.)
bIsMultiValue = (rs(0).Type > 100)
'Loop through the matching records
Do While Not rs.EOF
If bIsMultiValue Then
'For multi-valued field, loop through the values
Set rsMV = rs(0).Value
Do While Not rsMV.EOF
If Not IsNull(rsMV(0)) Then
strOut = strOut & rsMV(0) & strSeparator
End If
rsMV.MoveNext
Loop
Set rsMV = Nothing
ElseIf Not IsNull(rs(0)) Then
strOut = strOut & rs(0) & strSeparator
End If
rs.MoveNext
Loop
rs.Close
'Return the string without the trailing separator.
lngLen = Len(strOut) - Len(strSeparator)
If lngLen > 0 Then
ConcatRelated = Left(strOut, lngLen)
End If
Exit_Handler:
'Clean up
Set rsMV = Nothing
Set rs = Nothing
Exit Function
Err_Handler:
MsgBox "Error " & Err.Number & ": " & Err.Description, vbExclamation, "ConcatRelated()"
Resume Exit_Handler
End Function
and is used in the query as
SELECT Table1.ID, Table1.ProductName, Table1.ProductPrice, ConcatRelated("Type","Table1","ID = " & [Table1]![ID] & " AND ProductName = """ & [Table1]![ProductName] & """ AND ProductPrice = " & [Table1]![ProductPrice]) AS Expr1
FROM Table1
GROUP BY Table1.ID, Table1.ProductName, Table1.ProductPrice, ConcatRelated("Type","Table1","ID = " & [Table1]![ID] & " AND ProductName = """ & [Table1]![ProductName] & """ AND ProductPrice = " & [Table1]![ProductPrice]);
I found an example (here) that seems to be exactly what you are looking for:
Concatenate Column Values from Multiple Rows into a Single Column with Access
From the above link:
The Problem
Coming up with a meaningful title for
this article was the hardest part. The
issue is one that I have seen a couple
times in the Access newsgroups, but it
is hard to describe without a specific
example. One post to
comp.databases.ms-access some years
ago put it this way:
I would like to combine a field's values from multiple records in a single field. For example:
Last First Code
------- --------- ----
Lesand Danny 1
Lesand Danny 2
Lesand Danny 3
Benedi Eric 7
Benedi Eric 14
Result should look like:
Last First Codes
------- --------- -----
Lesand Danny 1,2,3
Benedi Eric 7,14
Something on these lines may suit, but concatenating is usually not a good idea:
Function ConcatList(strSQL As String, strDelim, _
ParamArray NameList() As Variant)
''Reference: Microsoft DAO x.x Object Library
Dim db As Database
Dim rs As DAO.Recordset
Dim strList As String
Set db = CurrentDb
If strSQL <> "" Then
Set rs = db.OpenRecordset(strSQL)
Do While Not rs.EOF
strList = strList & strDelim & rs.Fields(0)
rs.MoveNext
Loop
strList = Mid(strList, Len(strDelim) + 1)
Else
strList = Join(NameList, strDelim)
End If
ConcatList = strList
End Function
FROM: http://wiki.lessthandot.com/index.php/Concatenate_a_List_into_a_Single_Field_(Column)
Why don't you try the "crosstab query" solution?