Parameterized Queries in VBA + ADODB + Oracle - vba

I'm new to working with Oracle 11g and I'm having a lot of issues getting a parameterized query to work smoothly.
this code works:
Dim rs As ADODB.Recordset
Dim con As ADODB.Connection
Dim cmd As ADODB.Command
Dim prm As ADODB.Parameter
Set con = New ADODB.Connection
With con
.ConnectionString = GetConnection() '<-- the driver here is Driver={Oracle in OraClient11g_home1_32bit}
.Open
End With
Set cmd = New ADODB.Command
With cmd
Set .ActiveConnection = con
.CommandType = adCmdText
.CommandText = "SELECT * FROM MPA_LINEPLAN.REF_BRAND_SEASON_DROP WHERE DROP_ID = ?"
Set prm = .CreateParameter("dropID", adVarChar, adParamInput, 50, "P_SP19_5")
.Parameters.Append prm
Set rs = .Execute
End With
But the actual query I want to run will reference the dropID parameter several times. To get it working, I would have to add the same paramerter over and over. Tell me there's a better way? I tried the following:
With cmd
Set .ActiveConnection = con
.CommandType = adCmdText
.CommandText = "SELECT * FROM MPA_LINEPLAN.REF_BRAND_SEASON_DROP WHERE DROP_ID = :dropID"
Set prm = .CreateParameter("dropID", adVarChar, adParamInput, 50, "P_SP19_5")
.Parameters.Append prm
Set rs = .Execute
End With
But it hits unspecified error when I try to execute into the rs.
Also, assume for my particular case, that stored procs is not the best option (even though it should be the best option :-/)
edit:
The actual query is very long and in the interest of not making you hunt down all the :dropID references, I've reduced it here, but left enough to show the multiple references.
WITH
--...
DropDim AS (
SELECT DROP_ID
, DROP_NAME
, SEASON_ID
, SEASON_NAME
, BRAND_ID
, SEASON_YEAR
, 'DROP_' || substr(DROP_ID, LENGTH(DROP_ID),1) AS LP_Join_Drop
, SEASON_NAME || '_' || SEASON_YEAR AS LP_Join_Season
FROM MPA_LINEPLAN.REF_BRAND_SEASON_DROP
WHERE DROP_ID = :dropID),
--...
LYMap AS
(SELECT DC.DROP_ID
, DC.CHANNEL_ID
, BSD.SEASON_YEAR
, BSD.SEASON_NAME
, BSD.DROP_NAME
, FW.WEEKENDINGDATE AS LY_WEEKENDING_DATE
, FW.YEARWEEK AS LY_YEARWEEK
FROM MPA_LINEPLAN.REF_DROP_CHANNEL DC
INNER JOIN MPA_MASTER.FISCALWEEK FW
ON FW.YEARWEEK BETWEEN DC.LY_START_DT AND DC.LY_END_DT
INNER JOIN MPA_LINEPLAN.REF_BRAND_SEASON_DROP BSD ON BSD.DROP_ID = dc.DROP_ID
WHERE DC.DROP_ID = :dropID),
LLYMap AS
(SELECT DC.DROP_ID
, DC.CHANNEL_ID
, BSD.SEASON_YEAR
, BSD.SEASON_NAME
, BSD.DROP_NAME
, FW.WEEKENDINGDATE AS LLY_WEEKENDING_DATE
, FW.YEARWEEK AS LLY_YEARWEEK
FROM MPA_LINEPLAN.REF_DROP_CHANNEL DC
INNER JOIN MPA_MASTER.FISCALWEEK FW
ON FW.YEARWEEK BETWEEN DC.LLY_START_DT AND DC.LLY_END_DT
INNER JOIN MPA_LINEPLAN.REF_BRAND_SEASON_DROP BSD ON BSD.DROP_ID = dc.DROP_ID
WHERE DC.DROP_ID = :dropID ),
--....

