Avoid new line seperators in mySQL query within VBA code - mysql

I have the below VBA to extract data from the database:
Sub Get_Data_from_DWH ()
Dim conn As New ADODB.Connection
Dim rs As New ADODB.Recordset
Dim dateVar As Date
Set conn = New ADODB.Connection
conn.ConnectionString = "DRIVER={MySQL ODBC 5.1 Driver}; SERVER=XX.XXX.XXX.XX; DATABASE=bi; UID=testuser; PWD=test; OPTION=3"
conn.Open
strSQL = " SELECT" & _
" product, brand, sales_channel," & _
" country, sales_manager, sales_date, return_date, " & _
" process_type, sale_quantity, return_quantity " & _
" FROM bi.sales" & _
" WHERE sales_date BETWEEN '2020-01-01' AND '2020-06-30' " & _
" AND country IN ('DE', 'US', 'NL') " & _
" ORDER BY FIELD (brand, 'brand_A', 'brand_B', 'brand_C');"
Set rs = New ADODB.Recordset
rs.Open strSQL, conn, adOpenStatic
Sheet1.Range("A1").CopyFromRecordset rs
rs.Close
conn.Close
End Sub
This VBA extracts the data based on the SQL without any problem.
However, in my original file the VBA is much bigger and therefore I have to use a lot of new line separators " and " & _ .
This makes the handling of the SQL within the VBA very difficult since the structure is quite confusing.
Therefore I am wondering if there is an alternative that allows you to enter the SQL without the new line separators.
Something like this:
strSQL = " SELECT
product, brand, sales_channel,
country, sales_manager, sales_date, return_date,
process_type, sale_quantity, return_quantity
FROM bi.sales
WHERE sales_date BETWEEN '2020-01-01' AND '2020-06-30'
AND country IN ('DE', 'US', 'NL')
ORDER BY FIELD (brand, 'brand_A', 'brand_B', 'brand_C'); "
Do you have any idea if this is possible?

Definitive answer - It's not possible in VBA, there's no way for the IDE to allow line breaks in a string.
But here's some suggestions:
If you can't create views on the database and just use those, maybe you could directly link to the data source and do queries that way? So you don't have to use VBA to write queries - Excel has great in-built connectivity and you get a MySQL library, just google "connect to mysql database in excel"
If you really want your queries defined as text somewhere else, one thing that might help to neaten your code is to load your strings from another place where you can keep your sql queries in a format you prefer.
You could load them from a VBA module, which would still have split strings but you can call them like StringSQL = Module1.GetQueryOne and it helps keep the execution code and the SQL strings separate
You could create a method to pull queries from worksheet cells, or maybe keep them in textboxes on a worksheet, then you can use
SqlString = ThisWorkbook.Sheets("Sheet1").Shapes("SqlQuery1").OLEFormat.Object.Text
or even keep them in a text file that accompanies your Excel workbook and load them from there.

Related

VBA Access: No value given for one or more required parameters

I know, there are lots of answers out there for this problem which should be trivial, but I did not find the right one. Here is my problem:
I open a record set with the following select statement:
SELECT twinecellar.produktnavn, twinecellar.land,
twinecellar.produkttype, twinecellar.år,
twinecellar.antall, twinecellar.poeng,
twinecellar.Picture, twinecellar.KR,
twinecellar.Poengsum, twinecellar.Sum
FROM twinecellar
WHERE (((twinecellar.land)=forms!fmainview!list13)
And ((twinecellar.produkttype)=forms!fmainview!list15))
ORDER BY twinecellar.poeng;
In the immidiate window I see that list 13 contains "france" and list 15 contains "red"
When I create a new Query with this statement, it's working, however, on the rst.Open gsStrQuery I get this error. gsStrQuery contains the select string.
Here is the code:
Dim conn As ADODB.Connection
Dim rst As ADODB.Recordset
Set conn = CurrentProject.Connection
Set rst = New ADODB.Recordset
rst.CursorType = adOpenDynamic
rst.ActiveConnection = conn
rst.Open gsStrQuery
Anybody out there with a good idea about this issue?
When you build your SQL string, concatenate the "parameters" values into the string.
gsStrQuery = "SELECT twinecellar.produktnavn, twinecellar.land, " & _
"twinecellar.produkttype, twinecellar.år, " & _
"twinecellar.antall, twinecellar.poeng, " & _
"twinecellar.Picture, twinecellar.KR, " & _
"twinecellar.Poengsum, twinecellar.Sum " & _
"FROM twinecellar " & _
"WHERE (((twinecellar.land)= '" & forms!fmainview!list13 & "') " & _
"And ((twinecellar.produkttype)= '" & forms!fmainview!list15 & "')) " & _
"ORDER BY twinecellar.poeng;"
That way your parameter values are hard coded into the string before you try to open the query.
(Also note: I added single quotes around your parameters to indicate they are strings.)
(Also also note: & _ is a line continuation for VBA so your SQL string concatenates properly. This allows you have a readable SQL code that's nicely indented.)
________________________________
There is also a way to use your current gsStrQuery and assign parameters values to the ADO recordset. (But I find the above Replacement method much easier to read when going back to review the code. The only drawback is you have to rebuild your SQL string each time your parameters change. But that overhead is minimal for non complicated queries.)
However, if you really want to use ADO parameters, you can find a useful description here.
Hope that helps :)

