I have those two tables is MS Access :
lkpSchemaPIT :
| UID | lkpSchemaTitleEng |
|-----|--------------------------|
|--1--|---------Title1-----------|
|--2--|---------Title2-----------|
...
lkpSchemaPITChronology :
| ID | UID | PUID | Sort | Level | DateStart | DateEnd |
|----|-----|------|------|-------|-----------|---------|
|--0-|--1--|--0---|---5--|--2----|---Now()---|--NULL---|
...
The first table contains just nodes that i'm going to put in a treeview in access. I use the second table to construct the tree, but also keep track of all the parent that a node could've had through the years. You can see that the UID in the two tables are the same, but they have not a relationship between them, when I build the tree, I use a query with a join on it.
My problem is : When I want to add a new node in the lkpSchemaPIT table, I need to be able to add its "treeview" info as well (Parent, Sort, Level, etc.).
This is my code so far :
With CurrentDb
.Execute _
"INSERT INTO lkpSchemaPIT " & _
"(lkpSchemaTitleEng) " & _
"VALUES " & _
"('" & Title & "')"
.Execute _
"INSERT INTO lkpSchemaPITChronology VALUES (" & .OpenRecordset("SELECT ##IDENTITY").Fields(0) & ", " & [ParentID] & ", " & [NewSort] & ", " & [Level] & ", " & Date & ", null)"
End With
ParentID, NewSort, Level are 3 variables that have been determined before I call all this.
The "Date" parameters is the VBA function that returns the current date.
I know that the first INSERT INTO is working because a new value is displayed in my table. But the second INSERT INTO isn't working and I was able to get the error :
Error 3346 - Number of query values and destination fields are not the same.
Is anyone ever had this kind of problem ?
Once again, here is an example where parameterized queries would be invaluable as you avoid quote enclosures (including # for date/times) and string concatenation that conflates data and SQL together, rendering hard to read and maintain code.
MS Access allows the PARAMETERS clause where you can define the parameter placeholder names and data types and then in VBA you can bind values to such parameters using QueryDefs. No quotes needed or string interpolation.
SQL (save both as stored queries)
PARAMETERS TitleParam Text(255);
INSERT INTO lkpSchemaPIT (lkpSchemaTitleEng)
VALUES (TitleParam);
PARAMETERS UIDParam Long, PUIDParam Long, SortParam Long,
LevelParam Long, DateStart Datetime;
INSERT INTO lkpSchemaPITChronology (UID, PUID, [Sort], [Level], DateStart)
VALUES (UIDParam, PUIDParam, SortParam, LevelParam, DateStartParam);
VBA
...
Dim qdef As QueryDef
With CurrentDb
' FIRST QUERY
Set qdef = .QueryDefs("myFirstSavedAppendQuery")
qdef!TitleParam = [Title]
qdef.Execute dbFailOnError
' SECOND QUERY
Set qdef = .QueryDefs("mySecondSavedAppendQuery")
qdef!UIDParam = .OpenRecordset("SELECT ##IDENTITY").Fields(0)
qdef!PUIDParam = [ParentID]
qdef!SortParam = [NewSort]
qdef!LevelParam = [Level]
qdef!DateStart = Date()
qdef.Execute dbFailOnError
End With
Set qdef = Nothing
Related
I have a table in excel, with range : Sheets("Sheet1").Range("d4:d215"). These data are similar to PS.WELL in the server.
From that table, I want to retrieve data using this code (other SQL requisite has been loaded, this is the main code only):
strquery = "SELECT PS.WELL, PS.TYPE, PS.TOPSND " & _
"FROM ISYS.PS PS " & _
"WHERE PS.WELL = '" & Sheets("Sheet1").Range("D4:D215") "' AND (PS.TYPE = 'O' OR PS.TYPE = 'O_' OR PS.TYPE = 'GOW') " & _
"ORDER BY PS.WELL"
Unfortunately it didn't work. Can anyone help me how to write the code especially in the 'where' section?
You have to iterate through each item in the range and concatenate the results to a string variable so the contents look like this
'val1','val2','val3'
Then you have to adjust your query code to use the IN operator instead of equals operator. Let's say the string is concatenated to a variable called myrange.
"WHERE PS.WELL IN (" & myrange & ") AND ...
I have solved the problem. The key is to make 2 function of SQL:
to read and write each input
to count number of output per input (an input can have 0, 1, or more output).
then, just call using procedure
I have a cross tab query with 'mmm-yyyy' formatted dates for Fields in the Columns.
I have used the below Design to create the query.
Cross Tab Design View
The problem I am having is the dates are not sorting correctly from Dec-17 down to Jul-16 in descending order. This is going to be a dynamic query with months changing every month so I want to use an additional table of data to do the sorting (as opposed to entering a list of month names in the Properties window).
How would I fix my query to get it to do this please?
Thanks for your help
Unfortunately, no matter how joined tables are sorted, crosstab will sort columns by default in alphabetical order, hence Apr, Dec, ... begins the order. To change or even filter column order in crosstabs, you would specify values in PIVOT Col IN () clause of SQL statement.
Since you need a dynamic query consider creating a querydef in VBA to update the SQL behind the crosstab where you dynamically update the PIVOT Col IN () clause. Of course, pass begin and end dates as needed or by parameters:
Public Sub BuildCrossTab()
Dim db As Database
Dim qdef As QueryDef
Dim strSQL As String, dates As String
Dim i As Integer, monthsDiff As Integer
Set db = CurrentDb
' DELETE PREVIOUS SAVED QUERY
For Each qdef in db.QueryDefs
If qdef.Name = "AccuralsCrosstabQ" Then
db.Execute "DROP Table " & qdef.Name, dbFailOnError
End If
Next qdef
' LOOP THROUGH ALL MONTHS BACKWARDS
dates = "("
monthsDiff = DateDiff("m", #7/1/2016#, #12/1/2016#)
For i = monthsDiff To 0 Step -1
dates = dates & " '" & Format(DateAdd("m", i, #7/1/2016#), "mmm-yyyy") & "',"
Next i
dates = dates & ")"
dates = Replace(dates, ",)", ")")
' PREPARE SQL STRING
strSQL = "TRANSFORM SUM(a.[Amount $]) AS SumAmount" _
& " SELECT a.Company, a.[Accrual ID], SUM(a.[Amount $]) As [Total Amount $]" _
& " FROM [Accruals Raw Data] a " _
& " GROUP BY a.Company, a.[Accrual ID]" _
& " PIVOT Format(a.[Posted Date], ""mmm-yyyy"")" _
& " IN " & dates
' CREATE QUERY
Set qdef = db.CreateQueryDef("AccuralsCrosstabQ", strSQL)
Set qdef = Nothing
Set db = Nothing
End Sub
I had this working a few weeks ago but now I'm not sure what I did that made it not work anymore. I don't even get an error message to figure out what could be wrong. When I click the button I made to insert a row into the table, nothing happens. The form gets cleared and the table gets requeried, but the INSERT part of the code doesn't do anything.
Public Sub Command125_Click()
'Add row for downtime
Dim dbsCurrent As Database
Set dbsCurrent = CurrentDb
dbsCurrent.Execute " INSERT INTO tbl_Downtime " _
& "(job, suffix, production_date, reason, downtime_minutes, comment, shift) VALUES " _
& "('" & Me.Text116 & "','" & Me.Text118 & "','" & Me.Text126 & "','" & Me.Text121 & "','" & Me.Text123 & "','" & Me.Text128 & "','" & Me.Text144 & "');"
Call ClearControl(Me.Text116)
Call ClearControl(Me.Text118)
Call ClearControl(Me.Text126)
Call ClearControl(Me.Text121)
Call ClearControl(Me.Text123)
Call ClearControl(Me.Text128)
Call ClearControl(Me.Text144)
Me.subrpt_DowntimeTable.Requery
End Sub
The code I'm trying based on #Hambone's answer:
Public Sub Command125_Click()
Dim dbsCurrent As Database
Set dbsCurrent = CurrentDb
Dim query As QueryDef
Dim sql As String
For Each query In CurrentDb.QueryDefs
If query.Name = "InsertDowntime" Then
Exit For
End If
Next query
If query Is Nothing Then
sql = "parameters " & _
"P1 text, P2 text, P3 Date, P4 Text, P5 Number, P6 Text, P7 Text;" & _
"insert into [tbl_Downtime] " & _
"(job, suffix, production_date, reason, downtime_minutes, comment, shift) " & _
" VALUES ([P1], [P2], [P3], [P4], [P5], [P6], [P7])"
Set query = CurrentDb.CreateQueryDef("InsertDowntime", sql)
End If
query.Parameters("P1").Value = "test1"
query.Parameters("P2").Value = "test2"
query.Parameters("P3").Value = Now()
query.Parameters("P4").Value = "test3"
query.Parameters("P5").Value = 15
query.Parameters("P6").Value = "Miles O'Brien is a darn good transporter chief"
query.Parameters("P7").Value = "test6"
query.Execute
MsgBox query.Parameters("P1").Value & query.Parameters("P2").Value & query.Parameters("P3").Value & query.Parameters("P4").Value & query.Parameters("P5").Value & query.Parameters("P6").Value & query.Parameters("P7").Value
Me.subrpt_DowntimeTable.Requery
End Sub
MarkB and gmiley are absolutely right about using parameters. It's a little more code up front and countless hours saved later. And, it's a good practice to get into.
That said, for a native Access query (not a ADO Database query), it's not the most straight-forward process in the world. The normal ADO stuff, in my opinion, starts to make sense after you do it a time or two, but for an Access query, I still have to go back and plagiarize old examples to get it to work.
In your case, I think something like this will do the trick:
Dim query As QueryDef
Dim sql As String
For Each query In CurrentDb.QueryDefs
If query.Name = "InsertDowntime" Then
Exit For
End If
Next query
If query Is Nothing Then
sql = "parameters " & _
"P1 text, P2 text, P3 Date, P4 Text, P5 Number, P6 Text, P7 Text;" & _
"insert into [tbl_Downtime] " & _
"(job, suffix, production_date, reason, downtime_minutes, comment, shift) " & _
" VALUES ([P1], [P2], [P3], [P4], [P5], [P6], [P7])"
Set query = CurrentDb.CreateQueryDef("InsertDowntime", sql)
End If
query.Parameters("P1").Value = "test1"
query.Parameters("P2").Value = "test2"
query.Parameters("P3").Value = Now()
query.Parameters("P4").Value = "test3"
query.Parameters("P5").Value = 15
query.Parameters("P6").Value = "Miles O'Brien is a darn good transporter chief"
query.Parameters("P7").Value = "test6"
query.Execute
You were pulling your data from text-boxes. I used hard-coded values to demonstrate that this also manages data-typing if your values are not all text. No need to 'quote' text or #hash# dates. You can obviously changes these back to Me.TextBox123 and alter the data types to match your actual fields in tbl_Downtime.
-- edit 12/3/15 --
The entire section of code from For Each query In CurrentDb.QueryDefs all the way prior to query.Parameters could theoretically be omitted if you already had a query with this query text in it (as in you went into Access, created a query, went from Design view to SQL view and typed this in and named it InsertDowntime):
parameters
P1 text, P2 text, P3 Date, P4 Text, P5 Number, P6 Text, P7 Text;
insert into [tbl_Downtime]
(job, suffix, production_date, reason, downtime_minutes, comment, shift)
VALUES ([P1], [P2], [P3], [P4], [P5], [P6], [P7])
Because you don't, I just created that through code. If you tried to create it again, Access would puke because InsertDowntime already exists.
Either way, once it exists, the way you can manage it is by saying
Dim query As QueryDef
Set query = CurrentDb.QueryDefs("InsertDowntime")
And then everything else should be as I have it.
Personally, I would go option 2 -- create the query in Access and keep it as a persistent object and access it the way I just listed above. I suppose I could have said that, but yours was a VBA-ish question, and I kept it VBA -- plus, I thought the ability to create a query dynamically is sort of cool.
I would not build the SQL-Statement in the Execute-Method.
Create a variable and build the string first. So you can debug the actual value and test it seperately.
You can also try the Option dbFailOnError for the Execute-Method.
Dim stmt as String
stmt = "INSERT INTO....."
dbsCurrent.Execute stmt, dbFailOnError
Test the INSERT Statement with different inputs. It is not important what a normal user will enter in a form but what he is allowed to do by the application. If the input is not checked and the user can enter everything he/she wants's then the query can fail, produce weird results or - as stated - allows SQL injection. The best SQL injection is the one you as a developer will never notice. So you would'n know that you have a problem.
As per our discussion, you would want to look into parametrized queries: https://support.microsoft.com/en-us/kb/181734
The primary reasons for this are that 1) It will ensure your code is safe from intentional or unintentional SQL injection. Since user input cannot always be controlled in free-form fields, ensuring that your query is parametrized makes it impossible to execute arbitrary code; and 2) Readability, it makes it a lot easier to read code when the values are parametrized.
I have a form where a user selects a vendor's name from a combobox, whose catalog file is to be imported. The combobox selection then drives a query to create a one-record recordset (rsProfile) containing several profile variables queried from a table of all vendor profiles. These variables are then used in a series of different queries to reformat, translate and normalize the vendor's uniquely structured files to a standardized format that can be imported into our system.
I am frustrated that I can't figure out how to build my stored queries that will use one or more parameters that are automatically populated from the profile recordset.
Here is my rsProfile harvesting code. It works. Note that intVdrProfileID is a global variable set and used in other places.
Private Sub btn_Process_Click()
Dim ws As Workspace
Dim db, dbBkp As DAO.Database
Dim qdf As DAO.QueryDef
Dim rsProfile, rsSubscrip As Recordset
Dim strSQL As String
Dim strBkpDBName As String
Dim strBkpDBFullName As String
strBkpDBName = Left(strVdrImportFileName, InStr(strVdrImportFileName, ".") - 1) & "BkpDB.mdb"
strBkpDBFullName = strBkpFilePath & "\" & strBkpDBName
Set db = CurrentDb
Set ws = DBEngine.Workspaces(0)
MsgBox ("Vendor Profile ID = " & intVdrProfileID & vbCrLf & vbCrLf & "Backup file path: " & strBkpFilePath)
' Harvest Vendor Profile fields used in this sub
strSQL = "SELECT VendorID, Div, VPNPrefix, ImportTemplate, " & _
"VenSrcID, VenClaID, ProTyp, ProSeq, ProOrdPkg, ProOrdPkgTyp, JdeSRP4Code, " & _
"PriceMeth, " & _
"ProCost1Frml, ProCost2Frml, " & _
"ProAmt1Frml, ProAmt2Frml, ProAmt3Frml, ProAmt4Frml, ProAmt5Frml " & _
"FROM tZ100_VendorProfiles " & _
"WHERE VendorID = " & intVdrProfileID & ";"
Set qdf = db.QueryDefs("qZ140_GetProfileProcessParms")
qdf.SQL = strSQL
Set rsProfile = qdf.OpenRecordset(dbOpenSnapshot)
DoCmd.OpenQuery "qZ140_GetProfileProcessParms"
' MsgBox (qdf.SQL)
I have used QueryDefs to rewrite stored queries at runtime, and although it works, it is quite cumbersome and does not work for everything.
I was hoping for something like the sample below as a stored query using DLookups. I can get this to work in VBA, but I can't get anything to work with stored queries. I am open to other suggestions.
Stored Query "qP0060c_DirectImportTape":
SELECT
DLookUp("[VPNPrefix]","rsProfile","[VendorID]=" & intVdrProfileID) & [PartNo] AS VenPrtId,
Description AS Des,
DLookup("[Jobber]","rsProfile",[VendorID=" & intVdrProfileID) AS Amt1,
INTO tP006_DirectImportTape
FROM tJ000_VendorFileIn;
ADDENDUM:
Let me adjust the problem to make it a bit more complex. I have a collection of about 40 queries each of which use a different collection of parameters (or none). I also have a table containing the particular set of queries that each vendor 'subscribes' to. The goal is to have a database where a non-coding user can add new vendor profiles and create/modify the particular set of queries which would be run against that vendor file. I have almost 100 vendors so far, so coding every vendor seperately is not practical. Each vendor file will be subjected to an average of 14 different update queries.
Simplified Example:
Vendor1 file needs to be processed with queries 1, 2 and 5. Vendor2 file might need only update queries 2 and 4. The parameters for these queries might be as follows:
query1 (parm1)
query2 (parm1, parm4, parm8, parm11)
query4 (parm5, parm6, parm7, parm8, parm9, parm10, parm11)
query5 () -no parms required
This is the core query processing that loops through only the queries relevant to the current vendor file. rsSubscrip is the recordset (queried from a master table) containing this filtered list of queries.
' Run all subscribed queries
MsgBox "Ready to process query subscription list."
With rsSubscrip
Do While Not .EOF
db.Execute !QueryName, dbFailOnError
.MoveNext
Loop
.Close
End With
You can set the parameters of a predefined query using the syntax;
Set qdf = CurrentDB.QueryDefs(QueryName)
qdf.Parameters(ParameterName) = MyValue
To add parameters to the query, add the following before the SELECT statement in the sql
PARAMETERS [ParameterOne] DataType, [ParameterTwo] DataType;
SELECT * FROM tblTest;
I'm creating a database for a local rental company. What I'm trying to do right now is create some code that can be periodically run to add bills to the database.
I have two tables I'm referencing here, the first is tblRental which is a table of rental units which includes rentalID's as a primary key, rental status, and the monthly rent, which is different on each unit. The other is tblTransaction which among other things includes the rentalID as a foreign key in a 1 to many relationship, and the amount for each bill which is named UnitRate. In the database rentalID and UnitRate are both set to numbers.
My code right now is
Dim maxRentalID As Integer
Dim db As Database
Dim rentID As DAO.Recordset
Dim unit As DAO.Recordset
Set db = CurrentDb
maxRentalID = Combo4.Value
For i = 1 To maxRentalID
Set rentID = db.OpenRecordset("SELECT tblRental.RentalID, tblRental.Status FROM tblRental WHERE (((tblRental.Status)='3') AND ((tblRental.RentalID)=" & i & "));")
Set unit = db.OpenRecordset("SELECT tblRental.Rentalcharge FROM tblRental WHERE (((tblRental.RentalID)=" & i & "));")
If rentID Is Not Null Then
DoCmd.RunSQL "INSERT INTO tblTransction ([RentalID], [UnitRate]) VALUES (" & rentID, unit & ");"
End If
Next
maxRentalID is obtained from a form which determines the ID of the last record so that the for loop will go through until the end.
Right now I get a type mismatch error and it tells me the problem is with rentID in the DoCmd.RunSQL line.
I'm trying to make the loop run a query, and if it's a valid result (status=3 on a RentalID that exists), insert a new record into tblTransaction with that RentalID and corresponding billing rate.
Based on what the debugger says I assume that what's happening is the SQL isn't being evaluated into a statement with the query value being assigned to the variable and it's trying to put the statement into a number field which doesn't work. I'm not sure how to make it evaluate properly however. I've tried using OpenRecordSet for that but it doesn't work.
I would do it without loop and opening recordsets:
Dim maxRentalID As Integer
maxRentalID = Combo4.Value
CurrentDb.Execute "INSERT INTO [tblTransction] ([RentalID], [UnitRate]) " & _
"SELECT [tblRental].[RentalID], [tblRental].[Rentalcharge] " & _
"FROM [tblRental] " & _
"WHERE [tblRental].[Status]='3' AND " & _
"[tblRental].[RentalID] BETWEEN 1 AND " & maxRentalID