Continue to use the qmarks placeholder and simply use a for loop to append same parameter object. Specifically, qmarks correspond to the position placed in query. Assuming the below query
sql = "SELECT * FROM MPA_LINEPLAN.REF_BRAND_SEASON_DROP" _
& " WHERE DROP_ID = ? AND DROP_ID2 = ? AND DROP_ID3 = ?"
With cmd
Set .ActiveConnection = con
.CommandType = adCmdText
.CommandText = sql
For i = 1 To 3 ' ADJUST TO NUMBER OF PARAMS
Set prm = .CreateParameter("prm" & i, adVarChar, adParamInput, 50, "P_SP19_5")
.Parameters.Append prm
Next i
Set rs = .Execute
End With
Alternatively, turn your query into a stored procedure (avoiding very large SQL string or text file read in VBA), then define one parameter.
Oracle
CREATE OR REPLACE PROCEDURE my_procedure_name(dropID IN VARCHAR2) IS
BEGIN
...long query using dropID (without any symbol)...
END;
/
VBA
With cmd
Set .ActiveConnection = con
.Properties("PLSQLRSet") = TRUE
.CommandType = adCmdText
.CommandText = "{CALL my_procedure_name(?)}"
Set prm = .CreateParameter("prm", adVarChar, adParamInput, 50, "P_SP19_5")
.Parameters.Append prm
Set rs = .Execute
End With

The best solve for this was simply to stop using Oracle's ODBC driver and start using Oracle's OLEDB as a provider instead.
Old connection string:
.ConnectionString = "Driver={Oracle in OraClient11g_home1_32bit};UID=MyUname;PWD=MyPW;DBQ=MyDB;"
New connection string:
.ConnectionString = "Provider=OraOLEDB.Oracle;Data Source=MyDB; User ID=MyUname;Password=MyPW;"
OraOLEDB supports named parameters, which is exactly what I was trying to get to in the first place. Now, I can reference parameter names in the SQL statement with : prefix.
With cmd
Set .ActiveConnection = con
.CommandType = adCmdText
.CommandText = "SELECT * FROM MPA_LINEPLAN.REF_BRAND_SEASON_DROP WHERE DROP_ID =:dropID"
Set prm = .CreateParameter("dropID", adVarChar, adParamInput, 50, "P_SP19_5")
.Parameters.Append prm
Set rs = .Execute
End With
This now works!

Another approach is to use a CTE to have all your scalar parameters and then join back to the table you are interested in:
-- Outer SELECT * due to limitations of ODBC drivers in VBA
SELECT *
FROM
(
WITH lu as (
SELECT ? as drop_id
FROM dual
)
)
SELECT t1.*
FROM mpa_lineplan.ref_brand_season_drop t1
CROSS JOIN lu -- could be inner join or whatever type you are interested in
WHERE t1.drop_id = lu.drop_id
)

Related

Access "too few parameters" or crash but only before compact and repair

