MS Access parameterized queries VB - sql

dbs.Execute " INSERT INTO Log " _
& "(UserName, DateAccessed) VALUES " _
& "(#GetLogonName, #Today);"
GetLogonName and Today are variables but I get error "error- too few parameters, expected two". If I run the function using actual values like &"('abce', '2/2/2012') it works.
What am I doing wrong?
Thanks

Database.Execute doesn't accept query parameters, only the execution options defined in RecordsetOptionEnum.
To run a parameterized query you need to create a QueryDef object:
Dim query As QueryDef
Set query = dbs.CreateQueryDef("", "INSERT INTO LOG (UserName,DateAccessed)" & _
" VALUES(#user,#time)")
query.Parameters("#user").Value = "Moo"
query.Parameters("#time").Value = Now
query.Execute
The empty string means this is a temporary QueryDef. If you enter any other name, or omit the name entirely, a new Query object will be created in the database.
If you use the same query frequently, it's a good idea to create a Query and call it by name:
Set query = dbs.QueryDefs("myQueryName")
...
QueryDef.Execute accepts the same execution parameters as Database.Execute

Related

Run one MS Access SQL script on a particular Table chosen by user

I have a MS Access 2016 database (*.accdb) with 20+ Tables. Fields in each of them vary slightly from Table to Table. I've no VBA experience, so I'm sticking only to the SQL query below (redacted).
SQL script
myvar below is the parameter I'd like to be prompted when the script is run so that I enter the Table I want the changes applied to.
PARAMETERS
[myvar] TableID;
UPDATE
[myvar]
INNER JOIN
Excel_Data ON [myvar].[Part Number] = Excel_Data.[Part Number]
SET
[myvar].[Value] = '?',
[myvar].Description = Excel_Data.Description,
[myvar].[Ref] = '?'
.
.
.
WHERE
[myvar].Description Is Null;
Output
Error message:
Too few parameters. Expected 0.
What I need
I prefer a solution for above in a SQL script form as above, not involving VBA, preferably. I'd like to enter the Table name when prompted so the script knows which table to UPDATE. FYI: The PARAMETERS work when it is not a Table as I've shown in my script above.
Help/advise is highly appreciated.
EDIT 1
Since it seems not possible to use parameters as Table names, could you suggest a VBA solution? A sample code, perhaps?
As said in the comments, you can't really solve this without VBA.
You can store your SQL query in a string, and use a placeholder to indicate the tablename. Then get the tablename using an inputbox and replace the placeholder with the tablename.
Dim sqlString As String
sqlString = "UPDATE [%Placeholder%] " & vbCrLf & _
"INNER JOIN Excel_Data ON [%Placeholder%].[Part Number] = Excel_Data.[Part Number] " & vbCrLf & _
"SET [%Placeholder%].[Value] = '?', " & vbCrLf & _
...
"WHERE [%Placeholder%].Description Is Null;"
sqlString = Replace(sqlString, "%PlaceHolder%", InputBox("Enter a tablename"))
CurrentDb.Execute sqlString
In a more mature solution, I'd create a form with a combobox containing all available table names, and add a function to sanitize tablenames (replace "]" with "]]")

SQL statement in VBA

I am trying to run the following SQL statement in ACCESS 2013 VBA but am getting errors due to wrong formatting (in this case I get "Semicolon (;) missing from end of statement"). Could anybody tell me what I am doing wrong in the code below please?
Dim dbs As dao.Database
Set dbs = CurrentDb()
dbs.Execute "INSERT INTO TEMP2 ([Study_Date], [Created_By], [Part_Number],
[Upper_Tolerance], [Lower_Tolerance], [ID21_Number]) VALUES ([Study_Date],
[Created_By], [Part_Number], [Upper_Tolerance], [Lower_Tolerance], [ID21_Number])
FROM RAC_DATA_ENTRY
WHERE [RAC_CAP_VALS] = '" & Me.[RAC_CAP_VALS] & "'"
Don't use VALUES when you're pulling data from one table to INSERT into another. Use SELECT instead.
This example uses just two of your fields. Add in the others you need.
Dim strInsert As String
strInsert = "INSERT INTO TEMP2 ([Study_Date], [Created_By])" & _
" SELECT [Study_Date], [Created_By] FROM RAC_DATA_ENTRY" & _
" WHERE [RAC_CAP_VALS] = '" & Me.[RAC_CAP_VALS].Value & "';"
Debug.Print strInsert '<- view this in Immediate window; Ctrl+g will take you there
dbs.Execute strInsert, dbFailOnError
Notes:
A semicolon at the end of the statement is optional. Access will consider the statement valid with or without it.
Value is not actually required following Me.[RAC_CAP_VALS], since it's the default property. I prefer to make it explicit.
dbFailOnError gives you better information about failed inserts. Without it, a problem such as a primary key violation would fail silently.
Debug.Print strInsert allows you to inspect the statement you built and are asking the db engine to execute. If there is a problem, you can copy the statement text from the Immediate window and paste it into SQL View of a new Access query for testing.

