When using ExecuteReader in combination with InfoMessages event the last InfoMessages are not raised - if the query produces no rows. If the query produces rows you can see the InfoMessages in the end. Sample query that returns no rows might look like this:
PRINT 'this is the start of the test'
print CHAR(10) + CHAR(13);
Select top(0) 1, 2, 3;
print CHAR(10) + CHAR(13);
print 'this is the end of the test'
In the example above the InfoMessages even are not raised for the last two rows. It is only raised if the middle query returns something.
Sample code, simplified:
' add handler
AddHandler Cmd.Connection.InfoMessage, AddressOf InfoMessageReceived
' execute query
reader = Cmd.ExecuteReader
While reader.Read()
strValue = reader.GetValue(i).ToString()
End While
' remove handler
RemoveHandler Cmd.Connection.InfoMessage, AddressOf InfoMessageReceived
' consolidate output
Any idea why?
Related
Im trying to run Dlookup to return all values of a column in a query's results. As Dlookup can only return 1 result I have added a row count column to the query so I can use the row count as the criteria for the Dlookup.
My query results look like this in the results
Query design view
Query results 2
In SQL view the query is as follows:
SELECT tblAssets.SerialNumber, tblAssets.ID, tblAssets.AssetType, tblPlacing.Location, tblPlacing.PlacingStartDate, tblPlacing.PlacingEndDate, RowNumber([ID]) AS [NO]
FROM tblAssets INNER JOIN (tblLocations RIGHT JOIN tblPlacing ON tblLocations.LocationID = tblPlacing.Location) ON tblAssets.ID = tblPlacing.AssetID
GROUP BY tblAssets.SerialNumber, tblAssets.ID, tblAssets.AssetType, tblPlacing.Location, tblPlacing.PlacingStartDate, tblPlacing.PlacingEndDate, RowNumber([ID])
HAVING (((tblAssets.AssetType)=55) AND ((tblPlacing.Location)=[Forms]![FrmPPM]![TXTLocationID]) AND ((tblPlacing.PlacingEndDate) Is Null) AND ((ResetRowNumber())<>False));
I have added the row count with the following video (https://www.youtube.com/watch?v=HWbpzETe-M0), let me know if more information about this is needed.
The query results look to be correct, however, when using the Dlookup (beneath) it returns null.
Dim VAR1 as Variant
VAR1 = DLookup("[SerialNumber]", "QRYPPM", "[NO] = 1")
When I try and return the [NO] column value it works.
VAR1 = DLookup("[NO]", "QRYPPM", "[SerialNumber] = '6501038'")
I don't have your data, so can't run a full test, but I ran something similar using my RowNumber function (below), and DLookup was able to filter on the returned rownumber (your [NO]) and return a value from another field.
So, try to use this function (your SQL will need minor adjustments, see in-line comments, please):
' Builds consecutive row numbers in a select, append, or create query
' with the option of a initial automatic reset.
' Optionally, a grouping key can be passed to reset the row count
' for every group key.
'
' Usage (typical select query having an ID with an index):
' SELECT RowNumber(CStr([ID])) AS RowID, *
' FROM SomeTable
' WHERE (RowNumber(CStr([ID])) <> RowNumber("","",True));
'
' Usage (typical select query having an ID without an index):
' SELECT RowNumber(CStr([ID])) AS RowID, *
' FROM SomeTable
' WHERE (RowNumber("","",True)=0);
'
' Usage (with group key):
' SELECT RowNumber(CStr([ID]), CStr[GroupID])) AS RowID, *
' FROM SomeTable
' WHERE (RowNumber(CStr([ID])) <> RowNumber("","",True));
'
' The Where statement resets the counter when the query is run
' and is needed for browsing a select query.
'
' Usage (typical append query, manual reset):
' 1. Reset counter manually:
' Call RowNumber(vbNullString, True)
' 2. Run query:
' INSERT INTO TempTable ( [RowID] )
' SELECT RowNumber(CStr([ID])) AS RowID, *
' FROM SomeTable;
'
' Usage (typical append query, automatic reset):
' INSERT INTO TempTable ( [RowID] )
' SELECT RowNumber(CStr([ID])) AS RowID, *
' FROM SomeTable
' WHERE (RowNumber("","",True)=0);
'
' 2020-05-29. Gustav Brock, Cactus Data ApS, CPH.
'
Public Function RowNumber( _
ByVal Key As String, _
Optional ByVal GroupKey As String, _
Optional ByVal Reset As Boolean) _
As Long
' Uncommon character string to assemble GroupKey and Key as a compound key.
Const KeySeparator As String = "¤§¤"
' Expected error codes to accept.
Const CannotAddKey As Long = 457
Const CannotRemoveKey As Long = 5
Static Keys As New Collection
Static GroupKeys As New Collection
Dim Count As Long
Dim CompoundKey As String
On Error GoTo Err_RowNumber
If Reset = True Then
' Erase the collection of keys and group key counts.
Set Keys = Nothing
Set GroupKeys = Nothing
Else
' Create a compound key to uniquely identify GroupKey and its Key.
' Note: If GroupKey is not used, only one element will be added.
CompoundKey = GroupKey & KeySeparator & Key
Count = Keys(CompoundKey)
If Count = 0 Then
' This record has not been enumerated.
'
' Will either fail if the group key is new, leaving Count as zero,
' or retrieve the count of already enumerated records with this group key.
Count = GroupKeys(GroupKey) + 1
If Count > 0 Then
' The group key has been recorded.
' Remove it to allow it to be recreated holding the new count.
GroupKeys.Remove (GroupKey)
Else
' This record is the first having this group key.
' Thus, the count is 1.
Count = 1
End If
' (Re)create the group key item with the value of the count of keys.
GroupKeys.Add Count, GroupKey
End If
' Add the key and its enumeration.
' This will be:
' Using no group key: Relative to the full recordset.
' Using a group key: Relative to the group key.
' Will fail if the key already has been created.
Keys.Add Count, CompoundKey
End If
' Return the key value as this is the row counter.
RowNumber = Count
Exit_RowNumber:
Exit Function
Err_RowNumber:
Select Case Err
Case CannotAddKey
' Key is present, thus cannot be added again.
Resume Next
Case CannotRemoveKey
' GroupKey is not present, thus cannot be removed.
Resume Next
Case Else
' Some other error. Ignore.
Resume Exit_RowNumber
End Select
End Function
The full story can be found in my project VBA.RowNumbers.
I am new in vb.net. i have a database and i am running a search query of employee records when search button clicked and display that information in textbox, however when a user is not in the database, the search output is displaying the information of the previous search. the information in textbox should display blank or say "No record found" if the user is not in the record. not sure what is wrong in my code.
Try
myConnection.Open()
Dim str As String
str = "SELECT * FROM tblEmp WHERE (EmpID = '" & ADS.UserEmpID & "')"
Dim cmd As OleDbCommand = New OleDbCommand(str, myConnection)
dr = cmd.ExecuteReader
While dr.Read
If dr.HasRows > 0 Then
MessageBox.Show("user already in the system", "Warning", MessageBoxButtons.OK)
ElseIf dr.HasRows = 0 Then
MessageBox.Show("Not Onboarded", "Warning", MessageBoxButtons.OK)
End If
BGC1 = dr("PreStartChecks").ToString
BGC2 = dr("EmpName").ToString
myConnection.Close()
End While
Catch ex As Exception
MsgBox("Unable to Connect to BGC DB. You may not have access or DB not available." &
ex.ToString)
End Try
Your While loop and If statement don't make sense. Firstly, HasRows is type Boolean so testing whether it is greater than zero is nonsensical. Secondly, Read returns False if there are no rows so the only way you can get to that If statement is if there is at least one row to read so testing HasRows when you already know that there are rows is also nonsensical. The proper option here is to use just an If statement and test Read only:
If dr.Read() Then
'There is a row to read and it was just read, so you can now get the data from the reader.
Else
'There is no row to read.
End If
If you want to clear a control when there's no data, you do so in the Else block.
The "rules" about when and how to use HasRows and Read are very simple and logical:
If all you care about is whether the query result set contains data or not but you don't care what that data is, just use an If statement to test HasRows. The HasRows property is type Boolean so there's no need to compare it to anything. It already is True or False.
If there can only be zero or one row in the result set, just use an If statement to call Read and test the result. Again, it's type Boolean so there's no need to compare it to anything. If it returns True then you can access the data for the row you just read.
If there can be multiple rows and you don't want to do anything special if there are no rows then just use a While or Do While loop to call Read and access the row that was just read inside the loop.
If there can be multiple rows and you do want to do something special if there are no rows, use an If statement to test HasRows and then a While or Do While loop inside the If block to call Read. You would handle the case where there are no rows in the Else block.
Assuming that txtBGC1 and txtBGC2 are TextBoxes, you could do something like this, assuming that the query can at most return one employee
...
If dr.Read Then ' There is an employee
txtBGC1.Text = dr("PreStartChecks").ToString
txtBGC2.Text = dr("EmpName").ToString
Else ' There is no employee
txtBGC1.Text = ""
txtBGC2.Text = "No record found"
End If
myConnection.Close()
I have an employee table with has name, age, city as columns. I want to display a column at run-time for my row numbers starting from 1. I am using SQL in Access.
Call the following function from your query.
Public Function GetNextNum(str As String) As Long
num = num + 1
GetNextNum = num
End Function
The caveat is that you must have at least one parameter (even if you don't need one) otherwise the function only gets called once and returns 1 for all the rows.
Before running the query set the global variable num to 0.
You only need one function to obtain a very speedy and even "groupable" row counter with or without automatic reset of the counter.
See in-line comments for typical usage:
Public Function RowCounter( _
ByVal strKey As String, _
ByVal booReset As Boolean, _
Optional ByVal strGroupKey As String) _
As Long
' Builds consecutive RowIDs in select, append or create query
' with the possibility of automatic reset.
' Optionally a grouping key can be passed to reset the row count
' for every group key.
'
' Usage (typical select query):
' SELECT RowCounter(CStr([ID]),False) AS RowID, *
' FROM tblSomeTable
' WHERE (RowCounter(CStr([ID]),False) <> RowCounter("",True));
'
' Usage (with group key):
' SELECT RowCounter(CStr([ID]),False,CStr[GroupID])) AS RowID, *
' FROM tblSomeTable
' WHERE (RowCounter(CStr([ID]),False) <> RowCounter("",True));
'
' The Where statement resets the counter when the query is run
' and is needed for browsing a select query.
'
' Usage (typical append query, manual reset):
' 1. Reset counter manually:
' Call RowCounter(vbNullString, False)
' 2. Run query:
' INSERT INTO tblTemp ( RowID )
' SELECT RowCounter(CStr([ID]),False) AS RowID, *
' FROM tblSomeTable;
'
' Usage (typical append query, automatic reset):
' INSERT INTO tblTemp ( RowID )
' SELECT RowCounter(CStr([ID]),False) AS RowID, *
' FROM tblSomeTable
' WHERE (RowCounter("",True)=0);
'
' 2002-04-13. Cactus Data ApS. CPH
' 2002-09-09. Str() sometimes fails. Replaced with CStr().
' 2005-10-21. Str(col.Count + 1) reduced to col.Count + 1.
' 2008-02-27. Optional group parameter added.
' 2010-08-04. Corrected that group key missed first row in group.
Static col As New Collection
Static strGroup As String
On Error GoTo Err_RowCounter
If booReset = True Then
Set col = Nothing
ElseIf strGroup <> strGroupKey Then
Set col = Nothing
strGroup = strGroupKey
col.Add 1, strKey
Else
col.Add col.Count + 1, strKey
End If
RowCounter = col(strKey)
Exit_RowCounter:
Exit Function
Err_RowCounter:
Select Case Err
Case 457
' Key is present.
Resume Next
Case Else
' Some other error.
Resume Exit_RowCounter
End Select
End Function
You have 5 methods available.
Reports only - Running Sum
If you are using this information for Access reports, there is an easy way that requires no VBA or fancy SQL. Simply add a textbox with control source set =1 then set Running Sum to Over All, done.
The rest of methods listed below applies to forms/datasheets/recordsets
Correlated subquery
You can do a correlated subquery. This solution is totally self-contained but is not very generic. It would be something similar to this:
SELECT
(
SELECT COUNT(*)
FROM Employees AS x
WHERE x.EmployeeID <= e.EmployeeID
ORDER BY x.EmployeeID
) AS RowNumber,
e.EmployeeID
FROM Employees AS e;
Note that because of the correlated subqueries, the performance will rapidly decrease as the amount of records increase in the table. You might have to customize the ORDER BY clause to get the desired number assignment if it's not supposed to depend on EmployeeID but something else (e.g. HireDate for instance)
VBA Function to maintain count, forward-only recordset
This method can perform much faster but can be only used once; and certainly not within forms/datasheets because VBA functions are continually evaluated as you navigate around. Thus, this is only appropriate when reading recordset in a forward-only manner. Using a standard VBA module:
Private Counter As Long
Public Function ResetRowNumber() As Boolean
Counter = 0
ResetRowNumber = (Counter = 0)
End Function
Public Function GetRowNumber(PrimaryKeyField As Variant) As Long
Counter = Counter + 1
GetRowNumber = Counter
End Function
To then use in a query:
SELECT
GetRowNumber([EmployeeID]) AS RowNumber,
EmployeeID
FROM Employees
WHERE ResetRowNumber();
Note the trick of using WHERE to implicitly call the ResetRowNumber function first. Please note this will work only as long there is only one query active; having multiple queries that takes row numbers will cause incorrect results. However the implementation is very simple and much faster.
VBA Function to maintain count and preserve the assignment
This is more expensive than the previous method but still can be cheaper than the correlated subquery solution for a sufficiently large table. This has the advantage of being useful in a form / datasheet because once number are given out, it is given out again. Again, in a standard VBA module:
Private NumberCollection As VBA.Collection
Public Function ResetRowNumber() As Boolean
NumberCollection = New VBA.Collection
ResetRowNumber = (NumberCollection.Count = 0)
End Function
Public Function GetRowNumber(PrimaryKeyField As Variant) As Variant
On Error Resume Next
Dim Result As Long
Result = NumberCollection(CStr(PrimaryKeyField))
If Err.Number Then
Result = 0
Err.Clear
End If
If Result Then
GetRowNumber = Result
Else
NumberCollection.Add NumberCollection.Count + 1, CStr(PrimaryKeyField)
GetRowNumber = NumberCollection.Count
End If
If Err.Number Then
GetRowNumber = "#Error " & Err.Description
End If
End Function
It's important that the input parameter PrimaryKeyValue references a non-nullable column (which a primary key column should be by definition). Otherwise, we'd have no way of knowing which number we should give out if it's already been given out to the record. The SQL is similar as previous method:
SELECT
GetRowNumber([EmployeeID]) AS RowNumber,
EmployeeID
FROM Employees
WHERE ResetRowNumber();
As with previous method, this is only good for one query at a time. If you need multiple queries, then you need twice the layer; a collection to reference which query's collection, then to inspect that query's collection. That might get a bit hairy. You might be also able to get more bang with a Scripting.Dictionary, so that's an alternative looking into.
Note also that the function now returns Variant due to the fact that it may encounter unexpected errors. Because the function can get called several times, potentially hundreds or even thousands of time, we can't pop open a message box, so we can mimic what built-in functions do and return a #Error, which is incompatible with the underlying type of Long we're really using.
Upgrade to SQL Server or other RDBMS
Access is a phenomenal RAD tool for building data-centric application. However, you are not necessarily tied to using its database engine. You could just migrate your data to one of free RDBMS, link using ODBC and continue to use your Access application as before, and get to benefit the power of SQL, including the window function ROW_NUMBER() that makes this much easier to achieve than VBA. If you are looking at doing more than just getting a row number, you might need to consider if you should migrate your data to a different database engine.
For additional references, this may be helpful.
I have a SQL script with a loop. In each iteration I change the where clause. So I get several selects displayed. But my .net program reads only the first select. The SQL script works in ssms.
This is my SQL script
while (#aktuellParam <= #countParam)
Begin
SELECT name1,
name2
FROM dbo.tableName
WHERE name like #var
SET #aktuellParam = aktuellParam+1
END
This is my vb.net code
Using reader As SqlClient.SqlDataReader = _server.ConnectionContext.ExecuteReader(script)
Dim lfdnr As Integer = 1
Do While reader.Read()
spaltenListe.Add(New ISpalten With
{
.Name1= reader.GetString(reader.GetOrdinal("name1")),
.Name2= reader.GetString(reader.GetOrdinal("name2"))
})
lfdnr = lfdnr + 1
Loop
reader.Close()
End Using
That's because subsequent selects are actually in a different result set. Your reader needs to do a .NextResult after each read.
I have an employee table with has name, age, city as columns. I want to display a column at run-time for my row numbers starting from 1. I am using SQL in Access.
Call the following function from your query.
Public Function GetNextNum(str As String) As Long
num = num + 1
GetNextNum = num
End Function
The caveat is that you must have at least one parameter (even if you don't need one) otherwise the function only gets called once and returns 1 for all the rows.
Before running the query set the global variable num to 0.
You only need one function to obtain a very speedy and even "groupable" row counter with or without automatic reset of the counter.
See in-line comments for typical usage:
Public Function RowCounter( _
ByVal strKey As String, _
ByVal booReset As Boolean, _
Optional ByVal strGroupKey As String) _
As Long
' Builds consecutive RowIDs in select, append or create query
' with the possibility of automatic reset.
' Optionally a grouping key can be passed to reset the row count
' for every group key.
'
' Usage (typical select query):
' SELECT RowCounter(CStr([ID]),False) AS RowID, *
' FROM tblSomeTable
' WHERE (RowCounter(CStr([ID]),False) <> RowCounter("",True));
'
' Usage (with group key):
' SELECT RowCounter(CStr([ID]),False,CStr[GroupID])) AS RowID, *
' FROM tblSomeTable
' WHERE (RowCounter(CStr([ID]),False) <> RowCounter("",True));
'
' The Where statement resets the counter when the query is run
' and is needed for browsing a select query.
'
' Usage (typical append query, manual reset):
' 1. Reset counter manually:
' Call RowCounter(vbNullString, False)
' 2. Run query:
' INSERT INTO tblTemp ( RowID )
' SELECT RowCounter(CStr([ID]),False) AS RowID, *
' FROM tblSomeTable;
'
' Usage (typical append query, automatic reset):
' INSERT INTO tblTemp ( RowID )
' SELECT RowCounter(CStr([ID]),False) AS RowID, *
' FROM tblSomeTable
' WHERE (RowCounter("",True)=0);
'
' 2002-04-13. Cactus Data ApS. CPH
' 2002-09-09. Str() sometimes fails. Replaced with CStr().
' 2005-10-21. Str(col.Count + 1) reduced to col.Count + 1.
' 2008-02-27. Optional group parameter added.
' 2010-08-04. Corrected that group key missed first row in group.
Static col As New Collection
Static strGroup As String
On Error GoTo Err_RowCounter
If booReset = True Then
Set col = Nothing
ElseIf strGroup <> strGroupKey Then
Set col = Nothing
strGroup = strGroupKey
col.Add 1, strKey
Else
col.Add col.Count + 1, strKey
End If
RowCounter = col(strKey)
Exit_RowCounter:
Exit Function
Err_RowCounter:
Select Case Err
Case 457
' Key is present.
Resume Next
Case Else
' Some other error.
Resume Exit_RowCounter
End Select
End Function
You have 5 methods available.
Reports only - Running Sum
If you are using this information for Access reports, there is an easy way that requires no VBA or fancy SQL. Simply add a textbox with control source set =1 then set Running Sum to Over All, done.
The rest of methods listed below applies to forms/datasheets/recordsets
Correlated subquery
You can do a correlated subquery. This solution is totally self-contained but is not very generic. It would be something similar to this:
SELECT
(
SELECT COUNT(*)
FROM Employees AS x
WHERE x.EmployeeID <= e.EmployeeID
ORDER BY x.EmployeeID
) AS RowNumber,
e.EmployeeID
FROM Employees AS e;
Note that because of the correlated subqueries, the performance will rapidly decrease as the amount of records increase in the table. You might have to customize the ORDER BY clause to get the desired number assignment if it's not supposed to depend on EmployeeID but something else (e.g. HireDate for instance)
VBA Function to maintain count, forward-only recordset
This method can perform much faster but can be only used once; and certainly not within forms/datasheets because VBA functions are continually evaluated as you navigate around. Thus, this is only appropriate when reading recordset in a forward-only manner. Using a standard VBA module:
Private Counter As Long
Public Function ResetRowNumber() As Boolean
Counter = 0
ResetRowNumber = (Counter = 0)
End Function
Public Function GetRowNumber(PrimaryKeyField As Variant) As Long
Counter = Counter + 1
GetRowNumber = Counter
End Function
To then use in a query:
SELECT
GetRowNumber([EmployeeID]) AS RowNumber,
EmployeeID
FROM Employees
WHERE ResetRowNumber();
Note the trick of using WHERE to implicitly call the ResetRowNumber function first. Please note this will work only as long there is only one query active; having multiple queries that takes row numbers will cause incorrect results. However the implementation is very simple and much faster.
VBA Function to maintain count and preserve the assignment
This is more expensive than the previous method but still can be cheaper than the correlated subquery solution for a sufficiently large table. This has the advantage of being useful in a form / datasheet because once number are given out, it is given out again. Again, in a standard VBA module:
Private NumberCollection As VBA.Collection
Public Function ResetRowNumber() As Boolean
NumberCollection = New VBA.Collection
ResetRowNumber = (NumberCollection.Count = 0)
End Function
Public Function GetRowNumber(PrimaryKeyField As Variant) As Variant
On Error Resume Next
Dim Result As Long
Result = NumberCollection(CStr(PrimaryKeyField))
If Err.Number Then
Result = 0
Err.Clear
End If
If Result Then
GetRowNumber = Result
Else
NumberCollection.Add NumberCollection.Count + 1, CStr(PrimaryKeyField)
GetRowNumber = NumberCollection.Count
End If
If Err.Number Then
GetRowNumber = "#Error " & Err.Description
End If
End Function
It's important that the input parameter PrimaryKeyValue references a non-nullable column (which a primary key column should be by definition). Otherwise, we'd have no way of knowing which number we should give out if it's already been given out to the record. The SQL is similar as previous method:
SELECT
GetRowNumber([EmployeeID]) AS RowNumber,
EmployeeID
FROM Employees
WHERE ResetRowNumber();
As with previous method, this is only good for one query at a time. If you need multiple queries, then you need twice the layer; a collection to reference which query's collection, then to inspect that query's collection. That might get a bit hairy. You might be also able to get more bang with a Scripting.Dictionary, so that's an alternative looking into.
Note also that the function now returns Variant due to the fact that it may encounter unexpected errors. Because the function can get called several times, potentially hundreds or even thousands of time, we can't pop open a message box, so we can mimic what built-in functions do and return a #Error, which is incompatible with the underlying type of Long we're really using.
Upgrade to SQL Server or other RDBMS
Access is a phenomenal RAD tool for building data-centric application. However, you are not necessarily tied to using its database engine. You could just migrate your data to one of free RDBMS, link using ODBC and continue to use your Access application as before, and get to benefit the power of SQL, including the window function ROW_NUMBER() that makes this much easier to achieve than VBA. If you are looking at doing more than just getting a row number, you might need to consider if you should migrate your data to a different database engine.
For additional references, this may be helpful.