Bit of an odd one here, essentially I have a VBA function in Microsoft Access that takes two arguments provided and cuts the data from the existing table to a temp table, and then compares this to the latest data from an external an SQL database and then reappends the updated information.
This has worked fine for years, and has never been touched, until recently, everytime I run the function I get an error:
Run-time error '-2147217904(8004e10)':
Too few parameters. Expected 2.
However, if I manually compact and repair, or recompile the database, this error goes away and the function completes as normal. But only for that session, currently the staff that use this function have to compact and repair everytime they open the Acces front end to make the function complete. Compact and repair on close does not work.
The code is below, but again, it has worked as-is for year with no changes and works after a C&R.
Function AccCompleteOrder(LabID As String, OrderID As String) As Boolean
Dim cmd As New ADODB.Command
Dim conn As New ADODB.Connection
Dim rstPackageCount As ADODB.Recordset
Dim rstTmpData As ADODB.Recordset
Dim rstRealData As New ADODB.Recordset
Dim i As Integer
Dim params() As Variant
'set up cmd and query parameters
params = Array(LabID, OrderID)
Set conn = CurrentProject.Connection
cmd.ActiveConnection = conn
'check that packages have been added to the order in genophyle
cmd.CommandText = "qryAccCheckPackagesAdded"
cmd.CommandType = adCmdStoredProc
cmd.Parameters.Refresh
Set rstPackageCount = cmd.Execute(, params)
If (rstPackageCount("packageCount") = 0) Then
AccCompleteOrder = False
Exit Function
End If
'Move dummy records to Temp table
If TableExists("tmpTblAccDeletion") Then
DoCmd.DeleteObject acTable, "tmpTblAccDeletion"
End If
cmd.CommandText = "qryAccMoveToTemp"
cmd.CommandType = adCmdStoredProc
cmd.Parameters.Refresh
cmd.Execute , params
'delete old lines from table
cmd.CommandText = "qryAccDeleteFromWIL"
cmd.CommandType = adCmdStoredProc
cmd.Parameters.Refresh
cmd.Execute , params
'Append real data from Genophyle orders
cmd.CommandText = "qryAccAppendfrmGenophyle"
cmd.CommandType = adCmdStoredProc
cmd.Parameters.Refresh
cmd.Execute , params
'Get tempData recordset
cmd.CommandText = "qryAccSelectTmpData"
cmd.CommandType = adCmdStoredProc
cmd.Parameters.Refresh
Set rstTmpData = cmd.Execute
'Get real Data (from WIL) dataset
cmd.CommandText = "qryAccSelectRealData"
cmd.CommandType = adCmdStoredProc
cmd.Parameters.Refresh
cmd.Parameters.Append cmd.CreateParameter("LabID", adChar, , 10, LabID)
cmd.Parameters.Append cmd.CreateParameter("OrderID", adBigInt, , 10, OrderID)
rstRealData.Open cmd, , adOpenDynamic, adLockOptimistic
Do While Not rstRealData.EOF
rstRealData("Country") = rstTmpData("Country")
Do While Not rstTmpData.EOF
If (rstRealData.Fields("Platform") = rstTmpData.Fields("Platform")) Then
For i = 0 To rstRealData.Fields.Count - 1
If (IsNull(rstRealData.Fields(i)) Or rstRealData.Fields(i) = 0) Then
rstRealData.Fields(i) = rstTmpData.Fields(i)
End If
Next
End If
rstTmpData.MoveNext
Loop
rstTmpData.MoveFirst
rstRealData.Update
rstRealData.MoveNext
Loop
rstRealData.Close
'update the accessioning check table
cmd.CommandText = "qryAccUpdateAccessioningCheckOrderComplete"
cmd.Parameters.Refresh
cmd.Parameters.Append cmd.CreateParameter("LabID", adChar, , 10, LabID)
cmd.Parameters.Append cmd.CreateParameter("OrderID", adBigInt, , 10, OrderID)
cmd.Parameters.Append cmd.CreateParameter("ComDate", adChar, , 50, Format(Now(), "dd/mm/yyyy hh:MM:ss"))
cmd.Parameters.Append cmd.CreateParameter("ComCheck", adBoolean, , , True)
cmd.Execute
AccCompleteOrder = True
End Function
The debugger indicates the error is when it reaches the line
rstRealData.Open cmd, , adOpenDynamic, adLockOptimistic
This has me stumped.

How to display ADO recordset in Access 2010

