What's the best way to read a very large database table in VB.NET? - vb.net

I'm developing a VB.NET application to obtain data from any data source (using an odbc connection string), and because this I can't use specific .net connectors like MySql.Net connector and I can't previously know if the file/DMBS supports LIMIT parameter. This app will read data from a table in the data source and use the information to make some files. At this point there was no problem with some tables because they are less than 3,000,000 records length, but there is a table that has 5,000,000+ rows length, and when I send the query the connection is lost. I'm working with OdbcDataReader, because I read on MSDN site that this is the best way to read a table one row at time and I just use each record once.
This is an example of my code:
Private Sub ReadData()
dim cnn as odbc.odbcConnection
dim coma as odbc.odbcCommand
dim reg as odbc.odbcDataReader
try
cnn=new odbc.odbcConnection("Driver={MySQL ODBC 3.51 Driver}; server=localhost; Database=datos; User=usuario; Password=contrasenia; option=3;")
cnn.open()
coma=new odbc.odbcCommand("select * from tabla")
reg=coma.ExecuteReader()'<- when this line is executed fails with the 5,000,000+ length table.
catch ex as Exception
MessageBox("Error: "+ex.Message,MsgBoxStyle.Critical,"Error")
end try
... 'Do anything with the data
end sub
In VBA or VB6 I do something like this:
Private Sub ReadData()
dim cnn as object
dim tab as object
set cnn = CreateObject("ADODB.Connection")
set tab = CreateObject("ADODB.Recordset")
cnn.cursorlocation=3
cnn.open "Driver={MySQL ODBC 3.51 Driver}; server=localhost; Database=datos; User=usuario; Password=contrasenia; option=3;"
tab.open "tabla", cnn,,2
...'Do anything with the data
end sub
And this code is executed without problem.
Any idea how to retreive data in a more efficent way in VB.NET? Or there is any way to do like ADODB (just indicating the table name not the SQL sentence).
Sorry if anything is incomprehensible.

Perhaps try setting the ConnectionTimeout property? Try adding this line before your cnn.open() call:
cnn.ConnectionTimeout = 50000 ' Number of seconds before timeout

When I had similar problem, my solution was to add LimitQuery function that added keywords to query to limit the number of results depending on the provider.
Something like this:
Public Function LimitQuery(ByVal query As String, ByVal RowLimit As Integer) As String
If RowLimit > 0 Then
Select Case m_DbType
Case DbType.Oracle
return "SELECT * FROM(" & query & ") WHERE ROWNUM<" & cstr(RowLimit + 1)
Case DbType.SQLServer
return Replace(query, "SELECT", "SELECT TOP " & cstr(RowLimit), 1, 1)
Case DbType.MySQL
return query & " LIMIT " & cstr(RowLimit)
End Select
Else
return query
End If
End Function
This is jast a quick hack, if you want to use any data source, sooner or later, you'll need some database abstraction layer.

Related

Type Mismatch in VBA SQL expression

