Inserting Single/double type to DB using VBA in access - vba

I need some help with an issue that is doing my head in.
I need to update a database in access and its been working fine operating with Long and Integers.
Look at this code.
sql = "UPDATE DBNAME SET Long_Field = " & Long_Variable & " WHERE ID = " & id
DoCmd.SetWarnings (False)
DoCmd.RunSQL sql
This code runs flawlessly, it takes a long variable and puts it into the correct field which is set as Long.
However, if I want to populate a single/double field (ive tried both)
sql = "UPDATE DBNAME SET Double_Field = " & double_Variable & " WHERE ID= " & id
DoCmd.SetWarnings (False)
DoCmd.RunSQL sql
I keep getting Run-Time error 3144: Syntax error in update statement.
I can literally just switch out the field name and the variable name and the code runs flawlessly, but as soon as i try to send a double value, for example (5,8), to a field in the table that is set to double, it gives me this error.
Anyone?

I assume that you want a dot as decimal separator in your string.
The conversion from double to string is done using the separator from the system locale settings so in your case a comma.
This means that
double_variable = 5.8
sql = "... " & double_variable & " ..."
will produce ... 5,8 ... in the sql variable.
The easiest way to fix that is to use
"..." & Replace(CStr(double_variable), ",", ".") & "..."
This will replace all , with .. I put the CStr there to make sure it gets converted to a string first. It will also work if the system locale changes since nothing will happen if there is no ,. The only caveat is that if for some reason the conversion inserts 1000s separators it will fail but that would only be relevant in other circumstances as I don't think CStr will ever do that.

The current answer is not the easiest, neither the simplest.
The universal method is to use Str as it always returns a dot as the decimal separator:
sql = "UPDATE DBNAME SET Double_Field = " & Str(double_Variable) & " WHERE ID = " & id & ""

Related

Syntax error in where clause in Access

The error I am getting is Syntax error in where clause in Access.
Here is the code:
SQL = "Select * FROM tblPermitAgencyInformation & WHERE [RecordID] = " & Me.AgencyInfoRecordID.Value
Set rs = db.OpenRecordset(SQL)
RecordID is an autonumber field and the AgencyInfoRecordID is an integer.
It looks like you misread the article you state. It appears to be attempting to format the adhoc query in the text variable.
Note: it says
strSQL = "SELECT wazzle FROM bamsploot" & vbCrLf & " WHERE plumsnooker = 0"
You need to make sure that you have the ampersands outside of the quotes, (they are used to append variables and strings together in this case)
Follow June7 advice and remove the ampersand there. It should help you get running.
Make your code this:
SQL = "Select * FROM tblPermitAgencyInformation WHERE [RecordID] = " & Me.AgencyInfoRecordID.Value
Hope that helps

Access 2010 VBA: SQL insert query is picking up 1 variable but not the other variable

