Named variables in SQL using ADODB in vba - sql

I have lots of SQL scripts, many of which use various different variables throughout, and I'd like to be able to drop the results directly into Excel. The hope is to do this as 'smoothly' as possible, so that when someone gives me a new SQL script (which may be relatively complicated), it is relatively clean to set up the spreadsheet that gathers its results.
Currently trying to get this working using ADODB Command objects parameters, but I can't even manage to get a very basic example to work. I have the following VBA:
Dim oConnection As ADODB.Connection
Set oConnection = New ADODB.Connection
oConnection.ConnectionString = "MyConnectionString"
oConnection.Open
Dim cmd As ADODB.Command
Set cmd = New ADODB.Command
cmd.ActiveConnection = oConnection
Up to here is just setting up the connection, which seems to work fine.
cmd.CommandText = "DECLARE #DateID integer;" & _
"SELECT TOP 10 * FROM dbo.SomeRecords " & _
"WHERE DateID = #DateID"
cmd.CommandType = adCmdText
Dim DateID As ADODB.Parameter
Set DateID = cmd.CreateParameter("#DateID", adInteger, adParamInput)
cmd.Parameters.Append DateID
DateID.Value = 20120831
Dim rst AS ADODB.RecordSet
Set rst = cmd.Execute()
ActiveSheet.Range("A1").CopyFromRecordset rst
Unfortunately, this doesn't return anything. However, if I replace the line:
"WHERE DateID = #DateID"
with this:
"WHERE DateID = 20120831"
Then the query returns exactly what you'd expect (the top 10 records from August 31), so obviously I'm not passing the value of the variable from VBA into SQL properly, but I have to admit that I'm pretty much stuck.
Certainly something is being passed into SQL (if I change the type of the variable #DateID to datetime in the SQL, then I get a SQL Server arithmetic overflow error, from trying to convert something to datetime), but it isn't doing what I was expecting.
I guess there are two questions: Is there a way to fix this code? Is there a better way of achieving the general goal described at the start?

Try this
cmd.CommandText = "DECLARE #DateID integer;" & _
"SET #DateID = ?DateID;" & _
"SELECT TOP 10 * FROM dbo.SomeRecords " & _
"WHERE DateID = #DateID"
.....
Set DateID = cmd.CreateParameter("?DateID", adInteger, adParamInput)
Re:
in which case why bother having names for them in the first place
Well, so that you can match them up as shown above. By all means use it many times, but use a sql-server local declare and set it there as shown.

Remove the DECLARE #DateID integer; from the SQL string.
ie:
cmd.CommandText = "SELECT TOP 10 * FROM dbo.SomeRecords " & _
"WHERE DateID = #DateID"

Parameters in an ADO parameterized query want a ? as placeholder so:
"SELECT TOP 10 * FROM dbo.SomeRecords WHERE DateID = ?"

Related

using sql to get data from excel sheets

I am using SQL to extract data from a worksheet. I have a sheet of sales data ("Sales"). The 1st three columns are SalesDate, Customer and CBand.
sample of my sheet
First, I get a list of distinct dates:
wb = ThisWorkbook.Path & "\" & ThisWorkbook.name
Set cn = CreateObject("ADODB.Connection")
With cn
.Provider = "Microsoft.ACE.OLEDB.12.0"
.ConnectionString = "Data Source=" & wb & ";" & "Extended Properties=""Excel 12.0 Xml;HDR=YES"";"
.Open
End With
sql = "SELECT distinct SalesDate FROM [Sales$] where CBand=3 order by SalesDate desc"
Set RS = cn.Execute(sql)
ct = 0
If Not RS.EOF Then
... etc
I save all the dates thus retrieved in another worksheet.
Then, using one of the dates I have saved, I again query the sales sheet:
(note, 'dt' is supplied as a parameter to this sub - eg "08/10/2019" or "05/10/2019" - the same format that the date is stored)
wb = ThisWorkbook.Path & "\" & ThisWorkbook.name
Set cn = CreateObject("ADODB.Connection")
With cn
.Provider = "Microsoft.ACE.OLEDB.12.0"
.ConnectionString = "Data Source=" & wb & ";" & "Extended Properties=""Excel 12.0 Xml;HDR=YES"";"
.Open
End With
sql = "SELECT distinct Customer FROM [Sales$] where SalesDate = #" & dt & "# and CBand=3"
output = output & sql & vbNewLine
Set RS = cn.Execute(sql)
ct = 0
If Not RS.EOF Then...
The second query will only work for certain dates. For example, using the sample portion of sheet attached, the 5th October returns no rows (the RS.EOF test fails), but the 8th October works fine, and I am able to loop through all the rows associated with that date. I have tried with several dates, most work, but there are a handful that don't, and I can't see why.
I am stumped!
If it comes to dates, you should deal with dates, not with representations of dates. 08/10/2019 is not a date - it's a string that might be seen as a date - but is it 8th of October or is it 10th of August?
If you have real dates in your sheet, they are stored as Double values and only shown to you in a way that you can see them as a date. That depends on the formatting of your cells and also on regional settings of the computer. So 8th of October is stored as number 43746, and if you add 0.5, it's noon of 8th October. You might see it as 08/10/2019, or as 08-Oct-2019 or as 08. Oktober 2019 - possibilities are endless, but the value itself stays the same.
If you want to query dates, you should pass a date parameter to the query. If you pass #" & dt & "#, you are passing a string. if dt is a Date-variable, VBA converts the date to a string and the SQL parser tries to convert it back to a date - not necessarily in the same way. Of course you also don't want to pass 43746 as parameter. You don't mind about how a date is stored, and if you query a different datasource (eg Oracle, SQL Server...), they will store the date in a different format.
The best way to solve this problem is to use ADODB.Parameter. It's a little bit more coding because you cannot pass a ADODB.Parameter to the Execute-method of the Connection-object, you have to involve a ADODB.Command-object, but once you are used to it, you will enjoy the benefits: You no longer have to take care about formatting, don't need to put a string-parameter into quotes and so on. Plus, it prevents SQL injection.
Have a look to the following code (as you can see, I prefer to use early binding, but of course this works also when you switch to late binding)
Dim cn As ADODB.Connection, cmd As ADODB.Command, param As ADODB.Parameter
Set cn = New ADODB.Connection
With cn
.Provider = "Microsoft.ACE.OLEDB.12.0"
.ConnectionString = "Data Source=" & ThisWorkbook.FullName & ";" & "Extended Properties=""Excel 12.0 Xml;HDR=YES"";"
.Open
End With
Dim sql As String
sql = "SELECT * FROM [Sheet1$] where SalesDate = ?" ' The ? is a placeholder for a parameter
' Create command
Set cmd = New ADODB.Command
Set cmd.ActiveConnection = cn
cmd.CommandText = sql
' Create a parameter of type Date and pass the date value
Set param = cmd.CreateParameter("p1", adDate, adParamInput, , dt)
cmd.Parameters.Append param
Dim rs As ADODB.Recordset
Set rs = cmd.Execute
With this, the query runs with a real Date (there are other parameter types for numbers, Strings etc).
Now, there is another aspect when it comes to SQL queries involving dates: A date can contain a time part, which is stored as the fraction part of the double. If you're searching for a date and you want to include date values from the whole day, you could change your code to
sql = "SELECT * FROM [Sheet1$] where SalesDate >= ? and SalesDate < ?"
' Now we have to provide two parameters:
Set param = cmd.CreateParameter("p1", adDate, adParamInput, , dt)
cmd.Parameters.Append param
' Second parameter is the next day - a little lazy, you can deal with DateAdd instead
Set param = cmd.CreateParameter("p2", adDate, adParamInput, , dt+1)
cmd.Parameters.Append param

"Data type mismatch in criteria expression" when building SQL statement from user input

I'm trying to compare a date from user input with a date stored in an Access database, using ASP Classic.
The user inputs a date (SearchedStartDate), which is submitted through a form, and then my SQL statement should select records where the start date field is more recent than the date the user inputted.
The Access database field is a Date/Time data type, and my SQL statement is like:
SELECT SessionID, StartDate
FROM SessionTable
WHERE StartDate>='"&SearchedStartDate&"'
ORDER BY StartDate DESC"
I've searched around and tried lots of different things, such as using # signs around the date for Access, and SearchedStartDate = FormatDateTime(SearchedStartDate, vbShortDate), but all my attempts result in a "Data type mismatch in criteria expression" error.
How can I get this to work?
As you've "searched around" you must have seen warnings that Dynamic SQL is widely regarded as a bad thing. The correct approach is to use a parameterized query, for example,
Const adDBTimeStamp = 135
Const adParamInput = 1
Dim SearchedStartDate
SearchedStartDate = "2018-01-01" ' test data
Dim cmd
Set cmd = CreateObject("ADODB.Command")
cmd.ActiveConnection = con ' currently open ADODB.Connection
cmd.CommandText = _
"SELECT SessionID, StartDate " & _
"FROM SessionTable " & _
"WHERE StartDate >= ? " & _
"ORDER BY StartDate DESC"
cmd.Parameters.Append cmd.CreateParameter("?", _
adDBTimeStamp, adParamInput, , CDate(SearchedStartDate))
Dim rst
Set rst = cmd.Execute ' ADODB.Recordset
Do Until rst.EOF
' work with the values in the current row of the Recordset
rst.MoveNext
Loop
It should read like this:
"SELECT SessionID, StartDate
FROM SessionTable
WHERE StartDate >= #" & SearchedStartDate & "#
ORDER BY StartDate DESC"
where SearchedStartDate should be a string of the format yyyy/mm/dd to express your date value. If Format is available, you can do:
SearchedStartDate = Format(YourDateValue, "yyyy\/mm\/dd")

Data type mismatch on SQL Query in VBA

I am trying to do an SQL query in VBA to retun a specific case number. Whenever I execute the query, it returns an error of "Data Type Mismatch in Criteria Expression". I am passing the query an integer to use to query an autonumber primary key.
Dim c As ADODB.Connection
Dim r As ADODB.Recordset
Dim strSQL As String, strManager As String
Set c = New ADODB.Connection
c.Open "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=Commit Tracker.accdb; Persist Security Info=False;"
strSQL = "SELECT * FROM CommitTrk WHERE CASE_ID_NBR = '" & CInt(frmCommitViewer.lstCases.Value) & "'"
Set r = c.Execute(strSQL)
Of course the debug hilights the execute command. Any help would be appreciated. Am I passing the wrong datatype to match the autonumber? If so, what datatype should I be using? Thanks!
if CASE_ID_NBR has numeric type, you should use it without quotes:
strSQL = "SELECT * FROM CommitTrk WHERE CASE_ID_NBR = " & CInt(frmCommitViewer.lstCases.Value)
you may also want to read this: Global Variables in SQL statement

SQL Variables not working in Excel Query

Hi I have a query that can not be represented graphically in Excel External SQL Server Query, because the query is not simple I can not set Excel Variables. Instead I try to set variables in the SQL using declare/set however when doing this it fails when trying to import the data.
Is there a way to get around this? I need a level of flexibility for running the query and piping the data into Excel..
Okay so here is an example as requested.
declare #enddate as datetime
set #enddate = (select max(rpt_week) from [results].dbo.t_Data)
Select * from results.dbo.t_Data
where rpt_week = #enddate
This will not run in excel, obviously my query is more complex but the use of variables is essential and I am looking for a way to have this work.
Thanks
When using declare values in Excel, you need to SET NOCOUNT ON
your sample would be as follows:
SET NOCOUNT ON;
declare #enddate as datetime
set #enddate = (select max(rpt_week) from [results].dbo.t_Data)
Select * from results.dbo.t_Data
where rpt_week = #enddate
There is another question on this subject which has a correct answer:
Use SET NOCCOUNT ON in your query.
I encountered the same problem as the OP when I tried to run an SP from Excel, which was supposed to return data from an embedded SELECT. It was caused by a ... row(s) affected message being returned from the server. Apparently, Excel can't deal with this message correctly, and the returned data is ignored. Once I used SET NOCOUNT ON in my SP, the data was displayed correctly in Excel.
I am not quite sure what you wish, but here are some notes:
Dim cn As New ADODB.Connection
Dim rs As New ADODB.Recordset
Dim cmd As New ADODB.Command
cn.Open ServerCon ''Connection string
''Straight sql
sSQL = "SELECT * FROM table_1 WHERE adate = (SELECT Max(adate) FROM table_1)"
rs.Open sSQL, cn
''Not a good idea, see procedure parameter below for a better way
sSQL = "SELECT * FROM table_1 WHERE adate = '" _
& Sheets("Sheet7").Range("B2") & "'"
rs.Open sSQL, cn
''Stored procedure
rs.Open "ExcelTest", cn
''Stored procedure with parameter
cmd.ActiveConnection = cn
cmd.CommandType = adCmdStoredProc
cmd.CommandText = "ExcelTest"
cmd.Parameters("#enddate") = Sheets("Sheet7").Range("B2")
Set rs = cmd.Execute
''Write to Excel
ActiveWorkbook.Sheets("Sheet12").Cells(1, 1).CopyFromRecordset rs
You can also use Query Tables.

How to return the value in one field based on lookup value in another field

This is basic stuff, but I'm somewhat unfamiliar with VBA and the Word/Access object models.
I have a two column database of about 117000 records. The columns are 'surname' and 'count'. I want a user to be able to type SMITH in a textbox and hit submit. I then want to run something like
SELECT table.count FROM table WHERE surname = string
and return the value of table.count in a string.
It feels like this should be five or six lines of code (which I have but won't post) but I'm obviously missing something!
Cheers
First of all, be careful naming the column 'count' -- this is a keyword in SQL and might cause problems. Similarly, don't call the table 'table'.
Here is some sample code which shows one way of doing it:
' This example uses Microsoft ActiveX Data Objects 2.8,
' which you have to check in Tools | References
' Create the connection. This connection may be reused for other queries.
' Use connectionstrings.com to get the syntax to connect to your database:
Dim conn As New ADODB.Connection
conn.Open "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=c:\tmp\Database1.accdb"
Dim cmd As New ADODB.Command
Set cmd.ActiveConnection = conn
' Replace anything which might change in the following SQL string with ?
cmd.CommandText = "select ct from tbl where surname = ?"
' Create one parameter for every ?
Dim param As ADODB.Parameter
Set param = cmd.CreateParameter("surname", adBSTR, adParamInput, , TextBox1.Text)
cmd.Parameters.Append param
Dim rs As ADODB.Recordset
Set rs = cmd.Execute
MsgBox rs("ct")
rs.Close
conn.Close
It is possible to use InsertDatabase:
Sub GetData()
ActiveDocument.Bookmarks("InsertHere").Select
Selection.Range.InsertDatabase Format:=0, Style:=0, LinkToSource:=False, _
Connection:="TABLE Members", SQLStatement:= _
"SELECT [Count] FROM [Members]" _
& " WHERE Surname='" _
& ActiveDocument.FormFields("Text1").Result & "'", _
DataSource:="C:\docs\ltd.mdb", From:=-1, To:= _
-1, IncludeFields:=True
End Sub
This is an edited macro recorded using the database toolbar.
EDITED Warning: this code, as shown, is subject to a SQL Injection attack.