If/Then in SQL Embedded in VBA - sql

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.

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.

Access VBA - Query Parent Table Data Easily?

Is there a shortcut way to access fields in a parent table in my query like the below:
SELECT childField1
FROM childTable
WHERE childField2 = "somestring"
AND
WHERE parentField1 = "someotherstring" 'parent field is in parentTable
My tables are of course keyed correctly with ID fields and a parentField1_ID in the childTable.
I can go the traditional route with some JOINs, but given that this is VBA i'm wondering if there isn't something easier built in to make this kind of reference.
In VBA you build a dynamic query string that results in the SQL string required (including joins, or whatever else you need), then you typically use currentDb.OpenRecordset() to set a recordset variable. Alternatively, the string can be applied to form and control Recordsource properties.
Here's an (non-working - your joins requirements are clear to me) example using variables for your two strings:
Dim str1 As String
Dim str2 As String
Dim sql As String
str1 = "somestring"
str2 = "someotherstring"
sql = "SELECT childField1 " & _
"FROM childTable1 " & _
"INNER JOIN parentTable " & _
"ON childTable.childField = parentTable.ParentField " & _
"WHERE childField = """ & str1 & """ AND " & _
" parentField = """ & str2 & """;"
Me.RecordSource = sql

Run Time error 3061 Too Few parameters. Expected 6. Unable to update table from listbox

All,
I am running the below SQL and I keep getting error 3061. Thank you all for the wonderful help! I've been trying to teach myself and I am 10 days in and oh my I am in for a treat!
Private Sub b_Update_Click()
Dim db As DAO.Database
Set db = CurrentDb
strSQL = "UPDATE Main" _
& " SET t_Name = Me.txt_Name, t_Date = Me.txt_Date, t_ContactID = Me.txt_Contact, t_Score = Me.txt_Score, t_Comments = Me.txt_Comments" _
& " WHERE RecordID = Me.lbl_RecordID.Caption"
CurrentDb.Execute strSQL
I am not sure but, you can try somethink like that
if you knom the new value to insert in the database try with a syntax like this one
UPDATE table
SET Users.name = 'NewName',
Users.address = 'MyNewAdresse'
WHERE Users.id_User = 10;
Now, if you want to use a form (php)
You have to use this
if(isset($_REQUEST["id_user" ])) {$id_user = $_REQUEST["id_user" ];}
else {$id_user = "" ;}
if(isset($_REQUEST["name" ])) {$name= $_REQUEST["name" ];}
else {$name = "" ;}
if(isset($_REQUEST["address" ])) {$address= $_REQUEST["adress" ];}
else {$adress= "" ;}
if you use mysql
UPDATE table
SET Users.name = '$name',
Users.address = '$adress'
WHERE Users.id_User = 10;
i don't know VBA but I will try to help you
Going on from my comment, you first need to declare strSQL as a string variable.
Where your error expects 6 values and access doesn't know what they are. This is because form objects need to be outside the quotations of the SQL query, otherwise (as in this case) it will think they are variables and obviously undefined. The 6 expected are the 5 form fields plus 'strSQL'.
Private Sub b_Update_Click()
Dim db As DAO.Database
dim strSQL as string
Set db = CurrentDb
strSQL = "UPDATE Main" & _
" SET t_Name = '" & Me.txt_Name & "'," & _
" t_Date =#" & Me.txt_Date & "#," & _
" t_ContactID =" & Me.txt_Contact & "," & _
" t_Score =" & Me.txt_Score & "," & _
" t_Comments = '" & Me.txt_Comments & "'," & _
" WHERE RecordID = '" & Me.lbl_RecordID.Caption & "';"
CurrentDb.Execute strSQL
end sub
Note how I have used double quotes to put the form fields outside of the query string so access knows they aren't variables.
If your field is a string, it needs encapsulating in single quotes like so 'string'. If you have a date field it needs encapsulating in number signs like so #date# and numbers/integers don't need encapsulating.
Look at the code I have done and you can see I have used these single quotes and number signs to encapsulate certain fields. I guessed based on the names of the fields like ID's as numbers. I may have got some wrong so alter where applicable... Or comment and I will correct my answer.

Return Empty String as NULL

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.

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 :)