I've been out of Access for years, but I've been tasked with a small database function. What I need to do is create a query based on dropdown results, and open that query so that end users can copy/paste what they want from it (or the entire result set).
My code looks like this:
Private Sub btnSubmit_Click()
Dim X As String
Dim Y As String
Dim sSQL As String
Dim MyRs As Recordset
If IsNull(cboReportName.Value) Or IsNull(cboError.Value) Or cboReportName.Value = "" Or cboError.Value = "" Then
MsgBox "One or more of your selections is empty."
Exit Sub
End If
X = cboReportName.Column(2)
Y = cboError.Column(1)
sSQL = "Select * from " & X & " where Error = '" & Y & "'"
Set MyRs = CurrentDb.OpenRecordset(sSQL)
End Sub
I'm getting an error on the Set MyRS line, it's telling me there's a Type Mismatch. Does this have to do with how Access uses Short Text and Long Text? There are NULL results in the query, would that throw this off? Any ideas are appreciated.
It is very unlikely that you get a VBA Type Mismatch error from your query since even if the [Error] column were not Text, it would simply return false when comparing to a string value. (This isn't to discount Erik's comment about multiple query levels causing errors... been there, dealt with that, and I believe this could still be a cause if my answer doesn't help.)
It is more likely that you have referenced an ADO library (from VBA window menu Tools | References...) and placed its priority above the default Access data object libraries. That would cause Dim MyRs As Recordset to interpret this as an ADO recordset, but CurrentDb.OpenRecordset(sSQL) will return a DAO.Recordset.
Update the declaration to
Dim MyRs As DAO.Recordset
or change the priority order of the ADO library in the Tools | References... list.

VB Access DB Update statement

I am new to this forum, please could you help me get this code to work, when i execute it, it simply does nothing and does not update the DB. If i remove the square brackets it gives an error: "SYNTAX ERROR in UPDATE statement"
Any help appreciated!
Dim connection As OleDbConnection
connection = New OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=UserDB.accdb;Jet OLEDB:Database;")
connection.Open()
Dim pass As String
pass = txtconfirm.Text
Dim user As String
user = LoginForm.UsernameTextBox.Text
Dim query As String = "UPDATE [Users] SET [Password]= '" & pass & "' WHERE [Username]= '" & user & "';"
Dim command As New OleDbCommand(query, connection)
command.ExecuteNonQuery()
connection.Close()
Given your actual connection string, the database that will be updated is the one in the directory where your application starts. This means that if you work with a WinForms application this folder is \BIN\DEBUG or x86 variant. If there is not error then you could get the return value of the ExecuteNonQuery call to verify if a record has been updated or not
Dim rowsUpdated = command.ExecuteNonQuery()
MessageBox.Show("Record updated count = " & rowsUpdated)
If this value is not zero then your database has been updated and you are looking for changes in the wrong database. Check the one in the BIN\DEBUG folder.
In any case your code has big problems. If your variables user or pass contain a single quote, then your code will crash again because your string concatenation will form an invalid SQL. As usual the only workaround is to use a parameterized query
Dim pass = txtconfirm.Text
Dim user = LoginForm.UsernameTextBox.Text
Dim query As String = "UPDATE [Users] SET [Password]= #p1 WHERE [Username]= #p2"
Using connection = New OleDbConnection("...........")
Using command As New OleDbCommand(query, connection)
connection.Open()
command.Parameters.Add("#p1", OleDbType.VarWChar).Value = pass
command.Parameters.Add("#p2", OleDbType.VarWChar).Value = user
command.ExecuteNonQuery()
End Using
End Using
The parameterized approach has many advantages. Your query text is more readable, there is no misunderstanding between your code and the values expected by your database engine. And while not easy to exploit with MS-Access there is no problem with Sql Injection
I think Steve presents a much better approach for you coding this...
Let me just throw out a few more things:
The reason you can't take those brackets out is some of your column names are reserved words; just FYI.
Since you report "it does nothing..." when you execute, it sounds like you have a valid connection and sql syntax, in which case my next step would be to copy the sql command text while in debug mode, change it to a select and run it in your DB. You should get one result when you do. If not, either your criteria or field contents are not what you think they are...
Just change the Update table SET field-value ... to SELECT * FROM table and leave the WHERE clause as is.

Inserting data to other xls workbook via ADO & SQL – Data type error

I am building a quite complex UserForm that uses ADO connection to connect to another Excel workbook that serves as a database and retrieve & insert data via SQL queries. Please note I am not allowed to use Access in this case.
I have already figured out how to use SELECT, but there is one particular error with INSERT I can't resolve. That bothers me a lot, I've put a lot of work to it.
First the connection (I use JET for retrieving data and ACE for saving data as I was not able to get JET to work for that):
Public Sub InsertDataToSheet(SQLCmd As String)
Dim cnx As Object
Set cnx = CreateObject("ADODB.Connection")
cnx.Open "Provider=Microsoft.ACE.OLEDB.12.0; Data Source='" & ThisWorkbook.Path & "\Database.xls'; Extended Properties=Excel 12.0;"
cnx.Execute SQLCmd
cnx.Close
End Sub
Then there is a subroutine linked to a Submit button that actually generates the Query as I need to save only filled out Textboxes and Combos to avoid Nulls:
Private Sub SaveRecord()
Dim SQL As String
SQL = "INSERT INTO [Report$A2:AM50000] ("
Dim i As Control
For Each i In Me.controls
If TypeName(i) = "TextBox" Or TypeName(i) = "ComboBox" Then
If i <> e Then SQL = SQL & i.Name & ","
End If
Next i
SQL = Mid(SQL, 1, Len(SQL) - 1) & ") VALUES(" ' Remove last space & comma
Dim j As Control
For Each j In Me.controls
If TypeName(j) = "TextBox" Or TypeName(j) = "ComboBox" Then
If j <> e Then
If j = "Unknown" Then MsgBox "Fire"
Select Case IsNumeric(j)
Case False
SQL = SQL & "'" & j & "'" ' Add single quotes around strings
Case True
SQL = SQL & j
End Select
SQL = SQL & ","
End If
End If
Next j
SQL = Mid(SQL, 1, Len(SQL) - 1) & ")" ' Remove last comma
' Connect
InsertDataToSheet (SQL)
End Sub
There are two particular textboxes in the form that work exactly the same. Normally, users enter numbers to them and everything saves fine (don't mind the '+' buttons):
Sometimes, however, users do not know the values but can't leave those empty. That's when they are supposed to tick the checkboxes to set the value(s) to 'Unknown':
Now there comes the funny part – for the Batch field, it saves fine. But when I set the Shipment ID to 'Unknown' (or any other string), it throws an error:
Note the fields are not Disabled, just Locked with some appearance changes. I was also unable to find any specific details about the error, but it seems there is some problem with the query:
(It says something like 'Incompatible data types in the expression'). The generated query is this:
Any ideas what goes wrong? I'd very much like to keep the functionality as it is know and solve the error rather than redesign it as it already took some effort and the fields can't stay empty.
Never used sql in xls workbooks, but I had this problem with SQL server already. There's nothing "wrong" with your query, the problem is that data type that's accepted on the field of the table you want to insert. Try to turn that field to use text values instead of numbers and it should work.

Can I create a pass-through query via VBA to call a parameterized tSQL UDF and send it a dynamic parameter to return results to Access?

I currently have a SQL 2008 R2 database backend with an Access 2013 accdb front end with ODBC DSN-less connection and linked tables. In SQL I have many parameterized tSQL UDFs created to feed data into reports (currently working well in my Access 2010 adp frontend). The reports are complicated: multiple tSQL UDFs run calculations and then feed into a final UDF that feeds the respective report. I would like to keep the UDFs on the server – rewriting into Access queries would be a poor solution.
My problem is that I have not been able to figure out how to write the VBA correctly to send a pass-through query to call the tSQL UDF and give it a parameter, which would change for each report. I know pass-through queries are read-only, that’s ok. I’ve read that I can call a stored procedure (SP) from VBA, but can I call the UDF rather than having to convert each to a SP or create a SP just to call the UDF so that I could call the SP from VBA. Based on my research, I think I might have to either create a SP to call the UDF or convert the UDF to a SP to get the VBA to work (i.e., return results without error). Is this correct?
I found this discussion: https://social.msdn.microsoft.com/Forums/office/en-US/898933f5-73f9-44e3-adb9-6aa79ebc948f/calling-a-sql-udf-from-access?forum=accessdev , but it has conflicting statements “You can't call a tSql udf from Access.”, and “You can use a passthrough query to call UDF's or stored procedures or anything else written in tsql.” Also, their code is written in ADO instead of DOA so it’s a bit cryptic to me since I’ve only written DAO so far, but the general gist that I got was they converted their UDF to a SP.
I found this article a great read, but again did not get a clear “yes” to my question:
http://technet.microsoft.com/en-us/library/bb188204(v=sql.90).aspx
It may be possible to remove the parameter from the Server side and add it to the Access side similar to this Access 2007 forms with parameterized RecordSource , but wouldn't that cause Access to load the entire dataset before filtering, instead of processing on the Server side – possibly causing performance issues?
I can successfully create a pass-through query in the Access interface if I supply it with a constant parameter, for example “Select * from udf_FinalReport(2023)”, but what I really need is to be able to pass a dynamic parameter. For example, the parameter would be from Forms!Control![txtboxValue]. Can I do this? The following code is what I’m using– it works if I use a table name in the SQL (ex, “SELECT * FROM Table WHERE tblYear = “&intYear ) in line 9 so I feel like I have everything coded right, but when I put my UDF in the SQL like below I get the error #3131 “Syntax error in FROM clause.” (I did verify that I should not use the prefix schema (dbo.) – this gives error 3024 “could not find file”.) Is this user error or just plain telling me I can’t call a UDF this way?
1 Sub AnnualSummary()
2 Dim dbs As DAO.Database
3 Dim qdfPoint As DAO.QueryDef
4 Dim rstPoint As DAO.Recordset
5 Dim intYear As Integer
6 intYear = Reports!Annual_Delineation_Summary!txtYear
7 Set dbs = OpenDatabase("", False, False, "ODBC;DRIVER=sql server;SERVER=******;APP=Microsoft
8 Office 2010;DATABASE=*******;Network=DBMSSOCN")
9 Set qdfPoint = dbs.CreateQueryDef("", "Select * from udf_AnnualReport(" & intYear& ")")
10 GetPointTemp qdfPoint
11 ExitProcedure:
12 On Error Resume Next
13 Set qdfPoint = Nothing
14 Set dbs = Nothing
15 Set rstPoint = Nothing
16 Exit Sub
17 End Sub
18
19 Function GetPointTemp(qdfPoint As QueryDef)
20 Dim rstPoint As Recordset
21 With qdfPoint
22 Debug.Print .Name
23 Debug.Print " " & .SQL
24 Set rstPoint = .OpenRecordset(dbOpenSnapshot)
25 With rstPoint
26 .MoveLast
27 Debug.Print " Number of records = " & _
28 .RecordCount
29 Debug.Print
30 .Close
31 End With
32 End With
33 End Function
I also tried writing the code a little differently, using the following instead of lines 5, 6, and 9. This also works when I use a table name in the select statement, but I get error #3131 when I use a UDF name:
Set qdfPoint = dbs.CreateQueryDef("", "Parameters year int; Select * from Point_Info where
year(Sample_Date) = year")
qdfPoint.Parameters("year").Value = intYear
Both code variations also work if I try use the name of a SQL View in the tSQL SELECT statement.
My consensus is using ADO language instead of DAO to write the pass-through query works well. But, I have found that it is still probably better to execute a stored procedure than to try to call the UDF. Here is the code that ended up working most smoothly for me: (my ADO Connection uses Public variables strUID and strPWD)
Dim cn As ADODB.Connection
Dim rs As ADODB.Recordset
Dim strPoint As String
strPoint = Forms!FRM_Vegetation_Strata!Point_ID
Set cn = New ADODB.Connection
cn.Open "Provider = sqloledb;Data Source=imperialis.inhs.illinois.edu;" & _
"Initial Catalog=WetlandsDB_SQL;User Id=" & strUID & ";Password=" & strPWD
Set rs = New ADODB.Recordset
With rs
Set .ActiveConnection = cn
.Source = "sp_Report_VegWorksheet '" & strPoint & "'"
.LockType = adLockOptimistic
.CursorType = adOpenKeyset
.CursorLocation = adUseClient
.Open
End With
Set Me.Recordset = rs
On a side note I found that to get set the .Recordset to fill a subform put this code in the "Open" event of the subform.
Then to clean up your connection:
Private Sub Form_Unload(Cancel As Integer) 'use "unload", not "close"
'Close the ADO connection we opened
Dim cn As ADODB.Connection
Dim rs As ADODB.Recordset
Set cn = Me.Recordset.ActiveConnection
cn.Close
Set cn = Nothing
Set rs = Nothing
Set Me.Recordset = Nothing
End Sub
This approach does not work for populating a report. "Set Me.Recordset" only works for forms. I believe I will have to call a stored procedure then populate a temp table to use as the report recordset.
EDIT: I have found that I can call a SQL UDF or SP from VBA in Access using DOA. This is particularly helpful when one wants to pull the data from a complicated SQL function/procedure and put it into an Access-side temp table. See Juan Soto's blog https://accessexperts.com/blog/2012/01/10/create-temp-tables-in-access-from-sql-server-tables/#comment-218563 This code puts the info into a temp table, which is what I wanted to populate my reports. I used his code example and the following to call the sub:
To execute as SP: CreateLocalSQLTable "testTBL","exec dbo.sp_Report_WetDet_point '1617-1-1A'",False
To call a UDF: CreateLocalSQLTable "testTBL","Select * from dbo.QryReport_Main('1617-2-2A')",False
I don't know if it's the most efficient method of passing a variable parameter through a pass-through query into a function and returning the results to Access, as I am still relatively new to Access, but I came across this earlier when I was attempting a similar problem.
I managed it by creating a couple of pass-through queries that executed functions in SQL server and returned a result. I then made a small VBA script that re-wrote the pass-through queries with the new variable every time I wanted to change it, and executed them.
I got the result back out using OpenRecordset, and stored it as a string to use in the rest of my code.

Recordsets in VB.NET

All,
Trying to implement a recordset approach in VB.NET and (with the wonderful help of this community) have gotten to the point where things almost run. :)
Code:
Dim strSQL As String
Dim rsMaster As New ADODB.Recordset
strSQL = "select * " & "from tblDQ " & "order by xid, xcode, xDOS"
rsMaster.Open(strSQL, objConn, adOpenForwardOnly, adLockOptimistic)
The last line throws an exception while attempting to execute the rsMaster.Open line:
COM Exception was unhandled
Arguments are of the wrong type, are out of acceptable range, or are in conflict with one another.
Any ideas? Changing the cursor type or lock type doesn't seem to do any good, and the types specified above are available as options when coding the line. I've (on a WAG) added the following lines to the project:
Imports ADODB.LockTypeEnum
Imports ADODB.CursorTypeEnum
with no luck.
TIA!
Dave
Some ideas:
Does the SQL statement work in a "pure" database tool? (The &s looks a bit strange, but I assume that's a formatting issue in SO.)
The select * should work, but try a specific simple column (a short string or an integer), maybe there is an issue with a "tricky" column data type.
Do you have the objConn declared and a proper connection opened?
Some more ideas:
As you get the error when you execute the statement it seems that there is a problem with your statement. Is there a really simple SQL statement that will work in your scenario? (no order by, just a column, ..)
Try the statement from within MSAccess using the same connection parameters you are using in your VB.NET program. Maybe you are using a different user from within your program, maybe you have to qualify the table name etc.
If you say the connection is working fine, what did you do using the connection? Was there anything requiring access to the DB?
Ok, if removing the adOpenForwardOnly and the adLockOptimistic parameters doesn't change anything, how about changing the code slightly to use the Execute Method of the Connection object?
rsMaster.Open(strSQL, objConn, adOpenForwardOnly, adLockOptimistic)
Becomes
rsMaster = objConn.Execute(strSQL)
Not sure about your connection. You have an Access tag, but mentioned you tested in SQL Server. If you are using an access .mdb file
Imports adodb
Public Class Form1
Private Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim strSQL, strConn As String
Dim rsMaster As New ADODB.Recordset
Dim objConn As New ADODB.Connection
strConn = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\DQ\DQ.mdb"
objConn.Open(strConn)
strSQL = "select * " & "from tblDQ " & "order by xid, xcode, xDOS"
rsMaster.Open(strSQL, objConn, CursorTypeEnum.adOpenForwardOnly, LockTypeEnum.adLockOptimistic)
rsMaster.MoveFirst()
Me.Text = rsMaster("xcode").Value
rsMaster.Close()
rsMaster = Nothing
End Sub
End Class