I need some help solving this issue. I'm a VB novice, by the way. I currently have a stored procedure that accepts 3 parameters and when executed returns a temp table. I tested my procedure in SQL and it's working as expected.
I'm using an Access form as my front-end. It is to accept the 3 parameters via 3 text box controls and then executes my procedure 'on click' (User clicks the search button to execute and fire up SQL). The code below accomplishes this, up until it's time to display my recordsets in a table. I'm stuck on how to display my results from my procedure in a table by utlizing ADO properties I'm ok with either displaying the recordset in a datasheet form or inserting them into a table.
The code seems to choke up at this line, where I'm attempting to view my recordset:
rs.Open cmd
Access VB Code
Private Sub cmdExecuteQuery_Click()
Dim conn As ADODB.Connection
Dim cmd As ADODB.Command
Dim rs As ADODB.Recordset
Dim accessionno As String
Set conn = New ADODB.Connection
Set rs = New ADODB.Recordset
conn.ConnectionString = "Driver={SQL Server};Server=***;Database=***;Uid=****;Pwd=***;"
conn.Open
Set cmd = New ADODB.Command
cmd.ActiveConnection = conn
cmd.CommandType = adCmdStoredProc
cmd.CommandText = "dbo.NameofStoredProcedure"
cmd.Parameters.Append cmd.CreateParameter("#worksheetno", adVarChar,
adParamInput, 10, Forms!RepeatForm!txtEnterWrkSheetno)
cmd.Parameters.Append cmd.CreateParameter("#name", adVarChar,
adParamInput, 10, Forms!RepeatForm!txtEnterName)
cmd.Parameters.Append cmd.CreateParameter("#year", adVarChar,
adParamInput, 4, Forms!RepeatForm!txtEnterYear)
With rs
.ActiveConnection = conn
.CursorType = adOpenForwardOnly
.CursorLocation = adUseServer
End With
Set rs = cmd.Execute()
DoCmd.OpenForm "frmRepeats", acViewNormal
rs.Open cmd
rs.Close
conn.Close
Set rs = Nothing
Set cmd = Nothing
Set conn = Nothing
End Sub
SQL
CREATE PROCEDURE [dbo].[NameofStoredProcedure] #worksheetno varchar(10),
#name varchar(10), #year varchar(4)
AS
BEGIN
CREATE TABLE #Temp (accessionno varchar (100),
No varchar (100),
worksheetno varchar (10),
name varchar(100),
location varchar (10),
Result varchar (100),
year varchar (4))
INSERT INTO #Temp
SELECT DISTINCT
a.accessionno,
e.name + substring(a.Year, 3,2) + '-' + convert(varchar(10),
c.SampleTestNum) AS No,
a.worksheetno,
b.name,
c.platetext AS Location,
d.finalcomment AS Result,
a.Year
FROM
tb_sample a (nolock), tb_patient b (nolock), tb_plate c (nolock),
tb_finalresult d (nolock), tb_testcode e (nolock)
WHERE
a.sample_id = b.sample_id
and a.sample_id = c.sample_id
and a.sample_id = d.sample_id
and a.test_id = e.test_id
and finalcomment like '%1061%'
and worksheetno = #worksheetno
and e.name = #name
and a.year = #year
ORDER BY No
END
select distinct * from #Temp
drop table #Temp
GO

Update Sqlite table values from VBA created array

I'm trying to UPDATE a table column named "OrigMask" in myfile.sqlite by using an array created in my VBA code. I'm struggling with the syntax. The VBA array is named "NewMask" and has 864 elements as does the table "OrigMask". How do I create the sql statement in VBA and execute. Help is VERY much appreciated !
I'm establishing a statement & connection like so:
Set conn = CreateObject("ADODB.Connection")
Set rst = CreateObject("ADODB.Recordset")
conn.Open "DRIVER=SQLite3 ODBC Driver;Database=C:\myfile.sqlite;"
strSQL1 = "UPDATE MyTable SET OrigMask= NewMask;"
rst.Open strSQL1, conn
Set rst = Nothing: Set conn = Nothing
Several issues here:
You need to iterate through you array and not integrate whole object in SQL as NewMask is unrecognized identifier in database.
An update query is an action query and not a resultset to be retrieved in a recordset.
You need a WHERE clause in UPDATE or all records are update not row by row.
Therefore, consider a For Each loop through a parameterized ADO command object:
strSQL1 = "UPDATE MyTable SET OrigMask = ? WHERE id = ?;"
i = 1
For Each var In NewMask
Set cmd = CreateObject("ADODB.Command")
With cmd
.ActiveConnection = conn
.CommandText = strSQL1
.CommandType = adCmdText
.CommandTimeout = 15
.Parameters.Append .CreateParameter("maskparam", adVarChar, adParamInput, 255, var)
.Parameters.Append .CreateParameter("idparam", adInt, adParamInput, , i)
.Execute
End With
i = i + 1
Next var