Parameterized field name

I was thinking if this will work:
Dim query As String = "UPDATE tblPiglets SET #to=#todate, CurrentLocation=#to" & _
" WHERE Week=#week AND SowOrder=#so AND PigletNumber=#pig"
But I caught Cannot update #to field lol
The #to is a variable in which I thought would work the same its value though its worth a try. Its value is dependent on a user input, so, is there any other way to do that?
Or this? (not sure if this will work though):
Dim to As String = "foo"
Dim query As String = "UPDATE tblPiglets SET " & to & "=#todate, CurrentLocation=#to" & _
" WHERE Week=#week AND SowOrder=#so AND PigletNumber=#pig"
It is always preferable to use parameters to insert user input into SQL code but parameters can only be used for values, not identifiers. Think of SQL parameters the same as parameters in a VB method. You can't use a method parameter to specify a property or method to use and you can't use a SQL parameter to specify a column or table.
You have no choice but to use string concatenation but doing so opens you up to SQL injection, so make absolutely sure that the user cannot insert arbitrary SQL code. If it's a column name then they should have to select it from a list that you have retrieved from the database itself, so that you are guaranteed that it's a valid column.
I have used option 2 to make it work.
Dim to As String = "foo"
Dim query As String = "UPDATE tblPiglets SET " & to & "=#todate, CurrentLocation=#to" & _
" WHERE Week=#week AND SowOrder=#so AND PigletNumber=#pig"
But it would be nicer if I can get the same result if I will be using the first option. thanks

Invoking a SQL Procedure in the database via VBA code

I have an SQL query which takes up a parameter stored in the db. I want to invoke the same from VBA code which is running on the same db. I am using MS Access for my work.
So for example consider, I have an SQL query 'Q' which takes a parameter 'p', I intend to invoke this SQL query from my VBA code 'C' , which also naturally involves passing this parameter 'p' to the query.
Help much appreciated
Soham
There are a few possibilities here.
Let us say it is a SELECT query built on a form that holds the parameters to be used and that the input is safe:
s = "SELECT * FROM MyTable WHERE AText ='" & Me.MyText & "'"
This can be used like so:
Forms!SomeForm.RecordSource = s
Or
Set qdf = CurrentDb.CreateQueryDef("NewQuery", s)
However, the above can be done in other, better ways.
Let us say it is a ACTION query run from a form that holds the parameters to be used and that the input is safe:
s = "UPDATE MyTable Set AText ='" & Me.MyText & "'"
Then
Set db = CurrentDB
db.Execute s, dbFailOnError
Or you can use a temporary query, which can be safer:
'Temporary query
s = "UPDATE MyTable Set AText = MyRext"
Set qdf = db.CreateQueryDef("", s)
qdf.Parameters!MyText = Me.MyText
qdf.ReturnsRecords = False
qdf.Execute dbFailOnError
Something similar to the above would also be suitable for an existing query.
You can also pass the parameter to a procedure, in which case Me.MyText becomes a variable, or you can use Inputbox, which is rarely a good idea.
After that, there is the whole world of ADO.

Can my users inject my dynamic sql?

