Return Empty String as NULL - sql

I have a listbox select and I want when the user selects null for the empty string it produces to pull the nulls from the SQL table.
Here's what I have now. Blank strings return nothing because there are no empty fields in the table.
SELECT * FROM dbo.Table WHERE ID = " & TextBox2.Text & " and And Field1 IN (" & Msg1 & ")
How do I code that?

Use an If statement. When your textbox is empty, have the SQL string contain "ID is null" instead of appending the textbox's value.
If (TextBox1.Text = "") Then
' use Is Null in your sql statement
Else
' use the textbox text value in your sql statement
End If
(Assuming you're talking about the textbox and not whatever Msg1 is.)

dim sql
If (TextBox2.Text = null) Then
sql = "SELECT * FROM dbo.Table WHERE ID is null and And Field1 IN (" & Msg1 & ")"
Else
sql = "SELECT * FROM dbo.Table WHERE ID = " & TextBox2.Text & " and And Field1 IN (" & Msg1 & ")"
End If
See #John Saunders comment, you are risking sql injections. When passing paremeter to an sql query, be sure to use parameters, and not concatenating strings.

SELECT * FROM dbo.Table WHERE ID = " & TextBox2.Text &
" And Field1 " & IIF(Trim(Msg1) = "", "IS NULL", "IN (" & Msg1 & ")")
Yes, it is crude.
One should not write query in this style.
EDIT: Corrected. Please check.

Related

Looping through table to find value from a textbox

I'm trying to loop through a column inside a table from a form in Access to find out whether a "Case Name" already exists or not, and if it does not, then add the new record to the table. I want the criteria to be based on the input value of a text box. The good news is I have figured out how to add a new record to the table with the code below. I'm just stuck on how to loop through a table to find out if a record already exists. Thanks in advance!
Private Sub SaveNewCase_Click()
If Me.txtNewCaseName.Value <> "Null" And Me.txtCaseDepth.Value <> "Null" And Me.txtCaseHeight2.Value <> "Null" And Me.txtCaseWeight.Value <> "Null" And Me.txtCaseWidth <> "Null" And Me.cboCaseCategory.Value <> "Null" Then
'I think the loop should go here, but not sure'
CurrentDb.Execute "INSERT INTO tblCases(CaseName, CaseWidth, CaseHeight, CaseCasters, CaseWeight, CaseDepth, CaseCategory) " & _
" VALUES ('" & Me.txtNewCaseName & "'," & Me.txtCaseWidth & "," & Me.txtCaseHeight2 & ",'" & Me.chkboxCasters & "'," & Me.txtCaseWeight & "," & Me.txtCaseDepth & ",'" & Me.cboCaseCategory & "')"
Else
MsgBox "Please enter all new case criteria."
End If
End Sub
Firstly, use parameters!
Concatenating values supplied by a user directly into your SQL statement exposes your to SQL injection, either intentional (i.e. users entering their own SQL statements to sabotage your database) or unintentional (e.g. users entering values containing apostrophes or other SQL delimiters).
Instead, represent each of the field values with a parameter, for example:
With CurrentDb.CreateQueryDef _
( _
"", _
"insert into " & _
"tblcases (casename, casewidth, caseheight, casecasters, caseweight, casedepth, casecategory) " & _
"values (#casename, #casewidth, #caseheight, #casecasters, #caseweight, #casedepth, #casecategory) " _
)
.Parameters("#casename") = txtNewCaseName
.Parameters("#casewidth") = txtCaseWidth
.Parameters("#caseheight") = txtCaseHeight2
.Parameters("#casecasters") = chkboxCasters
.Parameters("#caseweight") = txtCaseWeight
.Parameters("#casedepth") = txtCaseDepth
.Parameters("#casecategory") = cboCaseCategory
.Execute
End With
Since the value of each form control is fed directly to the parameter within the SQL statement, the value will always be interpreted as a literal and cannot form part of the SQL statement itself.
Furthermore, you don't have to worry about surrounding your string values with single or double quotes, and you don't have to worry about formatting date values - the data is used in its native form.
Where testing for an existing value is concerned, you can either use a domain aggregate function, such as DLookup, or you could use a SQL select statement and test that no records are returned, e.g.:
Dim flg As Boolean
With CurrentDb.CreateQueryDef _
( _
"", _
"select * from tblcases where " & _
"casename = #casename and " & _
"casewidth = #casewidth and " & _
"caseheight = #caseheight and " & _
"casecasters = #casecasters and " & _
"caseweight = #caseweight and " & _
"casedepth = #casedepth and " & _
"casecategory = #casecategory " _
)
.Parameters("#casename") = txtNewCaseName
.Parameters("#casewidth") = txtCaseWidth
.Parameters("#caseheight") = txtCaseHeight2
.Parameters("#casecasters") = chkboxCasters
.Parameters("#caseweight") = txtCaseWeight
.Parameters("#casedepth") = txtCaseDepth
.Parameters("#casecategory") = cboCaseCategory
With .OpenRecordset
flg = .EOF
.Close
End With
End With
If flg Then
' Add new record
Else
' Record already exists
End If
Finally, you're currently testing the values of your form controls against the literal string "Null", which will only be validated if the user has entered the value Null into the control, not if the control is blank.
Instead, you should use the VBA IsNull function to check whether a variable holds a Null value.

How to speed up nested recordset/SQL calls?

I am looking for help on how to speed up the code bit below because as it stands, it is taking too long to perform the task. Any suggestions would be much appreciated. Thanks in advance!
The code bit below is a stripped down version of the actual version but all the important guts should be there. The code works; however, the code is really slow on even a modest size dataset. Needless to say, the primary culprit is the second, nested recordset/SQL call. The LIKE operator is part of the slowdown but I'm more concerned about the nesting and I think the LIKE operator will be required in what we're trying to accomplish. I tried nesting the second SQL call into the first but I didn't see a clean way of doing so.
Platform: Classic ASP, VBScript, MS Access DB
' Go through all people in the table.
sql1 = "SELECT ID, FN, LN, Email FROM Table1"
Call rst1.Open(sql1, cnx, 0, 1)
While Not rst1.EOF
id = rst1.Fields("ID").Value
fn = rst1.Fields("FN").Value
ln = rst1.Fields("LN").Value
email = rst1.Fields("Email").Value
If IsNull(email) Or IsEmpty(email) Then
email = ""
End If
' ----- Figure out if any other people in the table has a similar name or is using the same e-mail address.
' Capture both the ID of those other people as well as figure out the total number of possible duplicates.
sql2 = "SELECT ID FROM Table1"
sql2 = sql2 & " WHERE"
sql2 = sql2 & " ID <> " & id
sql2 = sql2 & " AND"
sql2 = sql2 & " ("
sql2 = sql2 & " FN & ' ' & LN LIKE '%" & Replace(fn & " " & ln, "'", "''") & "%'"
If email <> "" Then
sql2 = sql2 & " OR"
sql2 = sql2 & " Email LIKE '%" & Replace(email, "'", "''") & "%'"
End If
sql2 = sql2 & " )"
Call rst2.Open(sql2, cnx, 0, 1)
numDups = 0
possibleDups = ""
While Not rst2.EOF
numDups = numDups + 1
If possibleDups <> "" Then
possibleDups = possibleDups & ", "
End If
possibleDups = possibleDups & rst2.Fields("ID").Value
Call rst2.MoveNext()
Wend
Call rst2.Close()
' ----- End nest query.
Call Response.Write(fn & " " & ln & " has " & numDups & " possible duplicates (" & possibleDups & ")")
Call rst1.MoveNext()
Wend
Call rst1.Close()
Update 1:
Per request, here is a bit more info on the sample data and the expected output. Table1 is basically a table with the fields: id, fn, ln, email. id is an autogenerated ID representing the entry and fn/ln represent the first/last name, respectively, of the person's entry. Expected output is as coded, e.g.,...
John Doe has 3 possible duplicates (1342, 3652, 98325)
John Doe has 3 possible duplicates (986, 3652, 98325)
John Doe has 3 possible duplicates (986, 1342, 98325)
John Doe has 3 possible duplicates (986, 1342, 3652)
Sam Jones has 0 possible duplicates ()
Jane Smith has 2 possible duplicates (234, 10562)
Jane Smith has 2 possible duplicates (155, 10562)
Jane Smith has 2 possible duplicates (155, 234)
The numbers in parentheses correspond to the id's that appear to be duplicates to each person. A possible duplicate is a scenario in which another entry in the same table appears to share the same name or e-mail. For example, there could be 4 John Doe's and 3 Jane Smith's in the table based on name alone.
Ideally, only one SQL query is required to reduce the roundtrip induced by the recordset call but Access is limited compared to regular SQL Server as far as features and I'm not sure what I'm missing that might help speed this up.
Update 2:
Using the SQL Fiddle by #Abecee, I was able to get a faster query. However, I am now encountering two problems as a result.
The big picture view is still the same. We are looking for possible duplicates based on first name, last name, and e-mail address. However, we also added a search criteria, which are the lines wrapped inside of If searchstring <> "" Then ... End If. Also, note that the e-mail info is now being pulled from a separate table called EmailTable with the fields id, IndividualID (representing Table1.id), and email.
Mods: The updated query is similar but slightly different from the original query above. I'm not sure if it's better to create a whole new question or not, so I'll just leave this here for now. Let me know if I should move this to its own question.
If the code associated with comment A below is uncommented sql1 = sql1 & " OR (INSTR(E1.Email, E2.Email) > 0) ", I get an error message: Microsoft JET Database Engine (0x80040E14) Join expression not supported. The query seems to be coded correctly so what is missing or incorrect?
If the code associated with comment B below is uncommented sql1 = sql1 & " OR INSTR(E1.Email, '" & Replace(searchstring, "'", "''") & "') > 0", the query runs but it hangs. I tried dropping the query directly into Access to see if it'll work (e.g., New Query > SQL View) but it also hangs from within Access. I think the syntax and logic are correct but obviously something is askew. Do you see what or why it would hang with this line of code?
Here is the updated query:
sql1 = sql1 & "SELECT "
sql1 = sql1 & " T1.ID, T1.FN, T1.LN, E1.Email, "
sql1 = sql1 & " T2.ID, T2.FN, T2.LN "
sql1 = sql1 & "FROM "
sql1 = sql1 & " ((Table1 T1 LEFT JOIN [SELECT E1.* FROM EmailTable E1 WHERE E1.Primary = True]. AS E1 ON T1.ID = E1.IndividualID)"
sql1 = sql1 & " LEFT JOIN (Table1 T2 LEFT JOIN EmailTable E2 ON T2.ID = E2.IndividualID) "
sql1 = sql1 & " ON "
sql1 = sql1 & " ("
sql1 = sql1 & " T1.ID <> T2.ID "
sql1 = sql1 & " AND "
sql1 = sql1 & " ("
sql1 = sql1 & " ((INSTR(T1.FN, T2.FN) > 0) AND (INSTR(T1.LN, T2.LN) > 0)) "
' A. When the following line is uncommented, error is "Join expression not supported."
' sql1 = sql1 & " OR (INSTR(E1.Email, E2.Email) > 0) "
sql1 = sql1 & " ) "
sql1 = sql1 & " ) "
sql1 = sql1 & " ) "
If searchstring <> "" Then
sql1 = sql1 & " WHERE "
sql1 = sql1 & " INSTR(T1.FN & ' ' & T1.LN, '" & Replace(searchstring, "'", "''") & "') > 0"
' B. When the following line is uncommented, code hangs on the rst1.open() call."
' sql1 = sql1 & " OR INSTR(E1.Email, '" & Replace(searchstring, "'", "''") & "') > 0"
End If
sql1 = sql1 & " ORDER BY T1.LN, T1.FN, T1.ID"
prevID = 0
Call rst1.Open(sql1, cnx, 0, 1)
While Not rst1.EOF
id = rst1.Fields("ID").Value
' Get initial values if we've come across a new ID.
If (id <> prevID) Then
fn = rst1.Fields("T1.FN").Value
ln = rst1.Fields("T1.LN").Value
email = rst1.Fields("Email").Value
If IsNull(email) Or IsEmpty(email) Then
email = ""
End If
' Reset the counter for how many possible duplicates there are.
numDups = 0
' If there is an ID from the second table, then keep track of this possible duplicate.
tmp = rst1.Fields("T2.ID").Value
If IsNumeric(tmp) Then
tmp = CLng(tmp)
Else
tmp = 0
End If
If tmp > 0 Then
numDups = numDups + 1
possibleDups = possibleDups & tmp
End If
End If
' Figure out if we should show this row. Within this logic, we'll also see if there is another possible duplicate.
showrow = False
Call rst1.MoveNext()
If rst1.EOF Then
' Already at the end of the recordset so show this row.
showrow = True
Call rst1.MovePrevious()
Else
If rst1.Fields("T1.ID") <> lngIndividualIDCurrent Then
' Next record is different T1, so show this row.
showrow = True
Call rst1.MovePrevious()
Else
' Next record is the same T1, so don't show this row but note the duplicate.
Call rst1.MovePrevious()
' Also, add the new T2 as a possible duplicate.
tmp = rst1.Fields("T2.ID").Value
If IsNumeric(tmp) Then
tmp = CLng(tmp)
Else
tmp = 0
End If
If tmp > 0 Then
numDups = numDups + 1
If possibleDups <> "" Then
possibleDups = possibleDups & ", "
End If
possibleDups = possibleDups & tmp
End If
End If
End If
If showrow Then
Call Response.Write(fn & " " & ln & " has " & numDups & " possible duplicates (" & possibleDups & ")")
End If
Call rst1.MoveNext()
prevID = id
Wend
Call rst1.Close()
Yes, that's going to be slow because LIKE '%whatever%' is not sargable. So, if [Table1] has 1,000 rows then at best you'll be retrieving the other 999 rows for each row in the table, which means that you'll be pulling 999,000 rows in total.
A few observations:
You are performing the comparisons for every row in the table against every other row. That would be something that you might want to do one time only to find possible dups in legacy data, but as part of the normal operation of an application we would expect to compare one record against all of the others (i.e. the one record that you are inserting or updating).
You are looking for rows WHERE 'fn1 ln1' LIKE('%fn2 ln2%'). How is that significantly different from WHERE fn1=fn2 AND ln1=ln2? That would be sargable, so if you had indexes on [FN] and [LN] then that could speed things up a great deal.
You really should NOT be using an Access database as the back-end for a web application (ref: here).