VBA, ADO.Connection and PostgreSQL how to pass query parameter into pg_typeof()

I have been trying to get datatype of a column by passing the column name as parameter in following code part. But my query returns "unknown" value instead of defined datatypes like character varying, integer, text, etc. If I give paramater value directly into SQL query instead of question mark (?), it returns what I want to see. ("SELECT pg_typeof(haendler_id) from t_haendler limit 1;"). How can I handle this problem?
Sub PGTYPEOF()
Dim CN As New ADODB.Connection
Dim RS As New ADODB.Recordset
Dim CM As New ADODB.Command
Dim PM1 As New ADODB.Parameter
Dim strConn As String
Dim i, ItemVal As Variant
strConn = "Driver={PostgreSQL ODBC Driver(Unicode)};DSN=postgres;Server=localhost;
Port=5432;UID=postgres;PWD=###; Database=###;READONLY=0;PROTOCOL=6.4;FAKEOIDINDEX=0;
SHOWOIDCOLUMN=0; ROWVERSIONING=0;SHOWSYSTEMTABLES=1"
CN.Open strConn
i = "haendler_id"
With CM
.ActiveConnection = CN
.CommandText = "SELECT pg_typeof(?) from t_haendler limit 1;"
.CommandType = adCmdText
Set PM1 = .CreateParameter(, adVarChar, adParamInput, 30)
PM1.Value = i
.Parameters.Append PM1
Set RS = .Execute
End With
Do While Not RS.EOF
'Debug.Print RS.Fields(0).Value
ItemVal = RS.Fields(0).Value
RS.MoveNext
Loop
End Sub
I do not know why but pg_typeof() function does not function. I solved my problem changing my sql query as follows "select data_type from information_schema.columns where table_name = ? and column_name = ?;"
With CM
.ActiveConnection = CN
.CommandText = "select data_type from information_schema.columns where
table_name = ? and column_name = ?;"
.CommandType = adCmdText
Set PM1 = .CreateParameter(, adVarChar, adParamInput, 30)
PM1.Value = i
.Parameters.Append PM1
Set PM2 = .CreateParameter(, adVarChar, adParamInput, 30)
PM2.Value = j
.Parameters.Append PM2
Set RS = .Execute
End With

Problem with passing parameters to SQL procedure using VBA