I'm a desktop developer writing for internal users, so I'm not worried about malicious hackers, but I would like to know if there's anything they could enter when updating a value that would execute sql on the server.
The business defines their content schema and I have a CRUD application for them that doesn't have to be changed when their schema changes because the validation details are table-driven and the updates are with dynamic SQL. I have to support single quotes in their data entry, so when they enter them, I double them before the SQL is executed on the server. From what I've read, however, this shouldn't be enough to stop an injection.
So my question is, what text could they enter in a free-form text field that could change something on the server instead of being stored as a literal value?
Basically, I'm building an SQL statement at runtime that follows the pattern:
update table set field = value where pkField = pkVal
with this VB.NET code:
Friend Function updateVal(ByVal newVal As String) As Integer
Dim params As Collection
Dim SQL As String
Dim ret As Integer
SQL = _updateSQL(newVal)
params = New Collection
params.Add(SQLClientAccess.instance.sqlParam("#SQL", DbType.String, 0, SQL))
Try
ret = SQLClientAccess.instance.execSP("usp_execSQL", params)
Catch ex As Exception
Throw New Exception(ex.Message)
End Try
Return ret
End Function
Private Function _updateSQL(ByVal newVal As String) As String
Dim SQL As String
Dim useDelimiter As Boolean = (_formatType = DisplaySet.formatTypes.text)
Dim position As Integer = InStr(newVal, "'")
Do Until position = 0
newVal = Left(newVal, position) + Mid(newVal, position) ' double embedded single quotes '
position = InStr(position + 2, newVal, "'")
Loop
If _formatType = DisplaySet.formatTypes.memo Then
SQL = "declare #ptrval binary(16)"
SQL = SQL & " select #ptrval = textptr(" & _fieldName & ")"
SQL = SQL & " from " & _updateTableName & _PKWhereClauses
SQL = SQL & " updatetext " & _updateTableName & "." & _fieldName & " #ptrval 0 null '" & newVal & "'"
Else
SQL = "Update " & _updateTableName & " set " & _fieldName & " = "
If useDelimiter Then
SQL = SQL & "'"
End If
SQL = SQL & newVal
If useDelimiter Then
SQL = SQL & "'"
End If
SQL = SQL & _PKWhereClauses
End If
Return SQL
End Function
when I update a text field to the value
Redmond'; drop table OrdersTable--
it generates:
Update caseFile set notes = 'Redmond''; drop table OrdersTable--' where guardianshipID = '001168-3'
and updates the value to the literal value they entered.
What else could they enter that would inject SQL?
Again, I'm not worried that someone wants to hack the server at their job, but would like to know how if they could accidentally paste text from somewhere else and break something.
Thanks.
Regardless of how you cleanse the user input increasing the attack surface is the real problem with what you're doing. If you look back at the history of SQL Injection you'll notice that new and even more creative ways to wreak havoc via them have emerged over time. While you may have avoided the known it's always what's lurking just around the corner that makes this type of code difficult to productionize. You'd be better to simply use a different approach.
You can also evaluate an alternative solution. Dynamic generation of SQL with parameters. Something like this:
// snippet just for get the idea
var parameters = new Dictionary<string, object>();
GetParametersFromUI(parameters);
if (parameters.ContainsKey("#id")) {
whereBuilder.Append(" AND id = #id");
cmd.Parameters.AddWithValue("#id", parameters["#id"]);
}
...
Assuming you escape string literals (which from what you said you are doing), you should be safe. The only other thing I can think of is if you use a unicode-based character set to communicate with the database, make sure the strings you send are valid in that encoding.
As ugly as your doubling up code is (:p - Try String.Replace instead.) I'm pretty sure that will do the job.
The only safe assumption is that if you're not using parameterized queries (and you're not, exclusively, here, because you're concatenating the input string into your sql), then you're not safe.
You never never ever never want to build a SQL statement using user input that will be then directly executed. This leads to SQL injection attacks, as you've found. It would be trivial for someone to drop a table in your database, as you've described.
You want to use parameterized queries, where you build an SQL string using placeholders for the values, then pass the values in for those parameters.
Using VB you'd do something like:
'Define our sql query'
Dim sSQL As String = "SELECT FirstName, LastName, Title " & _
"FROM Employees " & _
"WHERE ((EmployeeID > ? AND HireDate > ?) AND Country = ?)"
'Populate Command Object'
Dim oCmd As New OledbCommand(sSQL, oCnn)
'Add up the parameter, associated it with its value'
oCmd.Parameters.Add("EmployeeID", sEmpId)
oCmd.Parameters.Add("HireDate", sHireDate)
oCmd.Parameters.Add("Country", sCountry)
(example taken from here) (also not I'm not a VB programmer so this might not be proper syntax, but it gets the point across)