OK usually I'm pretty good at googling around and using debug.print to isolate and solve the problem but this one is escaping me.
The purpose of this code is to create a new record in a table, using a form in which a person has selected a team member's name from a dropdown and a project phase from a dropdown and then input a number of hours into a textbox, then clicked a button that says "Add". There are a few if/thens involved but I'm leaving out the irrelevant parts (the code produces the same error in all cases.)
All of the code takes place inside one public function. All variables are Dim.
First it runs some code to find the value of "MyPersonID". (Complicated and not relevant as that works just fine).
Then it runs some code to find the value of "MyProjectPhaseID" which looks like this:
MyProjectPhaseID = [Forms]![HourValidationsFromTeam]![InputProjectPhase]
This variable populates correctly (as per Debug.Print)
Then it creates the INSERT SQL statement and runs it:
strAppendHourRecordSQL = "INSERT INTO PersonCommitmentsHours ( PersonNameLookup, ProjectPhase, WeekOfCommitment, DateValidated, HourCommitment, ValidationResult ) SELECT '" & (MyPersonID) & "' AS PersonNameLookup, '" & MyProjectPhaseID & "' AS ProjectPhase, [Forms]![HourValidationsFromTeam]![LastWeekDate] AS Week, Date$() AS TodaysDate, [Forms]![HourValidationsFromTeam]![InputSuppliedHours] AS Hours, " & Chr(34) & "More" & Chr(34) & " AS ValidationType;"
Debug.Print MyProjectPhaseID
Debug.Print strAppendHourRecordSQL
DoCmd.RunSQL strAppendHourRecordSQL
This is what Debug.Print returns:
2069
INSERT INTO PersonCommitmentsHours ( PersonNameLookup, ProjectPhase, WeekOfCommitment, DateValidated, HourCommitment, ValidationResult ) SELECT '260' AS PersonNameLookup, '' AS ProjectPhase, [Forms]![HourValidationsFromTeam]![LastWeekDate] AS Week, Date$() AS TodaysDate, [Forms]![HourValidationsFromTeam]![InputSuppliedHours] AS Hours, "More" AS ValidationType;
The query runs correctly and inserts a record with everything in the right place except it's missing the value where MyProjectPhaseID should go. It's just null. I thought maybe the variable was null, but Debug.Print returns the correct value. Even the debugger fills the value in when I hover over the SQL.
I tried different combinations of adding and removing parentheses and quotes around the variable in the SQL but they have no effect.
Please help!
I figured out the problem. The problem is that you cannot define the SQL before the variables have been populated. I thought you could define the SQL and then re-use it depending on where you get your variables from. But no. That's why it had the right value for the variable, but it couldn't put them together. I didn't make it clear from the way I wrote the question that this could be a suspect, i'm sorry about that.
So in order to not try to pre-define SQL for variables that don't exist yet, I isolated the part of the SQL that won't change and define that first as strBoilerplateSQL.
Then do the IF statement for the stuff that could change, then define the part of the SQL statement that depends on that change, then concat the 2 sql statements together. Then it runs the completed SQL statement.
strBoilerplateSQL = "INSERT INTO PersonCommitmentsHours ( PersonNameLookup, WeekOfCommitment, DateValidated, HourCommitment, ValidationResult, ProjectPhaseLookup ) SELECT " & (MyPersonID) & " AS PersonNameLookup, [Forms]![HourValidationsFromTeam]![LastWeekDate] AS Week, Date$() AS TodaysDate, [Forms]![HourValidationsFromTeam]![InputSuppliedHours] AS Hours, " & Chr(34) & "More" & Chr(34) & " AS ValidationType, "
'Check to see if this is going in to an existing project or should we create a new project first
If (IsNull([Forms]![HourValidationsFromTeam]![InputNewProject].Value)) Then
'If the Input New Project text box is null, assemble the SQL and run it
MyProjectPhaseID = [Forms]![HourValidationsFromTeam]![InputProjectPhase].Value
strMyProjectPhaseSQL = "" & (MyProjectPhaseID) & " AS ProjectPhase;"
strReadySQL = (strBoilerplateSQL) & (strMyProjectPhaseSQL)
DoCmd.RunSQL strReadySQL
Else
'Some other stuff happens here
MyProjectPhaseID = GetPhaseID![TheProjectPhase]
'Now that we have the new project phase ID we can run the SQL from above (oh hey remember that?)
strMyProjectPhaseSQL = "" & (MyProjectPhaseID) & " AS ProjectPhaseLookup;"
strReadySQL = (strBoilerplateSQL) & (strMyProjectPhaseSQL)
DoCmd.RunSQL strReadySQL
End If

VBScript not executing sql statment properly

