using sql to get data from excel sheets - sql

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

Related

How to write a query that uses a number as parameter and number type field?

I need to start a query to retrieve data from Access database using VBA which I want to use a variable number as a parameter. Is it possible?
like the:
field name: NMT field type (number)
table name: Orders
and the code is like the following:
Dim Con As New ADODB.Connection
Dim RS As New ADODB.Recordset
Dim X as Integer
X = me.textbox1.value
Con.Open "Provider= Microsoft.ACE.OLEDB.12.0;Data Source=" & U.Database01 & "\DB.accdb;Persist Security Info=False"
Rs.Open "select * from Orders where nmt = '" + X + "'", Con, adOpenDynamic, adLockPessimistic
Whenever I run this query, I get a run-time error '13' type mismatch.
Any suggestions ?
Multiple Issues
Type-mismatch in WHERE clause:
Your query (i.e. the WHERE clause) tries to compare a Number-column from database with a String-value (e.g. WHERE numberField = '123'). This will result in a runtime error Type mismatch (Error 13). See also similar question.
Unsafe to use + to concatenate Strings
When building the query you tried to concatenate the query-template with the number-parameter by a plus-sign. This works only when operating on numbers. See related question
Solution
remove single-quotes: you should compare the Number-column NMT with a number literal (e.g. WHERE nmt = 123)
use & to concatenate strings. This will also convert numbers to strings. Besides I explicitly used CStr function below.
Dim Con As New ADODB.Connection
Dim RS As New ADODB.Recordset
Dim strSQL As String
Dim nmtNumber as Integer ' you named it x before
nmtNumber = me.textbox1.value
strSQL = "SELECT * FROM Orders WHERE nmt = " & CStr(nmtNumber) ' removed single-quotes and used ampersand to concatenate with converted string
Con.Open "Provider= Microsoft.ACE.OLEDB.12.0;Data Source=" & U.Database01 & "\DB.accdb;Persist Security Info=False"
RS.Open strSQL, Con, adOpenDynamic, adLockPessimistic
Further improvement
I already extracted the SQL string (building) into a separate variable strSQL above.
Better would be to use predefined/prepared and parameterized queries:
QueryDef (DAO) where you can set the parameters (type-safe). See this question.
Command (ADODB) where you can set parameters (type-safe). See this question.
See also
What is ‘Run-time error ‘13’: Type mismatch’? And How Do You Fix It?
VBA Type Mismatch Error

"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")

Named variables in SQL using ADODB in vba

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 = ?"

update 2 fields in Access database with Excel data and probably a Macro

In my master database I have a small table that contains two dates and ID numbers. I use this table for updating queries and do some analysis. The table looks like this:
Number | Date
1 | 09.07.2012.
2 | 10.07.2012.
The thing I would like to do is to have an excel file that pops-up a form after startup.
That form should contain 2 fields and 2 buttons. In those fields I input 2 dates (with date picker or whatever) and with 1st button I update mentioned table in Access with fresh data (delete old dates and update with new ones) and with the other I start a predefined Macro in that Access database.
How complicated is this? can you guide me to the solution? Some sample code would be excellent.
Command25_Click()
CurrentDb.Execute "DELETE * FROM Datumi"
Dim tbl As Recordset
Set tbl = CurrentDb.OpenRecordset("Datumi")
tbl.AddNew tbl!brojka = "1"
tbl!datum = Text8.Value
tbl.Update
tbl.AddNew tbl!brojka = "2"
tbl!datum = Text10.Value
tbl.Update
This is going the long way about to a certain extend because I am using the update of the dates to demonstrate how you might also run the saved queries.
'SQL for stored query, this assumes you
'will be updating based on some key
UPDATE DatesTable t
SET t.Date1 = [#Date1], t.Date2 = [#Date2]
WHERE t.aAuto=[#Key]
'Code for Excel
'You could set a library reference to
'Microsoft ActiveX Data Objects x.x Library
Dim cmd As Object
Dim cn As Object
Set cmd = CreateObject("ADODB.Command")
Set cn = CreateObject("ADODB.Connection")
'Connection string, see also http://connectionstrings.com
strCon = "Provider=Microsoft.ACE.OLEDB.12.0; " & _
"Data Source=z:\docs\test.accdb"
cn.Open strCon
'Name of the saved query
cmd.CommandText = "UpdateDates"
cmd.CommandType = adCmdStoredProc
cmd.ActiveConnection = cn
'Some parameters.
'http://www.w3schools.com/ADO/met_comm_createparameter.asp
'Make sure you get the type right, you will find details here:
'http://www.w3schools.com/ADO/ado_datatypes.asp
'You will find direction here:
'http://www.w3schools.com/ado/prop_para_direction.asp
'Make sure you get the order right
'adDate = 7, adInteger = 3, adParamInput = 1
cmd.Parameters.Append cmd.CreateParameter("#Date1", 7, 1, , txtDate1)
cmd.Parameters.Append cmd.CreateParameter("#Date2", 7, 1, , txtDate2)
cmd.Parameters.Append cmd.CreateParameter("#Date2", 3, 1, , MyUniqueKey)
'recs : return for records affected
'adExecuteNoRecords = 128 : no records are returned by this query,
'so this increases efficiency
'http://www.w3schools.com/ADO/ado_ref_command.asp
cmd.Execute recs,,128
'Did it work?
MsgBox "Records updated: " & recs

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.