VB.NET OleDB MSAccess GetOleDBSchema returns incorrect number of Queries (Procedures) - vb.net

I have a perplexing problem that I have been grappling with for many hours to no avail.
Our (general audience) application compares queries in the latest version of our MS-Access database with end-users currently installed versions and and updates/syncs. the DB accordingly. Although this functionality has been working, it is now broken. I have resorted to the simplest test to demonstrate the behavior. OLEDB is NOT returning the correct number of queries.
After opening the Access 2007 database, I display the location and correct number of queries:
?CurrentDB.Name
C:\Users\Ron\Documents\Database4.accdb
?CurrentDB.QueryDefs.Count
1
The following simple form calls the OleDbSchemaTable method, but returns the wrong number of rows / queries (=0):
Public Class Form1
Private ConnStr As String = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\Users\Ron\Documents\Database4.accdb"
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
Dim dbo As DataTable = GetSchemaInfo()
Debug.Print("Number of queries=" & dbo.Rows.Count)
End Sub
Private Function GetSchemaInfo()
Try
Dim db As DataTable
Using conn = New OleDb.OleDbConnection(ConnStr)
conn.Open()
db = conn.GetOleDbSchemaTable(System.Data.OleDb.OleDbSchemaGuid.Procedures, Nothing)
End Using
Return db
Catch ex As Exception
MsgBox(ex.Message)
Return Nothing
End Try
End Function
End Class
Immediate Window:
Number of queries=0
I have tried everything I can think of (Compact/Repair, running on a different machine). Unfortunately, I have only one copy of Access 2007 to test with. Could this be a virus?
Any and all ideas are appreciated.

There are 2 types of saved queries.
Simple SELECT queries which could be created with CREATE VIEW DDL statements.
All other queries. These can not be created with CREATE VIEW but could be with CREATE PROCEDURE. Included within this category are: "action queries" (insert, update, delete, "make table"); parameter queries; simple SELECT queries which include an ORDER BY clause; and perhaps more I can't recall at the moment.
Schema views include the first type. Schema procedures include the second type. QueryDefs.Count gives you the count of all saved queries, which includes both types.
Try the VBA procedure below in your database. With my database, I get this output in the Immediate window:
QueryDefs.Count: 66
Views: 34
Procedures: 32
Unfortunately I don't know how to translate this VBA to Dot.Net. Perhaps that doesn't matter. I just want to emphasize that QueryDefs.Count should be the count of views plus the count of procedures. And it looks to me like your code asks for procedures only.
Public Sub CountQueries()
Const adSchemaProcedures = 16
Const adSchemaViews = 23
Dim cn As Object
Dim rs As Object
Dim i As Long
Debug.Print "QueryDefs.Count: " & CurrentDb.QueryDefs.Count
Set cn = CurrentProject.Connection
Set rs = cn.OpenSchema(adSchemaViews)
i = 0
With rs
Do While Not .EOF
i = i + 1
'Debug.Print !TABLE_NAME '
.MoveNext
Loop
.Close
End With
Debug.Print "Views: " & CStr(i)
Set rs = cn.OpenSchema(adSchemaProcedures)
i = 0
With rs
Do While Not .EOF
i = i + 1
'Debug.Print !PROCEDURE_NAME '
.MoveNext
Loop
.Close
End With
Debug.Print "Procedures: " & CStr(i)
Set rs = Nothing
Set cn = Nothing
End Sub

There are two types of schemas for MS Access, schema views and schema procedures. Procedures are action queries, views are select queries. So you need
OleDbSchemaGuid.Views
Note that the OleDbSchemaGuid returns the wrong data type for memo fields.

Related

How to add a column in a SQL expression using a user defined function through VB net (odbc connesction)