I write you this time because a VBScript that one of the application my company uses to retrieve information from an Oracle database does not seem to be working properly. Here are the facts:
There's part of the code that does the following:
sSql = "SELECT REQ_PAYMODE" & _
" FROM SYSADM.GBPRESTATIEGROEP" & _
" WHERE 1=1" & _
" AND SLEUTEL = " & sKeyPrestatiegroep
Set oRSGBPrest = connADO.execute(sSql)
If Not oRSGBPrest.EOF Then
sRequestPaymodeKey = oRSGBPrest("REQ_PAYMODE")
Else
//error handling
End If
Using a Statement Tracer for Oracle (www.aboves.com) I can capture that same statement with its corresponding value:
SELECT REQ_PAYMODE FROM
SYSADM.GBPRESTATIEGROEP WHERE 1=1 AND
SLEUTEL = 1572499
Now, the VBScript is supposed to take that value and execute another query:
sSql = "SELECT PAM_CODE" & _
" FROM SYSADM.PAYMODES" & _
" WHERE 1=1" & _
" AND PAM_KEY = " & sRequestPaymodeKey
Set oRSPaymodes = connADO.execute(sSql)
Right in this last line of code, the script throws an error that says:
ORA-00936: missing expression at line XXX --> Set oRSPaymodes = connADO.execute(sSql) <--
Which basically means that the query in (3) is not correct, which also means that for some reason sRequestPaymodeKey is empty. I cannot tell this for sure because this failing sql statement does not appear in the statement tracer, but that's the only explanation I could find. However, the worst part is that when running the query (2) on SQLDeveloper (that's where value sRequestPaymodeKey comes from) it shows a row with a value other than null or zero.
I can't think of anything else that might be happening here, maybe it's just a server thing... no idea.
Any suggestions from you guys? Any way I can actually debug a VBE file?
Your help is much appreciated!
You need to cast sRequestPaymodeKey as a vbLong which corresponds to sql's INT. I'm assuming PAM_KEY is an INT. A recordset will return a string value. So, your code would look like this:
If IsNumeric(sRequestPaymodeKey) Then
sSql = "SELECT PAM_CODE" & _
" FROM SYSADM.PAYMODES" & _
" WHERE 1=1" & _
" AND PAM_KEY = " & CLng(sRequestPaymodeKey)
Set oRSPaymodes = connADO.execute(sSql)
Else
'do error handling due to bad returned data(empty string?)
End If
Also, consider parameterizing your queries to prevent sql injection.
A few ideas to try:
Before Set oRSPaymodes = connADO.execute(sSql), put in a MsbBox and see what SQL is being executed. Is it valid? Will it run in a Oracle query analyzer(if there is one)?
Hard code a valid value in place of sRequestPaymodeKey. Does it work then?

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)

Escaping ' in Access SQL