SQL/VB.NET Search-Function looking for at least one correct input

I'm writing a program in Visual Basic about Databases. Now I have a Sub/Function who searches the database for correct inputs. I have five text boxes where the user can put in something for each data field.
If txtBox1.Text <> "" Or txtBox2.Text <> "" Or txtBox3.Text <> "" Or txtBox4.Text <> "" Or txtBox5.Text <> "" Then
Try
connection.Open()
command.CommandText = "SELECT * from lager WHERE (lager_waren_id LIKE '" & txtBox1.Text & "' OR lager_warenanzahl LIKE '" & txtBox2.Text & "' OR lager_warenname LIKE '%" & txtBox3.Text & "%' OR lager_warengewicht LIKE '" & txtBox4.Text & "%' OR lager_waren_verkaufspreis LIKE '" & txtBox5.Text & "%');"
reader = command.ExecuteReader()
FormKunde.Enabled = True
FormKunde.lstViewKundeStore.Items.Clear()
Do While reader.Read()
Dim lstViewItem As New ListViewItem(reader("lager_waren_id").ToString())
lstViewItem.SubItems.Add(reader("lager_warenanzahl").ToString())
lstViewItem.SubItems.Add(reader("lager_warenname").ToString())
lstViewItem.SubItems.Add(reader("lager_warengewicht").ToString())
lstViewItem.SubItems.Add(reader("lager_waren_verkaufspreis").ToString())
FormKunde.lstViewKundeStore.Items.Add(lstViewItem)
Loop
reader.Close()
FormKunde.Enabled = False
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
connection.Close()
Else
MessageBox.Show("Please fill in something in the text fields")
Exit Sub
End If
I'm aksing the database if at least one text field has some input that matches to the data field it belongs to. But when I put something in, doesn't matter how much, nothing happens in my list view. It just loads all data back in the list view. When I try to do "AND" instead of "OR", it works only if i fill all text fields with the correct datas for one data set. But I want, that it finds all data sets.
An example:
I have two data sets where the names are "App" and "Apple". When i just fill in "Ap" in the field for names (nothing in the others) it shows me both. I think it should work with "OR", but it just does nothing.
I'm really confused how to solve this, I hope anyone has a guess. Thank you!
Your problem is that your query always uses all the conditions also when there is no input in the relevant textboxes. In this way your LIKEs become LIKE '%%' and, of course, this matches every record.
You need to add the conditions only if the textboxes are not empty or null.
So you need to build your query in parts after checking if the textbox contains any value to search for.
connection.Open()
Dim sql = "SELECT * from lager WHERE "
if Not string.IsNullOrWhiteSpace(textBox1.Text) Then
sql = sql & "lager_waren_id LIKE #p1 OR "
command.Parameters.AddWithValue("#p1", textBox1.Text)
End If
if Not string.IsNullOrWhiteSpace(textBox2.Text) Then
sql = sql & "lager_warenanzahl LIKE #p2 OR "
command.Parameters.AddWithValue("#p2", textBox2.Text)
End If
if Not string.IsNullOrWhiteSpace(textBox3.Text) Then
sql = sql & "lager_warenname LIKE #p3 OR "
command.Parameters.AddWithValue("#p3", "%" & textBox3.Text & "%")
End If
if Not string.IsNullOrWhiteSpace(textBox4.Text) Then
sql = sql & "lager_warengewicht LIKE #p4 OR "
command.Parameters.AddWithValue("#p4", textBox4.Text & "%")
End If
if Not string.IsNullOrWhiteSpace(textBox5.Text) Then
sql = sql & "lager_waren_verkaufspreis LIKE #p5 OR "
command.Parameters.AddWithValue("#p5", textBox5.Text & "%")
End If
' Remove the last OR if any ....'
if sql.EndsWith(" OR ") then
sql = sql.Substring(0, sql.Length - 4)
End If
' Remove the WHERE if no textbox has been filled....'
if sql.EndsWith(" WHERE ") then
sql = sql.Substring(0, sql.Length - 7)
End If
command.CommandText = sql
reader = command.ExecuteReader()
Notice also that you should ALWAYS use a parameterized query to avoid Sql Injection particularly when you get your inputs directly from your user. (Not to mention the problems with typed texts that contain a single quote)
I hope I understand your problem correctly. I am sure there are better ways to do this and my VB is rusty but something like this may work
Dim query As String = "SELECT * FROM lager"
Function addField (ByVal query As String, ByVal value as String, ByVal field as String) As String
addField = query
If value <> "" Then
If query.IndexOf("where", 0, StringComparison.CurrentCultureIgnoreCase) > -1 Then
addField = query & " AND " & field & " LIKE '%" & value & "%'"
Else
addField = query & " WHERE " & field & " LIKE '%" & value & "%'"
End If
End If
End Function
query = addField(query, txtBox1.Text, "lager_waren_id")
query = addField(query, txtBox2.Text, "lager_warenanzahl")
'...continue adding fields...'
command.CommandText = query
This should make it so your query string only includes the populated fields