how can i open execuete a query in VB while there is reader opened?

is there any possible way to execute this without getting this error "There is already an open DataReader associated with this Connection which must be closed first." i already tried using "dr.close()" and i get another error that says "Invalid attempt to Read when reader is closed." can you help me out?
Heres my code:
Private Sub Button4_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button4.Click
Label2.Text = AllPicker1.Text
Label3.Text = AllPicker2.Text
If AllPicker1.Value >= AllPicker2.Value Then
MsgBox("End Date Must be Greater!")
Else
Dim SQLstatement As String = "SELECT * FROM tblStudInfo,tbl_studentLog WHERE tblStudInfo.StudID = tbl_studentLog.StudentNumber AND tbl_studentLog.LoginDate BETWEEN '" & AllPicker1.Text & "' AND '" & AllPicker2.Text & "'"
OpenData(SQLstatement)
End If
End Sub
Public Sub OpenData(ByRef SQLstatement As String)
Dim cmd As MySqlCommand = New MySqlCommand
With cmd
.CommandText = SQLstatement
.CommandType = CommandType.Text
.Connection = SqlConnection
dr = .ExecuteReader()
End With
While dr.Read
Dim SQLstatementSave As String = "INSERT INTO tbl_report (RepStudNo,RepName,RepCourse,RepDept,RepLogTime,RepLogdate) VALUES ('" & dr("StudID") & "','" & dr("Name") & "','" & dr("Course") & "','" & dr("Dept") & "','" & dr("LoginTime") & "','" & dr("LoginDate") & "') "
dr.Close()
Save(SQLstatementSave)
End While
SqlConnection.Close()
SqlConnection.Dispose()
SqlConnection.Open()
End Sub
Public Sub Save(ByRef SQLstatementSave As String)
Dim cmd As MySqlCommand = New MySqlCommand
With cmd
.CommandText = SQLstatementSave
.CommandType = CommandType.Text
.Connection = SqlConnection
.ExecuteNonQuery()
End With
SqlConnection.Close()
SqlConnection.Dispose()
SqlConnection.Open()
End Sub
End Class
It seems you are using only one SqlConnection. For most database systems you cannot reuse the connection while you are reading from it. You can either read all data into memory / DataTable and work on the rows after that or use a different SqlConnection for your Inserts.
When working with SqlConnections, Readers and Commands I find the Using Statement very helpful to visualize object usage and creation.
We can reduce this down to a single query:
INSERT INTO tbl_report
(RepStudNo,RepName,RepCourse,RepDept,RepLogTime,RepLogdate)
SELECT StudID, Name, Course, Dept, LoginTime, LoginDate
FROM tblStudInfo
INNER JOIN tbl_studentLog ON tblStudInfo.StudID = tbl_studentLog.StudentNumber
WHERE tbl_studentLog.LoginDate BETWEEN #StartDate AND #EndDate
Note the use of the full INNER JOIN syntax. The older TableA,TableB syntax for joins should be avoided. Also note the use of placeholders for your dates. This is important.
Now I need to draw attention to a couple functions I saw: OpenData(), and Save().
Those two functions are fundamentally broken, because they force you to build your queries in a way that leaves you vulnerable to sql injection hacking. Someday soon, someone will put a value like this into a textbox that is included with a query:
';DROP Table tbl_studentLog;--
Think carefully about what would happen now if someone entered that into your AllPicker1.Text. It would be hard to do that to a date picker, but I'll bet you have other plain text fields that would allow this. The first character (single quote) in my proposed input would close the string literal in the query. The second character (semi-colon) would end the individual statement, but sql server won't stop executing code. The next set of characters make up an additional statement that would drop your table. The final two characters comment out anything that follows, to avoid sql server rejecting or not committing the command because of syntax errors. Yes, Sql Server will run that additional statement, if that is what you put in a textbox.
So, your methods as written are broken, because the only accept completed sql strings as input. Any function that calls into the database MUST also include a mechanism for accepting query parameters. You ultimately want to be running code more like this:
Public Sub CreateReport(ByVal StartDate As DateTime, ByVal EndDate As DateTime)
Dim sql As String = _
"INSERT INTO tbl_report " & _
" (RepStudNo,RepName,RepCourse,RepDept,RepLogTime,RepLogdate) " & _
" SELECT StudID, Name, Course, Dept, LoginTime, LoginDate " & _
" FROM tblStudInfo " & _
" INNER JOIN tbl_studentLog ON tblStudInfo.StudID = tbl_studentLog.StudentNumber " & _
" WHERE tbl_studentLog.LoginDate BETWEEN #StartDate AND #EndDate"
'.Net is designed such in most cases that you really do want a new SqlConnection for each query
'I know it's counter-intuitive, but it is the right way to do this
Using cn As New SqlConnection("Connection string"), _
cmd As New SqlCommand(sql, cn)
'Putting your data into the query using parameters like this is safe from injection attacks
cmd.Parameters.Add("#StartDate", SqlDbType.DateTime).Value = StartDate
cmd.Parameters.Add("#EndDate", SqlDbType.DateTime).Value = EndDate
cn.Open()
cmd.ExecuteNonQuery()
End Using
End Sub
One thing to point out here is that at first glance I don't close the connection. However, the Using block will ensure that the connection is closed promptly... even if an exception is thrown. Your existing code will leave the connection hanging in the case of a exception.
Also note that this neatly side-steps the whole issue of needing to execute a separate query while your reader is opened... but if you ever do really need to do this (it's rare), the answer is simple: use a separate connection.
Instead of:
Dim SQLstatementSave As String = "INSERT INTO tbl_report
(RepStudNo,RepName,RepCourse,RepDept,RepLogTime,RepLogdate)
VALUES ('" & dr("StudID") & "','" & etc.
Try using .ToString on your DR() references.
Dim SQLstatementSave As String = "INSERT INTO tbl_report
(RepStudNo,RepName,RepCourse,RepDept,RepLogTime,RepLogdate)
VALUES ('" & dr("StudID").ToString & "','" & etc.

How do I delete rows in a table from one database to the other in MS Access

I have two databases, a "Master" source (held locally) and a "Copy" database (to be distributed). Neither will be able to see each other once distributed (either locally or across a network), so we can't perform queries across the databases after distribution. I need to remove some content from the Copy database before it is distributed, so I decided to create a VBA script to produce the Copy databases for distribution.
There are lookups in the tables, so I have decided to hold a template database (which was copied from the master source, and then stripped the tables out of them), then am dropping the tables and have recreated them in an appropriate order.
I'm now needing to remove some of the data, and I'm struggling.
DeviceTable:
AutoNumber(ID)
Text(DeviceName)
Integer(ClusterID)
Text(Distribution)
ClusterTable:
AutoNumber(ID)
Text(ClusterName)
VirtualSystemTable:
AutoNumber(ID)
Text(VirtualSystemName)
Integer(ClusterID)
Integer(DeviceID)
InterfaceTable:
AutoNumber(ID)
Integer(VirtualSystemID)
Integer(ClusterID)
Integer(DeviceID)
Text(Description)
I need to remove entries from DeviceTable, ClusterTable, VirtualSystemTable and InterfaceTable for anything which is not marked as Distribution: "Public"
Normally I would do (in psudocode):
arrDEV = SQL("SELECT ID, ClusterID FROM DeviceTable WHERE Distribution<>"Public"")
Then, for each response, I would
arrVSYS = SQL("SELECT ID FROM VirtualSystemTable WHERE DeviceID=$arrDEV.ID OR ClusterID=$arrDEV.ClusterID")
SQL("DELETE FROM InterfaceTable WHERE DeviceID=$arrDEV.ID OR ClusterID=$arrDEV.ClusterID OR VirtualSystemID=$arrVSYS.ID")
SQL("DELETE FROM VirtualSystemTable WHERE DeviceID=$arrDEV.ID OR ClusterID=$arrDEV.ClusterID")
SQL("DELETE FROM ClusterTable WHERE ID=$arrDEV.ClusterID")
My issue is that I can't work out how to perform these queries across the database link. I amusing ADODB. I normally code in PHP, so this is a bit of a struggle for me!
You have the right idea but VBA is quite a bit different from PHP. You can't use variables inside strings with first exiting the string, concatenating the variable, and then starting the string again.
Where you would typically use an array called arrDev or arrVSys, we use a DAO Recordset or ADO Recordset object inside MS Access.
Updated answer to reflect the information you gave:
Dim db As DAO.Database
Dim sSQL as String
Dim arrDEV As DAO.Recordset, Dim arrVSYS as DAO.Recordset
Set db = OpenDatabase("C:\SomeDatabase.accdb")
sSQL = "SELECT ID, ClusterID FROM DeviceTable WHERE Distribution <> 'Public'"
Set arrDEV = db.Open(sSQL)
If Not (arrDEV.EOF and arrDEV.BOF) Then
arrDEV.movefirst
Do Until arrDEV.eof = True
sSQL = "SELECT ID FROM VirtualSystemTable WHERE DeviceID = " & arrDEV("ID") & _
" OR ClusterID = " & arrDEV("ClusterID")
Set arrVSYS = CurrentDb.Open(sSQl)
sSQL = "DELETE FROM InterfaceTable WHERE DeviceID = " & arrDEV("ID") & _
" OR ClusterID = " & arrDEV("ClusterID") & " OR VirtualSystemID = " & arrVSYS("ID")
CurrentDb.Execute sSQl, dbFailOnError
sSQL = "DELETE FROM VirtualSystemTable WHERE DeviceID = " & arrDEV("ID") & " OR ClusterID = " & arrDEV("ClusterID")
CurrentDb.Execute sSQl, dbFailOnError
sSQL = "DELETE FROM ClusterTable WHERE ID = " & arrDEV("ClusterID")
CurrentDb.Execute sSQl, dbFailOnError
arrVSYS.Close
arrDEV.MoveNext
Loop
End If
'Cleanup
Set arrVSYS = Nothing
arrDEV.Close
Set arrDEV = Nothing
db.close
Set db = Nothing
The code is untested and could have some errors. The only thing I didn't plan for was having more than one record in arrVSYS. If that recordset will have multiple records then you'll need yet another loop.
As you can see, you don't need to use ADO to access another Access database. But if the "outside" database is something other than Access then yes, you would need to use ADO.

Preserving Single Quotes in Access

I have created a form in Access 2010 that is used to insert data into an existing table. The table contains a Keywords field, Source combo box, and a Code text box where i write the data to be inserted and there is a button for executing the query. The code for the form is:
Private Sub cmd_go_Click()
Dim insertstring As String
insertstring = "INSERT INTO KWTable (KW, Source, Code) VALUES('" & text_key.Value & "','" & combo_source.Value & "','" & txt_code.Value & "');"
DoCmd.RunSQL insertstring
End Sub
The code is simple, it inputs the data to the table so i can reference it for future use. Now the problem I am having is that when I try to add long bits of code that I use in SQL Server i get a syntax missing expression error which I am assuming is coming from the single quotes since the code is from SQL. I am getting the error because when i am trying to store a code i used in SQL Server it uses single quotes which access does not recognise. I think if I try to write in the code for the insert form something to help convert the single quotes into double quotes, then reconvert them back to single quoteswill help solve the problem. I just cant figure out how to do it and could really use some help.
Thank You
You can avoid trouble with included quotes in your inserted text by using a parameter query.
Consider an approach such as this for cmd_go_Click().
Dim strInsert As String
Dim db As DAO.database
Dim qdf As DAO.QueryDef
strInsert = "PARAMETERS pKW TEXT(255), pSource TEXT(255), pCode TEXT(255);" & vbCrLf & _
"INSERT INTO KWTable (KW, Source, Code) VALUES (pKW, pSource, pCode);"
'Debug.Print strInsert
Set db = CurrentDb
Set qdf = db.CreateQueryDef(vbNullString, strInsert)
qdf.Parameters("pKW") = Me.text_key.value
qdf.Parameters("pSource") = Me.combo_source.value
qdf.Parameters("pCode") = Me.txt_code.value
qdf.Execute dbFailOnError
Set qdf = Nothing
Set db = Nothing
However, I don't understand how JoinCells() fits in.
I use a function that handles Null Values, and escapes single quotes (by converting them to two single quotes) when creating SQL statements directly:
Function SafeSQL(ByVal pvarSQL As Variant) As String
SafeSQL2 = Replace(Nz(pvarSQL, ""), "'", "''")
End Function
Then in your routine you would have:
insertstring = "INSERT INTO KWTable (KW, Source, Code) VALUES('" & SafeSQL(text_key.Value) & "','" & SafeSQL(combo_source.Value) & "','" & SafeSQL(txt_code.Value) & "');"

Fastest way to move data from Oracle to MS Access via VBScript

Normally, I would just loop through using Recordset.AddNew, but this is painfully slow when dealing with large recordsets -- is there a better way to do it? I was hoping there would be a way to simply write an insert statement to go from Oracle to Access, perhaps by using two ADO connects, but the only examples I can find are in VB.net (using OleDbCommand), which is sadly not an option. Is this simply a limitation of VBScript?
Thanks!
Your question tags include access-vba, so I'll suggest the DoCmd.TransferDatabase Method.
This example, near the bottom of the page, is to create an ODBC link to a SQL Server table. It assumes a DSN (data source name) named DataSource1. The link name in Access will be Authors, and the server source table is named dboAuthors.
DoCmd.TransferDatabase acLink, "ODBC Database", _
"ODBC;DSN=DataSource1;UID=User2;PWD=www;LANGUAGE=us_english;" _
& "DATABASE=pubs", acTable, "Authors", "dboAuthors"
You can adapt that for Oracle with an appropriate DSN. And if you want to import rather than link, substitute acImport for acLink.
And actually, you might not even need code for this. With a working DSN, you can import (or link) via the Access user interface.
Your title mentioned VBScript. Tell us if you actually must use VBScript instead of VBA.
After reading your comments, I think you would be better of with a different approach.
Create a database with an ODBC link to the Oracle table.
Then export subsets of that table data to your individual database files.
SELECT master_link.* INTO local_table IN 'c:\somefolder\db1.mdb'
FROM master_link
WHERE location = 'location1';
Then adjust the SQL for each target db file and data selection criterion. It should be easy to drive that from a simple VBA procedure.
Here are a few notes for VBScript using SQL Server, rather than Oracle, however, it should be possible to use something very similar.
Const CreatePath = "Z:\docs\"
Const Provider = "Microsoft.ACE.OLEDB.12.0;"
''The easiest way to get this line is from the connect property of
''a linked table
Const ServerInLine = "[ODBC;DRIVER=SQL Server;SERVER=server;Trusted_Connection=Yes;DATABASE=db]"
''Change this to the Oracle connection
Const ServerConnect = "Provider=sqloledb;Data Source=server;Initial Catalog=db;Integrated Security=SSPI;"
Dim cnSS ''As ADODB.Connection
Dim cnMSA ''As ADODB.Connection
Dim cat ''As New ADOX.Catalog
Set cat = CreateObject("ADOX.Catalog")
Set cnSS = CreateObject("ADODB.Connection")
Set cnMSA = CreateObject("ADODB.Connection")
Set rs = CreateObject("ADODB.Recordset")
cnSS.Open ServerConnect
''Only the locations that exist. Assuming a location table
''with a description, Location
sSQL = "SELECT DISTINCT a.LocationID, b.Location FROM ATable a "
sSQL = sSQL & "INNER JOIN Location b ON a.LocationID = b.ID"
rs.Open sSQL, cnSS
Do While Not rs.EOF
DBName = CreatePath & Trim(rs.Fields("Location")) & ".accdb"
scn = "Provider=" & Provider & ";Data Source=" & DBName
cat.Create scn
cnMSA.Open scn
sSQL = "SELECT * INTO " & Replace(Trim(rs.Fields("Location")), " ", "")
sSQL = sSQL & " FROM " & ServerInLine & ".ATable"
sSQL = sSQL & " WHERE LocationID=" & rs.Fields("LocationID")
cnMSA.Execute sSQL, recs
sMsg = sMsg & vbCrLf & DBName & " created " & recs
rs.MoveNext
cnMSA.Close
Loop
MsgBox sMsg