I am new to VB.net and I would like to convert and display UnixStamp data in a new column.
Variant DatGridView = datasource is Dataset.
I can create empty columns within an SQL query (DataGridView_Dataset), unfortunately I can't use direct data conversion into new columns using my own function. Error see.SQL Error Code
The function works independently. see. Working UnixStampFunction
I got the result of 56,000 sentences in 7 seconds, without getting date and time values from UnixTimeStamp
Is there a solution for using udf in SQL Statement?
DataGridView variant - odbcExecuteRead Solution
Using the given code is not a problem to display eg 10 sentences (10 sentences result), but if the records are more than about 100 (around 50 thousand by month), an error like this will be displayed (Managed Debug Helper ContextSwitchDeadlock: The module CLR could not go out of context COM 0xb45680 to context 0xb455c8 for 60 seconds.).
Unchecking the ContextSwitchDeadlock option by
Debug > Windows > Exception Settings in VS 2019
I got the result of 56 000 record in awfull 228 seconds.
Is it possible to optimize the code or is it possible to use another solution?
Code:
Public Class Form1
Public strDateTime, strDate, strTime As String
Public x As Integer =0
Public y As Integer =0
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Try
Dim dgvDT As New DataGridView
Dim odbcConn As OdbcConnection
Dim odbcComm As OdbcCommand
Dim odbcAdapter As New Odbc.OdbcDataAdapter
Dim odbcDataset As New DataSet
Dim odbcDataTable As DataTable
Dim strConn, strSQL, strSQL1 As String
dgvDT.Location = New Point(382, 2)
dgvDT.Width = 360
dgvDT.Height = 600
Me.Controls.Add(dgvDT)
strConn = "Driver=Firebird/InterBase(r) driver;User=;Password=;DataSource=...." '
odbcConn = New OdbcConnection(strConn)
odbcConn.Open()
strSQL = "SELECT TEST.UNIXTIMESTAMP, " & "'dd.mm.yyyy" & "'" & "AS Date_ , " & "'hh:mm:ss" & "'" & "AS Time_ " _
& "From TEST " _
& "Where TEST.UNIXTIMESTAMP > 1646092800 " _ '1.3.2022
& "Order By TEST.ID "
strSQL1 = "SELECT TEST.UNIXTIMESTAMP, UnixTimestampToDateOrTime(TEST.UNIXTIMESTAMP,1) As Date_, " & "'hh:mm:ss" & "'" & "AS Time_ " _
& "From TEST " _
& "Where TEST.UNIXTIMESTAMP > 1646092800 " _ '1.3.2022
& "Order By TEST.ID "
odbcComm = New OdbcCommand(strSQL, odbcConn)
'odbcComm = New OdbcCommand(strSQL1, odbcConn)
odbcAdapter.SelectCommand() = odbcComm
odbcAdapter.Fill(odbcDataset, "TEST")
odbcDataTable = odbcDataset.Tables("TEST")
dgvDT.DataSource = odbcDataTable
dgvDT.Columns(0).HeaderText = "UnixTimeStamp"
dgvDT.Columns(1).HeaderText = "Date"
dgvDT.Columns(2).HeaderText = "Time"
dgvDT.Visible = True
Catch ex As Exception
MessageBox.Show("Error: " & ex.Message, "Error")
End Try
End Sub
Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
Try
Dim dgvDT1 As New DataGridView
Dim odbcConn1 As OdbcConnection
Dim odbcComm1 As OdbcCommand
Dim odbcDR As OdbcDataReader
Dim x As Integer = 0
Dim y As Integer = 0
Dim strConn1, strSQL, strSQL2 As String
dgvDT1.Location = New Point(382, 2)
dgvDT1.Width = 360
dgvDT1.Height = 600
For i As Integer = 0 To 2
Dim dgvNC As New DataGridViewTextBoxColumn
dgvNC.Name = "Column" & i.ToString
dgvDT1.Columns.Add(dgvNC)
Next
dgvDT1.Columns(0).HeaderText = "UnixTimeStamp"
dgvDT1.Columns(1).HeaderText = "Date"
dgvDT1.Columns(2).HeaderText = "Time"
dgvDT1.ReadOnly = True
dgvDT1.AllowUserToAddRows = False
dgvDT1.AllowUserToDeleteRows = False
strSQL2 = "SELECT TEST.UNIXTIMESTAMP " _
& "From TEST " _
& "Where TEST.UNIXTIMESTAMP > 1646092800 " _
& "Order By TEST.ID "
strConn1 = "Driver=Firebird/InterBase(r) driver;User=;Password=;DataSource="
odbcConn1 = New OdbcConnection(strConn1)
odbcConn1.Open()
odbcComm1 = New OdbcCommand(strSQL2, odbcConn1)
odbcDR = odbcComm1.ExecuteReader()
While (odbcDR.Read()) 'And y <= 10
dgvDT1.Rows.Add()
dgvDT1.Rows(y).Cells("Column0").Value = (odbcDR.GetValue(0).ToString())
dgvDT1.Rows(y).Cells("Column1").Value = (UnixTimestampToDateOrTime(odbcDR.GetValue(0), 1))
dgvDT1.Rows(y).Cells("Column2").Value = (UnixTimestampToDateOrTime(odbcDR.GetValue(0), 2))
y = y + 1
End While
Me.Controls.Add(dgvDT1)
dgvDT1.Visible = True
Catch ex As Exception
MessageBox.Show("Error: " & ex.Message, "Error")
End Try
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
MsgBox(UnixTimestampToDateOrTime(1646092800, 1) & vbNewLine & UnixTimestampToDateOrTime(1646092800, 2))
End Sub
Public Function UnixTimestampToDateOrTime(ByVal _UnixTimeStamp As Long, ByRef _Parameter As Integer) As String
strDateTime = New DateTime(1970, 1, 1, 0, 0, 0).AddSeconds(_UnixTimeStamp).ToString()
strDate = strDateTime.Substring(0, strDateTime.IndexOf(" "))
strTime = strDateTime.Substring(strDateTime.IndexOf(" "), strDateTime.Length - strDateTime.IndexOf(" "))
If _Parameter = 1 Then
Return (strDate)
Else
Return (strTime)
End If
End Function
End Class
You write:
I would like to convert and display UnixStamp data in a new column.
Yes you can. But i do not believe that would make your applicatio nany faster.
However I would outline the strategy i personally would had used getting data conversion without UDF.
Removing UDF, i believe, would enhance your database in general. But would not fix performance problems. Due to amount of information i could not format it as mere comment.
So, about removing UDF from the query.
Link to Firebird 2.5 documentation: https://www.firebirdsql.org/file/documentation/html/en/refdocs/fblangref25/firebird-25-language-reference.html#fblangref25-psql-triggers
Link to Firebird 3 documentation:
https://www.firebirdsql.org/file/documentation/html/en/refdocs/fblangref30/firebird-30-language-reference.html
The key difference would be that FB3+ has "stored functions" and even DETERMINISTIC ones, and FB2 - only "stored procedures".
I would avoid using UDFs here: they are declared obsolete, they make deployment more complex, and they would reduce your flexibiltiy (for example, you can not use Firebird for Linux/ARM if you only have UDF DLL for Win64)
So, the approach i speculate, you can use.
You would have to find an algorythm, how to "parse" UNIX date/time into separate values for day, month, etc.
You would have to implement that algorythm in Procedural SQL stored function (in FB3, make it DETERMINISTIC) or stored procedure (FB2, make it selectable by having SUSPEND command, after you set value for the output parameter and before exit) - see the Ch. 7 of the manual.
For assembling values of DATE or TIME or DATETIME types you would have to use integer to string (VARCHAR) to resulting type coersion, see Ch. 3.8 of the manual.
Example:
select
cast( 2010 || '-' || 2 || '-' || 11 AS DATE ),
cast( 23 || ':' || 2 || ':' || 11 AS TIME )
from rdb$database
CAST | CAST
:--------- | :-------
2010-02-11 | 23:02:11
db<>fiddle here
You would have add the converted columns to your table. Those columns qwould be read-only, but they would change "unix time" to Firebird native date and/or type.
Read Ch. 5.4.2. of the manual, about ALTER TABLE <name> ADD <column> command.
Read 5.4.1. CREATE TABLE of the manual about Calculated fields ( columns, declared using COMPUTED BY ( <expression> ) instead of actual datatype. Those columns you would have to creat, and here is where difference between FB3 and FB2 kicks in.
in FB3 i believe you would be able to directly use your PSQL Function as the expression.
in FB2 i believe you would have to use a rather specific trampoline, to coerce a stored procedure into expression:
ALTER TABLE {tablename}
ADD {columnname} COMPUTED BY
(
(
SELECT {sp_output_param_name} FROM {stored_proc_name}( {table_unixtime_column} )
)
)
In you Visual Basic application you would read those read-only converted columns instead of original value columns
Now, this would address the perfomance problem. Again, writing this aas an answer, because it is too large to fit as a comment.
No, while UDF has a number of problems - those are not about performance, but about being "old-school" and prone to low-level problems, such as memory leaks. UDFs can trigger problems if used in other places in query, by prohibiting SQL optimizer to use fast, indexed codepaths, but it is not your case. You only use your UDF in the result columns list of SELECT - and here should be no prerformance troubles.
Your application/database therefore should have other bottlenecks.
first of all, you use Where TEST.UNIXTIMESTAMP > 1646092800 and Order By TEST.ID in your query. The obvious question is if your table does have index on those columns. If not, you force the database server to do full natural scan to apply where condition, and then use external, temporary file sorting. THAT can be really slow, and can scale badly as the table grows.
Use your database design tool of choice to check query plan of your select.
Does Firebird use indexed or non indexed access paths.
There are articles online how to read Firebird's query. I don't instantly know English language ones though. Also, it would be specific to your database design tool, how to get it.
Sorry, there is no silver bullet. That is where learning about databases is required.
Read Data Definition Language / Create Index chapter of the documentation.
Link to Firebird 2.5 documentation: https://www.firebirdsql.org/file/documentation/html/en/refdocs/fblangref25/firebird-25-language-reference.html#fblangref25-psql-triggers
Link to Firebird 3 documentation: https://www.firebirdsql.org/file/documentation/html/en/refdocs/fblangref30/firebird-30-language-reference.html
Check your database structure, if UNIXTIMESTAMP and ID have index on them or not. In general, indices speed some (not all) reading queries and slow down (slightly) all writing queries.
You may decide you want to add those indices if they do not exist yet.
Again, it would be dependent upon your database design tool, how to check for existing of the indexes. It also would depend on your data and your applications which kind of indexes is needed or not. That is not what someone else can decide for you.
i also have a lot of suspicion about odbcAdapter.Fill(odbcDataset, "TEST") command. Basically, you try to read all the data in one go. And you do it via ODBC connection, that is not natural for C#.
Usually desktop application only read first 100 or so rows. People would rarely actually read anything after first page or two. Humans are not machines.
Try to somehow connect your visual grid to the select query without reading ALL the table. There should be way.
Additionally, there is free Firebird .Net Provider - this should work natively with VB.Net and should be your first choice, not ODBC.
There also is commercial IBProvider, based on native OLE DB technology it should be worse choice than .Net Provider, but it can work too and has some code examples for VB.Net, and i suppose it is still better mantained than ODBC driver.
you may also change Firebird configuration and allow it using more RAM for cache. This may somewhat relax problems of index-less selecting and sorting. But only somewhat relax and offset, not solve. You can find articles about it on www.ib-aid.com

In MS Access, is it possible to add an updatable blank field to a query to calculate it's values programmatically in VBA?

In MS Access, in order to prevent nesting queries, because it has become really slow, I'm trying to calculate programmatically in VBA all calculated fields for my queries using a Loop. In general, the idea is to stop using calculated fields on queries at all, to perform all calculations from the backend.
I am having problems showing the values in a TextBox in a Continuous Form. I'm trying to create a query in VBA with an empty field and then update the field with the calculated values, so then I can set the ControlSource of the textbox to that field for it to show properly. The problem is that since the empty field is a calculated field, I cannot update it's value. Is there any other way to add a field to a query that remains empty and updatable until I can calculate it's values?
An alternative solution would be to create a temporal table instead of a query and update the values, but I would really prefer not to create temporal tables every time an user needs to do a query with calculated fields (especially nested ones).
Is there a way to accomplish this?
Here is what I tried:
Private Sub Form_Load()
Dim SQLQ As string
Dim rs As DAO.Recordset
Dim dbs As DAO.Database
Dim qrydef As DAO.QueryDef
Dim i As Integer
'calcField1 is not a member of Items, and it's intended to be updated later on
SQLQ = "SELECT Items.Price, calcField1 FROM Items;"
Set dbs = CurrentDb()
Set qrydef = dbs.CreateQueryDef("testqry", SQLQ)
'Field is prompted for a value when the query is called and has no source
qrydef!calcField1 = vbNullString
Set rs = qrydef.OpenRecordset()
i = 0
Do Until rs.EOF
rs.Edit
rs!calcField1 = rs!Price *2 'Error here because calcField1 is a Calculated Field
rs.Update
i = i + 1
rs.MoveNext
Loop
Me.TextBox1.ControlSource = "=Price"
Me.TextBox2.ControlSource = "=calcField1"
Set Me.Recordset = rs
Me.Refresh
End Sub
I think it is better to create another table where you can insert the new calculated record with a reference to the original table so they are linked.
for example:
table 1:
item_id, item_desc, price
table 2:
item_id, calculated_value
then, the calculated value can be inserted or modified depending on the purpose of your calculation process.
Can you add the calculated field into a query and use that as the Record Source?
Private Sub Form_Load()
Dim strSQL As string
'calcField1 is calculated
strSQL = "SELECT Items.Price, (Items.Price * 2) AS calcField1 FROM Items;"
Me.TextBox1.ControlSource = "=Price"
Me.TextBox2.ControlSource = "=calcField1"
Set Me.RecordSource = SQLQ
Me.Refresh
End Sub
or if the logic is more complex you can put it into a function
'in the form
Private Sub Form_Load()
Dim strSQL As string
'calcField1 is computed by a function
strSQL = "SELECT Items.Price, MyFunction(Items.Price) AS calcField1 FROM Items;"
Me.TextBox1.ControlSource = "=Price"
Me.TextBox2.ControlSource = "=calcField1"
Set Me.RecordSource = strSQL
Me.Refresh
End Sub
'in a module
Public Function MyFunction(dbl As Double) As Double
MyFunction = dbl * 2
End Function

comparing a table and a query

I have a form, a table ("my_table") and a query ("my_query").
I want a function to look if any of the table's record's IDs ("my_ID") match with the IDs of the query (also "my_ID") to update a field ("my_Property") of given record with a value from the form.
I copied and modified this code. My code causes an error (have to translate): "Runtime error '3061': 1 parameter was expected, but too few parameters were passed."
I think the issue is that in the if loop I don't compare tbl.Fields("my_ID") with a discreet value but with a set values. Maybe I should also iterate through all values of qry.Fields("my_ID") but I don't see how to do the code. Also, this would significantly slow down the already slow process since my_Table contains more than 40,000 records. Is there a faster method of comparing the ids?
Private Sub btn_Click()
Dim db As Database
Set db = CurrentDb
Dim tbl As Recordset
Set tbl = db.OpenRecordset("my_Table")
Dim qry As QueryDef
Set qry = db.OpenRecordset("my_Query")
tbl.MoveFirst
Do Until tbl.EOF
If tbl.Fields("my_ID") = qry.Fields("my_ID") Then
tbl.Edit
tbl.Fields("my_Property") = Me!textbox1.Value
tbl.Update
End If
tbl.MoveNext
Loop
End Sub
As #Harassed Dad and #Andre suggested I simply copied "my_query" and changed the copy to an update query "my_updatequery" with the update field of "my_Property" set to
[Forms]![My_Form]![tbx_my_Value]. Then I changed the code for the button to:
Private Sub btn_Click()
DoCmd.OpenQuery "my_updatequery"
Forms.my_Form.Subform2.Requery
End Sub
Forms.my_Form.Subform2.Requery updates basically a subform showing the "my_query"-query.
I will look further for another way to call the update query to avoid the two prompts that opening an update query always entails.
edit:
To to avoid the propmts I simply turned Warnings off and on again:
Private Sub btn_Click()
DoCmd.SetWarnings False
DoCmd.OpenQuery "my_updatequery"
DoCmd.SetWarnings True
Forms.my_Form.Subform2.Requery
End Sub
Not the most elegant way but it will do the job.

ms access query with loop to create multiple tables

I have one table in oracle database - which will have two columns (project name, view name). In that table when you filter project name, we will get all view names related to that project, based on those view names again we need to write query like select * from projectname$viewaname; to fetch that view related data.
Doing this manually is taking long time for each project. So my idea is to create MS ACCESS database to create tables for selected project and export them as excel files to C:\temp folder.
I need your help to create multiple tables in one go (using query/passthrough query or any other option) in MS Access fetching data from oracle database.
For that I have created MS access file, created one linked table (in which i have project and view names).
After that I have created one form, using project field as combo box from linked table and updated settings like, this form should be opened at start-up.
When I open access file, automatically this form is opening and asking me to enter oracle database user id and password - after entering credentials, combo box is updating and I can select my project in that list.
After that, I have created one query using main table and applied filter condition based on the selection in the form. Now I got results like project and view name for the end user selected project.
I need your help like,
now we have data in table like below.
Project | Viewname
A | A1
A | A2
A | A3
A | A4
A | A5
SQL query to see individual view data is :
select * from projectname$view_name;
ex: select * from A$A1;
project name, view name and no of rows(views), columns in views are dynamic - will change based on project.
I need your help to create multiple tables(one per one view) dynamically - Please suggest me the best option.
Regards,
Murali
Consider iteratively looping through the project/view name query in a recordset and creating the pass-through query that then exports to an Excel spreadsheet.
Public Sub ImportOracleProjectViews()
Dim db As Database, rst As Recordset, qdef As QueryDef
Dim constr As String, strSQL As String, mkTBL As String
Set db = CurrentDb
' ENTER QUERY HOLDING PROJECT AND VIEW NAMES '
Set rst = db.OpenRecordset("QUERYNAME")
' DELETE TEMP QUERYDEF IF EXISTS '
For Each qdef In db.QueryDefs
If qdef.Name = "temp" Then
db.QueryDefs.Delete ("temp")
End If
Next qdef
If rst.RecordCount = 0 Then Exit Sub
rst.MoveLast: rst.MoveFirst
' LOOP THROUGH EACH RECORD OF RECORDSET '
Do While Not rst.EOF
' SQL STATEMENTS '
strSQL = "SELECT * FROM " & rst!projectname & "$" & rst!viewname
' ORACLE CONNECTION STRING '
constr = "ODBC;Driver={Microsoft ODBC for Oracle};Server=myServerAddress;" _
& "Uid=myUsername;Pwd=myPassword;"
' PASS THRU QUERY '
Set qdef = db.CreateQueryDef("temp")
qdef.Connect = constr
qdef.SQL = strSQL
qdef.Close
' EXPORT TO MS EXCEL SPREADSHEET '
DoCmd.TransferSpreadsheet acExport, acSpreadsheetTypeExcel12Xml, temp, _
"C:\Path\To\Output\Folder\" & rst!projectname & "_" & rst!viewname & ".xlsx", _
True
rst.MoveNext
Loop
rst.Close
Set rst = Nothing
Set qdef = Nothing
Set db = Nothing
MsgBox "Successfully imported views to local tables and " _
& "Excel spreadsheets!", vbInformation
End Sub
Resources
Open Recordset VBA
Method
Oracle Connections
Strings
Creating Pass-Through Query in VBA Code
You have asked multiple questions, so the answer is structured correspondingly:
In order to create MS Access Table using VBA refer to the following sample code snippet:
Public Sub CreateTableVBA()
Dim SQL As String
SQL = "CREATE TABLE ThisTable " & "(FirstName CHAR, LastName CHAR);"
DoCmd.RunSQL SQL
End Sub
In order to create multiple Tables you should have an array of Table names and corresponding array of SQL statements, like the one shown above. Then you can loop through the array using VBA For-Next code block, running DoCmd.RunSQL command.
Alternatively, instead of DoCmd.RunSQL you may use Execute() function on VBA Database object, like shown below:
Sub CreateTableXSQL()
Dim dbs As Database
' include the path to MyDb.mdb on your computer.
Set dbs = OpenDatabase([Path to MyDb.mdb])
' create a table SQL with two text fields.
dbs.Execute "CREATE TABLE ThisTable " & "(FirstName CHAR, LastName CHAR);"
dbs.Close
End Sub
Hope this may help.

Does MS access(2003) have anything comparable to Stored procedure. I want to run a complex query in MS acceess

I have a table, call it TBL. It has two columns,call them A and B. Now in the query I require one column as A and other column should be a comma seprated list of all B's which are against A in TBL.
e.g. TBL is like this
1 Alpha
2 Beta
1 Gamma
1 Delta
Result of query should be
1 Alpha,Gamma,Delta
2 Beta
This type of thing is very easy to do with cursors in stored procedure. But I am not able to do it through MS Access, because apparently it does not support stored procedures.
Is there a way to run stored procedure in MS access? or is there a way through SQL to run this type of query
You can concatenate the records with a User Defined Function (UDF).
The code below can be pasted 'as is' into a standard module. The SQL for you example would be:
SELECT tbl.A, Concatenate("SELECT B FROM tbl
WHERE A = " & [A]) AS ConcA
FROM tbl
GROUP BY tbl.A
This code is by DHookom, Access MVP, and is taken from http://www.tek-tips.com/faqs.cfm?fid=4233
Function Concatenate(pstrSQL As String, _
Optional pstrDelim As String = ", ") _
As String
'example
'tblFamily with FamID as numeric primary key
'tblFamMem with FamID, FirstName, DOB,...
'return a comma separated list of FirstNames
'for a FamID
' John, Mary, Susan
'in a Query
'(This SQL statement assumes FamID is numeric)
'===================================
'SELECT FamID,
'Concatenate("SELECT FirstName FROM tblFamMem
' WHERE FamID =" & [FamID]) as FirstNames
'FROM tblFamily
'===================================
'
'If the FamID is a string then the SQL would be
'===================================
'SELECT FamID,
'Concatenate("SELECT FirstName FROM tblFamMem
' WHERE FamID =""" & [FamID] & """") as FirstNames
'FROM tblFamily
'===================================
'======For DAO uncomment next 4 lines=======
'====== comment out ADO below =======
'Dim db As DAO.Database
'Dim rs As DAO.Recordset
'Set db = CurrentDb
'Set rs = db.OpenRecordset(pstrSQL)
'======For ADO uncomment next two lines=====
'====== comment out DAO above ======
Dim rs As New ADODB.Recordset
rs.Open pstrSQL, CurrentProject.Connection, _
adOpenKeyset, adLockOptimistic
Dim strConcat As String 'build return string
With rs
If Not .EOF Then
.MoveFirst
Do While Not .EOF
strConcat = strConcat & _
.Fields(0) & pstrDelim
.MoveNext
Loop
End If
.Close
End With
Set rs = Nothing
'====== uncomment next line for DAO ========
'Set db = Nothing
If Len(strConcat) > 0 Then
strConcat = Left(strConcat, _
Len(strConcat) - Len(pstrDelim))
End If
Concatenate = strConcat
End Function
I believe you can create VBA functions and use them in your access queries. That might help you.
There is not a way that I know of to run stored procedures in an Access database. However, Access can execute stored procedures if it is being used against a SQL backend. If you can not split the UI to Access and data to SQL, then your best bet will probably be to code a VBA module to give you the output you need.
To accomplish your task you will need to use code. One solution, using more meaningful names, is as follows:
Main table with two applicable columns:
Table Name: Widgets
Field 1: ID (Long)
Field 2: Color (Text 32)
Add table with two columns:
Table Name: ColorListByWidget
Field 1: ID (Long)
Field 2: ColorList (Text 255)
Add the following code to a module and call as needed to update the ColorListByWidget table:
Public Sub GenerateColorList()
Dim cn As New ADODB.Connection
Dim Widgets As New ADODB.Recordset
Dim ColorListByWidget As New ADODB.Recordset
Dim ColorList As String
Set cn = CurrentProject.Connection
cn.Execute "DELETE * FROM ColorListByWidget"
cn.Execute "INSERT INTO ColorListByWidget (ID) SELECT ID FROM Widgets GROUP BY ID"
With ColorListByWidget
.Open "ColorListByWidget", cn, adOpenForwardOnly, adLockOptimistic, adCmdTable
If Not (.BOF And .EOF) Then
.MoveFirst
Do Until .EOF
Widgets.Open "SELECT Color FROM Widgets WHERE ID = " & .Fields("ID"), cn
If Not (.BOF And .EOF) Then
Widgets.MoveFirst
ColorList = ""
Do Until Widgets.EOF
ColorList = ColorList & Widgets.Fields("Color").Value & ", "
Widgets.MoveNext
Loop
End If
.Fields("ColorList") = Left$(ColorList, Len(ColorList) - 2)
.MoveNext
Widgets.Close
Loop
End If
End With
End Sub
The ColorListByWidget Table now contains your desired information. Be careful that the list (colors in this example) does not exceed 255 characters.
No stored procedures, no temporary tables.
If you needed to return the query as a recordset, you could use a disconnected recordset.
Perhaps instead of asking if Jet has stored procedures, you should explain what you want to accomplish and then we can explain how to do it with Jet (it's not clear if you're using Access for your application, or just using a Jet MDB as your data store).
Well, you can use a Recordset object to loop through your query in VBA, concatenating field values based on whatever criteria you need.
If you want to return the results as strings, you'll be fine. If you want to return them as a query, that will be more complicated. You might have to create a temporary table and store the results in there so you can return them as a table or query.
You can use GetString in VBA which will return the recordset separated by any value you like (e.g. comma, dash, table cells etc.) although I have to admit I've only used it in VBScript, not Visual Basic. W3Schools has a good tutorial which will hopefully lend itself to your needs.
You can write your stored procedure as text and send it against the database with:
Dim sp as string
sp = "your stored procedure here" (you can load it from a text file or a memo field?)
Access.CurrentProject.AccessConnection.Execute sp
This supposes you are using ADODB objects (ActiveX data Objects Library is coorectly referenced in your app).
I am sure there is something similar with DAO ...
#Remou on DHookom's Concatenate function: neither the SQL standard nor the Jet has a CONCATENATE() set function. Simply put, this is because it is a violation of 1NF. I'd prefer to do this on the application side rather than try to force SQL to do something it wasn't designed to do. Perhaps ACE's (Access2007) multi-valued types is a better fit: still NFNF but at least there is engine-level support. Remember, the question relates to a stored object: how would a user query a non-scalar column using SQL...?
#David W. Fenton on whether Jet has stored procedures: didn't you and I discuss this in the newsgroups a couple of years ago. Since version 4.0, Jet/ACE has supported the
following syntax in ANSI-92 Query Mode:
CREATE PROCEDURE procedure (param1 datatype[, param2 datatype][, ...]) AS sqlstatement;
EXECUTE procedure [param1[, param2[, ...]];
So Jet is creating and executing something it knows (in one mode at least) as a 'procedure' that is 'stored' in the MDB file. However, Jet/ACE SQL is pure and simple: it has no control-of-flow syntax and a PROCEDURE can only contain one SQL statement, so any procedural code is out of the question. Therefore, the answer to whether Jet has stored procedures is subjective.