If/Then in SQL Embedded in VBA

I've got a string like this in my Excel VBA:
strSQL = "SELECT * FROM Total WHERE (SimulationID = (" & TextBox1.Text & ") And Test1 = (" & Example & "))"
However, sometimes Test will be 'is null', which makes the query
And Example = is NULL
How can I change it to add an if/then statement or something to make it say
And Example is null
when Example has a value of "is null"?
I would suggest doing the NULL comparison before assembling the SQL statement strSQL.
If you check for the value of Example beforehand, you can alter your strSQL statement accordingly based on that check.
EDIT:
In response to Daniel's first and second comment below, I still would prefer the following over doing it inline:
Dim strSqlTail
strSqlTail = ""
If (Example1 = Null) Then strSqlTail = "AND Example1 IS NULL"
If (Example2 = Null) Then strSqlTail = strSqlTail & "AND Example2 IS NULL"
If (Example3 = Null) Then strSqlTail = strSqlTail & "AND Example3 IS NULL"
...
Note: The strSqlTail can be whatever SQL would make your situation work since I don't quite understand what is being queried from the sample.
You just create a function that puts the equal sign and space before the number if it doesn't equal "is null", and modify the string assignment statement appropriately, like so:
Public Function NullModString(Example As String) As String
If Example <> "is null" Then Example = "= " & Example
NullModString = Example
End Function
strSQL = "SELECT * FROM Total WHERE SimulationID = " & TextBox1.Text & _
"And Test1 " & NullModString(Example)
Note, that I didn't understand why the extra parentheses were in there, but it would be simple to put them back in.
One solution is to coalesce out the null using a Coalesce function (or if using Access, the Nz function) :
trSQL = "SELECT ..." & _
" FROM Total" & _
" WHERE SimulationID = " & TextBox1.Text & " & _
" And Coalesce(Test1,"""") = "" & Example & """
A better way would be to dynamically include or not include the entire And statement based on whether Example had a value or to substitute Test Is Null when Example did not have a value. So something akin to:
Dim testElements() As Variant
Dim vElement As Variant
Redim testElements(6,1)
testElements(0,0) = Example1
testElements(0,1) = "Test1"
testElements(1,0) = Example2
testElements(1,1) = "Test2"
...
Dim elementIndex as Integer
Dim colName As String
Dim elementValue As Variant
For elementIndex = 0 To UBound(testElements)
elementValue = testElement(elementIndex, 0)
colName = testElement(elementIndex, 1)
If Len(Nz(elementValue)) = 0 Then
trSQL = trSQL & " And " & colName & " = """ & Example1 & """
Else
trSQL = trSQL & " And " & colName & " Is Null"
End If
Next
First, don't embed SQL in VBA: hard to maintain, SQL injection, etc. Use a stored proc on the database side (even Access has PROCEDUREs).
Second, use COALESCE (or equivalent logic) to handle your 'empty string equates to NULL' UI logic. You don't say which SQL syntax and you haven't posted schema DLL so we're merely guessing...
SQL Server:
CREATE PROCEDURE GetTotals
#SimulationID INTEGER,
#Test1 VARCHAR(20) = NULL
AS
SELECT *
FROM Total AS T1.
WHERE T1.SimulationID = #SimulationID
AND COALESCE(T1.Test1, '') = COALESCE(#Test1, '');
Access Database Engine (a.k.a. Jet):
CREATE PROCEDURE GetTotals
(
:SimulationID INTEGER,
:Test1 VARCHAR(20) = NULL
)
AS
SELECT *
FROM Total AS T1
WHERE T1.SimulationID = :SimulationID
AND IIF(T1.Test1 IS NULL, '', T1.Test1)
= IIF(:Test1 IS NULL, '', :Test1);
Then execute the proc using your middleware (ADO, DAO, etc) with Parameter objects, using the empty string (or other 'magic' value) for NULL.

Query will not run with variables, will work when variable's definitions are pasted in

This is a Query in VBA (Access 2007)
I have 3 strings defined:
str_a = "db.col1 = 5"
str_b = " and db.col2 = 123"
str_c = " and db.col3 = 42"
Then I use these in the WHERE part of my Query:
"WHERE '" & str_a & "' '" & str_b & "' '" & str_c & "' ;"
This fails, but If I paste in the strings like this:
"WHERE db.col1 = 5 and db.col2 = 123 and db.col3 = 42;"
It works perfectly. I'm guessing the syntax is wrong when using multiple variables in a string.
Anyone have any hints?
"WHERE '" & str_a & "' '" & str_b & "' '" & str_c & "' ;"
will include single quotes within your completed WHERE clause. And the working version you showed us has none:
"WHERE db.col1 = 5 and db.col2 = 123 and db.col3 = 42;"
So, try constructing the WHERE clause with no single quotes:
"WHERE " & str_a & " " & str_b & " " & str_c & ";"
For debugging purposes, it's useful to view the final string after VBA has finished constructing it. If you're storing the string in a variable named strSQL, you can use:
Debug.Print strSQL
to display the finished string in the Immediate Window of the VB Editor. (You can get to the Immediate Window with the CTRL+g keyboard shortcut.)
Alternatively, you could display the finished string in a message box window:
MsgBox strSQL
You've got some extra single quotes in there.
Try this:
"WHERE " & str_a & str_b & str_c
Note: In general, you shouldn't build query strings by concatenating strings, because this leaves you vulnerable to SQL injection, and mishandles special characters. A better solution is to use prepared statements. But assuming you're operating in a very controlled environment the solution I gave should work.
Quick tip about troubleshooting SQL that is built dynamically: echo the SQL string resulting from all the concatenation and interpolation, instead of staring at your code.
WHERE 'db.col1 = 5' ' and db.col2 = 123' ' and db.col3 = 42';
Nine times out of ten, the problem becomes a lot more clear.
For VB6/VBA dynamic SQL, I always find it more readable to create an SQL template, and then use the Replace() function to add in the dynamic parts. Try this out:
Dim sql As String
Dim condition1 As String
Dim condition2 As String
Dim condition3 As String
sql = "SELECT db.col1, db.col2, db.col3 FROM db WHERE <condition1> AND <condition2> AND <condition3>;"
condition1 = "db.col1 = 5"
condition2 = "db.col2 = 123"
condition3 = "db.col3 = 'ABCXYZ'"
sql = Replace(sql, "<condition1>", condition1)
sql = Replace(sql, "<condition2>", condition2)
sql = Replace(sql, "<condition3>", condition3)
However, in this case, the values in the WHERE clause would change, not the fields themselves, so you could rewrite this as:
Dim sql As String
sql = "SELECT col1, col2, col3 FROM db "
sql = sql & "WHERE col1 = <condition1> AND col2 = <condition2> AND col3 = '<condition3>';"
sql = Replace(sql, "<condition1>", txtCol1.Text)
sql = Replace(sql, "<condition2>", txtCol2.Text)
sql = Replace(sql, "<condition3>", txtCol3.Text)
Some comments on constructing WHERE clauses in VBA.
Your example is by definition going to be incorrect, because you're putting single quotes where they aren't needed. This:
str_a = "db.col1 = 5"
str_b = " and db.col2 = 123"
str_c = " and db.col3 = 42"
"WHERE '" & str_a & "' '" & str_b & "' '" & str_c & "' ;"
...will produce this result:
WHERE 'db.col1 = 5' ' and db.col2 = 123' ' and db.col3 = 42' ;
This is obviously not going to work.
Take the single quotes out and it should work.
Now, that said, I'd never do it that way. I'd never put the AND in the substrings that are used to construct the WHERE clause, because what would I do if I have a value for the second string but not for the first?
When you have to concatenate a number of strings with a delimiter and some can be unassigned, one thing to do is to just concatenate them all and not worry if the string before the concatenation is unassigned of not:
str_a = "db.col1 = 5"
str_b = "db.col2 = 123"
str_c = "db.col3 = 42"
To concatenate that, you'd do:
If Len(str_a) > 0 Then
strWhere = strWhere & " AND " str_a
End If
If Len(str_b) > 0 Then
strWhere = strWhere & " AND " str_b
End If
If Len(str_c) > 0 Then
strWhere = strWhere & " AND " str_c
End If
When all three strings are assigned, that would give you:
" AND db.col1 = 5 AND db.col2 = 123 AND db.col3 = 42"
Just use Mid() to chop of the first 5 characters and it will always come out correct, regardless of which of the variables have values assigned:
strWhere = Mid(strWhere, 6)
If none of them are assigned, you'll get a zero-length string, which is what you want. If any one of them is assigned, you'll first get " AND ...", which is an erroneous leading operator, which you just chop out with the Mid() command. This works because you know that all the results before the Mid() will start with " AND " no matter what -- no needless tests for whether or not strWhere already has been assigned a value -- just stick the AND in there and chop it off at the end.
On another note, someone mentioned SQL injection. In regards to Access, there was a lengthy discussion of that which considers a lot of issues close to this thread:
Non-Web SQL Injection
I have my favorite "addANDclause" function, with the following parameters:
public addANDclause( _
m_originalQuery as string, _
m_newClause as string) _
as string
if m_originalQuery doe not contains the WHERE keyword then addANDClause() will return the original query with a " WHERE " added to it.
if m_orginalQuery already contains the WHERE keyword then addANDClause() will return the original query with a " AND " added to it.
So I can add as many "AND" clauses as possible. With your example, I would write the following to create my SQL query on the fly:
m_SQLquery = "SELECT db.* FROM db"
m_SQLquery = addANDClause(m_SQLQuery, "db.col1 = 5")
m_SQLQuery = addANDClause(m_SQLQuery, "db.col2 = 123")
m_SQLQuery = addANDClause(m_SQLQuery, "db.col3 = 42")
Of course, instead of these fixed values, such a function can pick up values available in bound or unbound form controls to build recordset filters on the fly. It is also possible to send parameters such as:
m_SQLQuery = addANDClause(m_SQLQuery, "db.text1 like 'abc*'")
While dynamic SQL can be more efficient for the engine, some of the comments here seem to endorse my view that dynamic SQL can be confusing to the human reader, especially when they didn't write the code (think of the person who will inherit your code).
I prefer static SQL in a PROCEDURE and make the call to the proc dynamic at runtime by choosing appropriate values; if you use SQL DDL (example below) to define the proc you can specify DEFAULT values (e.g. NULL) for the parameters so the caller can simply omit the ones that are not needed e.g. see if you can follow the logic in this proc:
CREATE PROCEDURE MyProc
(
arg_col1 INTEGER = NULL,
arg_col2 INTEGER = NULL,
arg_col3 INTEGER = NULL
)
AS
SELECT col1, col2, col3
FROM db
WHERE col1 = IIF(arg_col1 IS NULL, col1, arg_col1)
AND col2 = IIF(arg_col2 IS NULL, col2, arg_col2)
AND col3 = IIF(arg_col3 IS NULL, col3, arg_col3);
Sure, it may not yield the best execution plan but IMO you have to balance optimization against good design principles (and it runs really quick on my machine :)