I am trying to pass #intDocumentNo and #intCustomerNo to a stored procedure using VBA but only #intCustomerNo is updated in dbo.tblOrderHead. I don't think the problem is with the procedure because if I enter the values manually it runs properly.
What am I doing wrong?
VBA Code:
Private Sub intCustomerNo_Click()
Dim cmdCommand As New ADODB.Command
With cmdCommand
.ActiveConnection = CurrentProject.Connection
.CommandType = adCmdStoredProc
.CommandText = "uspSelectCustomer"
'#intDocumentNo
.Parameters("#intDocumentNo").Value = Forms![frmSalesOrder].[IntOrderNo]
'#intCustomerNo
.Parameters("#intCustomerNo").Value = Me![intCustomerNo]
.Execute
End With
DoCmd.Close
Forms![frmSalesOrder].Requery
End Sub
Procedure:
UPDATE dbo.tblOrderHead
SET dbo.tblOrderHead.intCustomerNo = #intCustomerNo ,
dbo.tblOrderHead.intPaymentCode = dbo.tblCustomer.intPaymentCode,
dbo.tblOrderHead.txtDeliveryCode = dbo.tblCustomer.txtDeliveryCode,
dbo.tblOrderHead.txtRegionCode = dbo.tblCustomer.txtRegionCode,
dbo.tblOrderHead.txtCurrencyCode = dbo.tblCustomer.txtCurrencyCode,
dbo.tblOrderHead.txtLanguageCode = dbo.tblCustomer.txtLanguageCode
FROM dbo.tblOrderHead
INNER JOIN dbo.tblCustomer ON dbo.tblOrderHead.intCustomerNo =
dbo.tblCustomer.intCustomerNo
AND dbo.tblOrderHead.intOrderNo = #intDocumentNo
Solution
Change the procedure to this (suggestion below might work as well, but I have not yet tested):
UPDATE dbo.tblOrderHead
SET dbo.tblOrderHead.intCustomerNo = #intCustomerNo
WHERE dbo.tblOrderHead.intOrderNo = #intDocumentNo;
UPDATE dbo.tblOrderHead
SET dbo.tblOrderHead.intPaymentCode = dbo.tblCustomer.intPaymentCode,
dbo.tblOrderHead.txtDeliveryCode = dbo.tblCustomer.txtDeliveryCode,
dbo.tblOrderHead.txtRegionCode = dbo.tblCustomer.txtRegionCode,
dbo.tblOrderHead.txtCurrencyCode = dbo.tblCustomer.txtCurrencyCode,
dbo.tblOrderHead.txtLanguageCode = dbo.tblCustomer.txtLanguageCode
FROM dbo.tblOrderHead
INNER JOIN dbo.tblCustomer ON dbo.tblOrderHead.intCustomerNo =
dbo.tblCustomer.intCustomerNo
AND dbo.tblOrderHead.intOrderNo = #intDocumentNo
Try using SET NOCOUNT OFF in your SQL sp.
When an update or insert runs, it returns information on the amount of rows affected (like when you run in SSMS).
When an adodb.command recieves this information it stops executing, hence only executing your first statement.
You could try creating Parameter objects, then appending them to the Parameters collection.
The following is untested.
Private Sub intCustomerNo_Click()
Dim cmdCommand As New ADODB.Command
Dim paramDocNo as ADODB.Parameter
Dim paramCustNo as ADODB.Parameter
Set paramDocNo = cmdCommand.CreateParameter("#intDocumentNo", adInteger, adParamInput)
Set paramCustNo = cmdCommand.CreateParameter("#intCustomerNo", adInteger, adParamInput)
cmdCommand.Parameters.Append paramDocNo
cmdCommand.Parameters.Append paramCustNo
paramDocNo.Value = Forms![frmSalesOrder].[IntOrderNo]
paramCustNo.Value = Me![intCustomerNo]
With cmdCommand
.ActiveConnection = CurrentProject.Connection
.CommandType = adCmdStoredProc
.CommandText = "uspSelectCustomer"
.Execute
End With
DoCmd.Close
Forms![frmSalesOrder].Requery
End Sub
Could it possibly but you not declaring the parameters and it using an old cached copy? Personally when I issue parameters I use something like this. I’m not saying that is what is causing the problem but try it this way and see if it helps
Set cmd = New ADODB.Command
With cmd
.CommandText = "sptblTest_answers_UPSERT"
.CommandType = adCmdStoredProc
.ActiveConnection = dbCon
.Parameters.Append .CreateParameter("#Answer_ID", adInteger, adParamInput, , Me.txtAnswer_ID)
.Parameters.Append .CreateParameter("#Question_ID", adInteger, adParamInput, , Me.txtQuestion_ID)
.Parameters.Append .CreateParameter("#Answer_number", adTinyInt, adParamInput, , Me.txtAnswer_number)
.Execute
End with