I have a datetimepicker in my app which end users will use to choose when an item will be deferred to.
If the user picks a date and hits confirm, the date should be dropped into the mysql record for this entry, but I cant seem to get VB to update the DATE value :(
I'm thinking its probably a formatting thing, but I'm not sure how to best overcome this.
He're the code behind the 'confirm' button.
Private Sub ConfirmDefer_Click(sender As Object, e As EventArgs) Handles ConfirmDefer.Click
Dim deferdate As Date = Format(nb_deferdatepicker.Value, "yyyy-MM-dd")
updatesql("newbusiness_dataset", "action_date", deferdate, "id='" & nb_prospectid.Text & "'")
nb_defer_schedule.Visible = False
nb_defer_schedule.Enabled = False
End Sub
The 'UpdateSQL' bit is a function I use to simplify updating the db.
Its output SQL command is as follows:
UPDATE newbusiness_dataset SET action_date='20/02/2016' WHERE id='100001'
Any help appreciated! Thanks in advance
output SQL command is as follows:
UPDATE newbusiness_dataset SET action_date='20/02/2016' WHERE id='100001'
The way your function is updating is likely incorrect. Ticks are not all-purpose SQL variable delimiters. They are used to indicate text: so your method is converting the Date to string, and possibly the same for Id which are typically numeric. Similarly, this code:
Dim deferdate As Date = Format(nb_deferdatepicker.Value, "yyyy-MM-dd")
Using Option Strict On this will not compile. Format is a function returning a string, but the code is assigning the result to a DateTime var. There is simply no reason to try to format a DateTimePicker value, because it already is a date, and the MySQL NET data provider objects know how to pass a NET date to the db.
Given the resulting SQL, I also suspect you are using concatenation to glue bits of string together for the query. This is very dangerous and can lead to SQL injection attacks. It will also fail on names like Charles D'Artagnan or Pete's Plumbing.
SQL Parameters prevent this, allow you to pass typed data to the data provider and result in easy to read and maintain SQL. The general approach would be like this:
Dim rows As Int32
Dim SQL = "UPDATE Table_Name SET colA = #p1, colB = #p2, colC = #p3 WHERE Id = #p4"
Using dbcon As New MySqlConnection(connStr)
Using cmd As New MySqlCommand(SQL, dbcon)
cmd.Parameters.Add("#p1", MySqlDbType.String).Value = myFrm.TextBox1.Text
cmd.Parameters.Add("#p2", MySqlDbType.Int32).Value = intVar
cmd.Parameters.Add("#p3", MySqlDbType.DateTime).Value = DateTime.Now
' the where:
cmd.Parameters.Add("#p4", MySqlDbType.Int32).Value = intVarA
dbcon.Open()
rows = cmd.ExecuteNonQuery()
End Using
End Using
The USING blocks dispose of the things which should be disposed of to free resources
A new connection and command object are created, used and disposed of. Often helpers like to reuse these, but especially with the DBCommand object there is nothing reusable about them.
Since ExecuteNonQuery will report how many rows are affected, rows captures that result.
The magic, such as it is, is here:
cmd.Parameters.Add("#p3", MySqlDbType.Date).Value = datetimeVar
Using parameters you can pass an actual DateTime variable instead of text. You can use myDateTimePicker.Value because the value is a DateTime.
For a Date column, using MySqlDbType.Date the parameter will pass just the Date portion to the database.
MySqlDbType.DateTime will save whatever portion of the DateTime the DB is setup to accept.
The same is true using MySqlDbType.Int32 for the Id: rather than converting to string, you can pass the correct data type.
Related
I want to change a date in a specific table to today's date by clicking a button in a related form. So all the button does is changing the date in a certain field in my DB. Is there a simple way to do this with VBA?
*Update
Well I wrote this in my VBA code:
CurrentDb.Execute "UPDATE Machines SET LastMaintenance = Date() WHERE MachineID = MachineID.Value"
With "Machines" being my table, "LastMaintenance" the column containing the date that has to be changed into today's date, "MachineID" the name of the record and "MachineID.Value" the name of the textbox bound to that same record.
When I click the button I get this error:
"Not enough parameters. 1 expected."
When performing an update query, you'll want to be cognizant of the datatype for each field, as you will have to present it differently in your code. Also, you will need to break up your string text when inserting a variable. In your current state, it's looking for a MachineID field with 'MachineID.value' as its contents. Try this:
CurrentDb.Execute "UPDATE Machines SET LastMaintenance = Date() WHERE MachineID = " & MachineID.Value
The most straightforward way is to run a UPDATE query.
CurrentDB.Execute "UPDATE someTable SET someDate = Date() WHERE stuff = 47"
If
a button in a related form
means a form bound to that table displaying the record you wish to update, use the OnClick event of the button:
Private Sub NameOfYourButton_Click()
Me![NameOfYourDateField].Value = Date
' Optionally, save the record at once:
Me.Dirty = False
End Sub
An UPDATE command is dangerous because you are making a change to the database assuming sane inputs. I would recommend using parameterized VBA code to avoid both SQL injection and throw an error in VBA for malformed inputs.
This example uses a static string to load your recordset, then it clearly states that the unverified input is only used in a Find command. Then it only acts if a matching record is found. This is a safer operation, albeit more verbose. It's also debuggable in VBA where the SQL UPDATE command is a kind of black box.
With CurrentDb.OpenRecordset("Machines", dbOpenDynaset)
.FindFirst "[MachineID]=" & CLng(MachineID.Value)
If .NoMatch Then
Debug.Print "ID not found: " & MachineID.Value
Else
.Edit
.Fields.Item("LastMaintenance").Value = Date()
.Update
End If
.Close
End With
I've been trying to send a long MySQL command in vb.NET. When I want to use variables as part of the MySQL command string I've been concatenating the string using "&".
This works, but makes my code look messy, so I tried using "{0}, {1}" etc.
This however does not work and I get the error:
Overload resolution failed because no accessible 'New' accepts this number of arguments.
Examples:
connector.Open()
commander = New MySqlCommand("UPDATE 'pupil_data' SET '" & Question & "'='" & Answer & "' WHERE 'username'=" & Environment.UserName & ";", connector)
dataAdapter = New MySqlDataAdapter(commander)
connector.Close()
This works, however:
connector.Open()
commander = New MySqlCommand(("UPDATE 'pupil_data' SET '{0}'='{1}' WHERE 'username'='{2}'", Question, Answer, Environment.Username), connector)
dataAdapter = New MySqlDataAdapter(commander)
connector.Close()
Doesn't work. It could just be a problem with my bracketing but I've tried several combinations and it's logically sound (I think).
The "{n}" designations do not represent automatic concatenation, but are a formatting placeholder used as part of String.Format. A string with a placeholder wont use automatically know what to do with it. Your MySQLCommand object certainly doesn't know how to use it, so you get the error. Eg:
connector.Open()
Dim SQL As String = String.Format("UPDATE pupil_data SET '{0}'='{1}' WHERE 'username'='{2}'",
Question, Answer, Environment.Username)
commander = New MySqlCommand(SQL, connector)
dataAdapter = New MySqlDataAdapter(commander)
connector.Close()
I am not sure you need all those ticks in the SQL either. Note that if something like Question is a numeric, you need to convert to string:
Dim SQL As String = String.Format("Update ...",
Question.ToString, Answer.ToString, Environment.Username)
There are a few things like Console.WriteLine, StringBuilder.AppendFormat which automatically implement this as well.
See String.Format
Note that this is absolutely the wrong way to create SQL. If user name for instance is D'Artagnan the query will choke. String.Format does not protect you from SQL Injection, its just neater code than concatenating bits of string together.
I have this qry in access, if I go into its design it has a criteria (which as I understand it is a parameter).
The Report this qry is based off of works great, click on it a little thing pops up asks the required info and off it goes. In code I am trying to do this and get a
Run-time error '424'
Object Required
the offending line:
qdf.Parameters("Insurance Name").Value = inputStr
Lines before it:
Set qfd = CurrentDb.QueryDefs("qryInsGrpRoster")
Dim inputStr As String
inputStr = InputBox("Enter Insurance")
'Supply the parameter value
qdf.Parameters("Insurance Name").Value = inputStr
inputStr definitely equals the value, it fails though.
The criteria line in the qry is:
Like "*" & [Insurance Name] & "*"
Do I need the likes and all that to set that parameter?
in Access 2010 and 2013
This uses DAO and might be of interest
DIM MyQryDef as querydef
Dim a as string
a = ""
a = a & "PARAMETERS Parameter1 INT, Parameter2 INT; "
a = a & "SELECT f1, f2 FROM atable WHERE "
a = a & "f3 = [Parameter1] AND f4 = [Parameter2] "
a = a & ";"
Set MyQryDef = currentdb().CreateQueryDef("MyQueryName", a)
MyQryDef.Parameters("Parameter1").Value = 33
MyQryDef.Parameters("Parameter2").Value = 2
' You could now use MyQryDef with DAO recordsets
' to use it with any of OpenQuery, BrowseTo , OpenForm, OpenQuery, OpenReport, or RunDataMacro
DoCmd.SetParameter "Parameter1", 33
DoCmd.SetParameter "Parameter2", 2
DoCmd.Form YourFormName
' or
DoCmd.SetParameter "Parameter1", 33
DoCmd.SetParameter "Parameter2", 2
DoCmd.OpenQuery MyQryDef.Name
See here:
https://msdn.microsoft.com/en-us/library/office/ff194182(v=office.14).aspx
Harvey
The parameters property of an Access Query is read only.
You have basically two options here that I can think of right off.
The first is to just completely rewrite the SQL of the saved query each time you need to use it. You can see an example of this here: How to change querydef sql programmatically in MS Access
The second option is to manually set the RecordSource of the report each time it opens. Using this method you will not use a saved query at all. You'll need to set/store the entire SQL statement in your code when the report opens, ask for any input from the user and append the input you get to your SQL statement. You could setup a system where the base SQL is stored in a table instead but, for simplicity, that's not necessary to achieve what you're trying to do here.
MS Access does allow you to use parametrized queries in the manner you're attempting here (not the same code you have), but as far as I know, it would require you to use Stored Procedures in MS SQL Server or MySQL and then you'd need to use ADO. One big downside is that Access reports cannot be bound to ADO recordsets so this isn't really an option for what you're trying to do in this particular instance.
Seems like a typo. You're creating the object named 'qfd', and trying to use the object named 'qdf'
Set qfd = ...
and then
qdf.Para...
I like to put Option Explicit in my modules to help me find these types of issues.
I have an access 2003 front end database with a form that allows users to see a sorted and/or filtered view of some data. The data is displayed in a sub-form.
The base data (loaded when the form is opened) is retrieved into a disconnected ADODB.Recordset object (static client side cursor). The sub-form's Recordset property is set to the disconnected recordset and all records are displayed.
Applying just a sort (in code) to the recordset object and then setting the sub-form to use the sorted recordset displays the data with the correct sort applied. The filter property is set to adFilterNone for this to work. All records are displayed (correct).
Applying just a filter (in code) to the recordset object and then setting the sub-form to use the filtered recordset displays the data with the correct filter applied. The sort property is set to an empty string for this to work. All records matching the filter are displayed (correct).
When both the sort property AND the filter property are set on the recordset, and that recordset is then set to the sub-form's Recordset property, only the first 100 matching records are displayed (incorrect). They are displayed in sort order. The underlying recordset object shows the correct record count for the filtered records, they just don't all display on the form.
Does anyone know why this is happening and if there is a way to get around this apart from creating a recordset using a new SQL string each time?
Thanks in advance.
What you are seeing with filtering and sorting is a known limitation of ADO recordsets.
Take a look at the list of ADO Cons listed on this page. Notice the one on the bottom:
http://www.utteraccess.com/wiki/index.php/Choosing_between_DAO_and_ADO
I couldn't find any documentation on MS's Support site about this so I don't know if it's a bug or simply a limitation. I'm assuming it's the latter.
FYI, I think MS has basically forgotten about ADO (classic). The last release of MDAC (which is how you obtain ADO) was 5/10/2005.
As far as a work-around for this problem, you can try using this function. It returns a new, filtered and sorted recordset. Just keep a big, full recordset handy and use this function to get a new one every time you do a sort/filter. This does increase your overall resource usage, especially memory.
I have used this function but it hasn't been fully tested to make sure it's bullet proof in every way. You might quickly find some bug or limitation with it. I actually had a note that it needed some kind of work but my note was unclear, I didn't have time to test it now, and I did find that I'm using this function in my production code so I think it's working.
Public Function GetFilteredRecordset(ByRef rsSource As ADODb.Recordset, _
ByVal sWhere As String, _
Optional ByVal sOrderBy As String, _
Optional ByVal LockType As ADODb.LockTypeEnum = adLockUnspecified) As ADODb.Recordset
Dim sOriginalOrderBy As String
sOriginalOrderBy = rsSource.Sort
Dim F As ADODb.Field
For Each F In rsSource.Fields
'Debug.Print F.Name
Next F
rsSource.Filter = sWhere
If sOrderBy <> "" Then
If Left(LCase(sOrderBy), 8) = "order by" Then sOrderBy = Trim(Right(sOrderBy, Len(sOrderBy) - 8))
rsSource.Sort = sOrderBy
End If
Dim rsF As ADODb.Recordset
Dim objStream As ADODb.Stream
'Create a New ADO 2.5 Stream object
Set objStream = New ADODb.Stream
'Save the Recordset to the Stream object in XML format
rsSource.Save objStream, adPersistXML
'Create an exact copy of the saved Recordset from the Stream Object
Set rsF = New ADODb.Recordset
rsF.Open objStream, , , LockType
rsSource.Filter = ""
rsSource.Sort = sOriginalOrderBy
'Close and de-reference the Stream object
objStream.Close
Set objStream = Nothing
Set GetFilteredRecordset = rsF
End Function
Another strange limitation of filtering ADO recordsets is that your OR keyword must always be on the top level. That is also documented in the link I posted above although I'm not sure if the examples given are accurate.
im trying to get the next number in the autonumber sequence for the primary key programatically. For instance, if the last number in the table was 10, i need it to return 11. Before, I would use something like:
docmd.RunCommand acCmdRecordsGoToNew
in order to tell the database to go to the next record, and then i'd assign it to a control on the form to show the user what record they are currently entering. The problem is, this function ceased to work when I disabled the navigation buttons by setting it's property to "No" in the properties window. How do I get the next record in vba without the nav bar being enabled?
To know what the real next value is, you have to look up the SeedValue for your Autonumber column. This code does that:
Public Function GetSeedValue(strTable As String, strColumn As String) As Long
Dim cnn As Object 'ADODB.Connection
Dim cat As Object ' New ADOX.Catalog
Dim col As Object ' ADOX.Column
Set cnn = CurrentProject.Connection
Set cat = CreateObject("ADOX.Catalog")
cat.ActiveConnection = cnn
Set col = cat.Tables(strTable).Columns(strColumn)
GetSeedValue = col.Properties("Seed")
Set col = Nothing
Set cat = Nothing
Set cnn = Nothing
End Function
If you're going to call it a lot, you'd likely want to cache the ADOX Catalog object variable, rather than re-initialize it each time you call this function.
Note that in a multiuser environment, this may or may not be accurate, since by the time you use it, it may have been updated by another user. However, it doesn't have the problem with skipping Autonumber values that Max()+1 can have.
Keep in mind, though, that if you care about the next Autonumber value, it means YOU'RE USING IT WRONG. Autonumber values are surrogate keys and you should never, ever care what the values are.
Turns out that there is a VBA function that will interact with the database and actually return a value. This is what I ended up doing to get the next record number:
Dim nextRecord As Integer
nextRecord = DMax("ID", "my_table") + 1
Sloppy, but effective for my single client situation. There is also a where clause that can be applied:
Dim nextRecord As Integer
nextRecord = DMax("ID", "my_table", "field = value")