I'm trying to do a domain lookup in vba with something like this:
DLookup("island", "villages", "village = '" & txtVillage & "'")
This works fine until txtVillage is something like Dillon's Bay, when the apostrophe is taken to be a single quote, and I get a run-time error.
I've written a trivial function that escapes single quotes - it replaces "'" with "''". This seems to be something that comes up fairly often, but I can't find any reference to a built-in function that does the same. Have I missed something?
The "Replace" function should do the trick. Based on your code above:
DLookup("island", "villages", "village = '" & Replace(txtVillage, "'", "''") & "'")
It's worse than you think. Think about what would happen if someone entered a value like this, and you haven't escaped anything:
'); DROP TABLE [YourTable]
Not pretty.
The reason there's no built in function to simply escape an apostrophe is because the correct way to handle this is to use query parameters. For an Ole/Access style query you'd set this as your query string:
DLookup("island", "village", "village = ? ")
And then set the parameter separately. I don't know how you go about setting the parameter value from vba, though.
Though the shorthand domain functions such as DLookup are tempting, they have their disadvantages. The equivalent Jet SQL is something like
SELECT FIRST(island)
FROM villages
WHERE village = ?;
If you have more than one matching candidate it will pick the 'first' one, the definition of 'first' is implementation (SQL engine) dependent and undefined for the Jet/ACE engine IIRC. Do you know which one would be first? If you don’t then steer clear of DLookup :)
[For interest, the answer for Jet/ACE will either be the minimum value based on the clusterd index at the time the database file was last compacted or the first (valid time) inserted value if the database has never been compacted. Clustered index is in turn determined by the PRIAMRY KEY if persent otherwise a UNIQUE constraint or index defined on NOT NULL columns, otherwise the first (valid time) inserted row. What if there is more than one UNIQUE constraint or index defined on NOT NULL columns, which one would be used for clustering? I've no idea! I trust you get the idea that 'first' is not easy to determine, even when you know how!]
I've also seen advice from Microsoft to avoid using domain aggregate functions from an optimization point of view:
Information about query performance in an Access database
http://support.microsoft.com/kb/209126
"Avoid using domain aggregate functions, such as the DLookup function... the Jet database engine cannot optimize queries that use domain aggregate functions"
If you choose to re-write using a query you can then take advantage of the PARAMETERS syntax, or you may prefer the Jet 4.0/ACE PROCEDURE syntax e.g. something like
CREATE PROCEDURE GetUniqueIslandName
(
:village_name VARCHAR(60)
)
AS
SELECT V1.island_name
FROM Villages AS V1
WHERE V1.village_name = :village_name
AND EXISTS
(
SELECT V2.village_name
FROM Villages AS V2
WHERE V2.village_name = V1.village_name
GROUP
BY V2.village_name
HAVING COUNT(*) = 1
);
This way you can use the engine's own functionality -- or at least that of its data providers -- to escape all characters (not merely double- and single quotes) as necessary.
But then, it should be like this (with one more doublequote each):
sSQL = "SELECT * FROM tblTranslation WHERE fldEnglish=""" & myString & """;"
Or what I prefer:
Make a function to escape single quotes, because "escaping" with "[]" would not allow these characters in your string...
Public Function fncSQLStr(varStr As Variant) As String
If IsNull(varStr) Then
fncSQLStr = ""
Else
fncSQLStr = Replace(Trim(varStr), "'", "''")
End If
End Function
I use this function for all my SQL-queries, like SELECT, INSERT and UPDATE (and in the WHERE clause as well...)
strSQL = "INSERT INTO tbl" &
" (fld1, fld2)" & _
" VALUES ('" & fncSQLStr(str1) & "', '" & fncSQLStr(Me.tfFld2.Value) & "');"
or
strSQL = "UPDATE tbl" & _
" SET fld1='" & fncSQLStr(str1) & "', fld2='" & fncSQLStr(Me.tfFld2.Value) & "'" & _
" WHERE fld3='" & fncSQLStr(str3) & "';"
I believe access can use Chr$(34) and happily have single quotes/apostrophes inside.
eg
DLookup("island", "villages", "village = " & chr$(34) & nonEscapedString & chr$(34))
Though then you'd have to escape the chr$(34) (")
You can use the Replace function.
Dim escapedString as String
escapedString = Replace(nonescapedString, "'", "''")
Parametrized queries such as Joel Coehoorn suggested are the way to go, instead of doing concatenation in query string. First - avoids certain security risks, second - I am reasonably certain it takes escaping into engine's own hands and you don't have to worry about that.
By the way, here's my EscapeQuotes function
Public Function EscapeQuotes(s As String) As String
If s = "" Then
EscapeQuotes = ""
ElseIf Left(s, 1) = "'" Then
EscapeQuotes = "''" & EscapeQuotes(Mid(s, 2))
Else
EscapeQuotes = Left(s, 1) & EscapeQuotes(Mid(s, 2))
End If
End Function
For who having trouble with single quotation and Replace function, this line may save your day ^o^
Replace(result, "'", "''", , , vbBinaryCompare)
put brackets around the criteria that might have an apostrophe in it.
SOmething like:
DLookup("island", "villages", "village = '[" & txtVillage & "]'")
They might need to be outside the single quotes or just around txtVillage like:
DLookup("island", "villages", "village = '" & [txtVillage] & "'")
But if you find the right combination, it will take care of the apostrophe.
Keith B
My solution is much simpler. Originally, I used this SQL expression to create an ADO recordset:
Dim sSQL as String
sSQL="SELECT * FROM tblTranslation WHERE fldEnglish='" & myString & "';"
When myString had an apostrophe in it, like Int'l Electrics, my program would halt. Using double quotes solved the problem.
sSQL="SELECT * FROM tblTranslation WHERE fldEnglish="" & myString & "";"