MS Access Looping a union query through all columns? - sql

Is it possible to simplify the following into a loop?
SELECT ID as ColHead, "Field1" AS RowHead, Field1 AS TheVal
FROM `master`
UNION
SELECT ID, "Field2",Field2
FROM `master`
UNION
SELECT ID, "Field3",Field3
FROM `master`
UNION
...
SELECT ID, "Field90",Field90
FROM `master`
There are 90 fields. Before I start writing this by hand, is there a way to simplify the process?

If you build that massive UNION query, consider UNION ALL instead.
UNION ALL will return all rows from each SELECT piece, including duplicate rows. With just UNION, the db engine returns only distinct rows. However ensuring distinct rows imposes a greater burden on the engine.
Use UNION if you require only distinct rows. Use UNION ALL if you can accept duplicate rows. Also use UNION ALL when the candidate rows can not include duplicates to begin with.
If that ID field is the master primary key, or if you have another unique constraint (index) on ID, the candidate rows will already be unique. If that is your situation, use UNION ALL to make that big query easier on the db engine.
Actually I'm apprehensive about trying to UNION (or UNION ALL) 89 SELECTs. I've never attempted such a huge SQL statement. If you want to try, I'll offer another approach.
I created a VBA function to create the SQL statement. It reads the field names from the TableDef and loops through those names to add a SELECT piece for each field name.
Here is an Immediate window session where I tested the function in Access 2007. My master table includes only 4 fields: ID; fld1; fld2; and fld3.
? BuildUnionStatement
SELECT ID as ColHead, 'fld1' AS RowHead, [fld1] AS TheVal
FROM [master]
UNION ALL
SELECT ID as ColHead, 'fld2' AS RowHead, [fld2] AS TheVal
FROM [master]
UNION ALL
SELECT ID as ColHead, 'fld3' AS RowHead, [fld3] AS TheVal
FROM [master]
I don't know what you intend to do with the query after you create it. But a function to create the SQL offers flexibility. You could use the function's output to open a recordset, save it as a QueryDef, for the record source of a form or report, etc.
Public Function BuildUnionStatement() As String
Const cstrTable As String = "master"
Dim db As DAO.database
Dim fld As DAO.Field
Dim tdf As DAO.TableDef
Dim strPattern As String
Dim strSql As String
'strPattern = vbCrLf & "UNION" & vbCrLf &
strPattern = vbCrLf & "UNION ALL" & vbCrLf & _
"SELECT ID as ColHead, " & _
"'FLDNAME' AS RowHead, " & _
"[FLDNAME] AS TheVal" & vbCrLf & _
"FROM [" & cstrTable & "]"
Set db = CurrentDb
Set tdf = db.TableDefs(cstrTable)
For Each fld In tdf.Fields
If fld.Name <> "ID" Then
strSql = strSql & Replace(strPattern, _
"FLDNAME", fld.Name)
End If
Next
Set fld = Nothing
Set tdf = Nothing
Set db = Nothing
'BuildUnionStatement = Mid(strSql, 10) ' UNION
BuildUnionStatement = Mid(strSql, 14) ' UNION ALL
End Function
After you save that function in a module, open the Immediate window (Ctrl+g). To execute the function, type this and press Enter
? BuildUnionStatement
Copy the text it returns, create a new query and switch to SQL View, then paste in the copied text.
Since that gave you too much text to copy from the Immediate window, create a new query --- any query will work. Then assign the function's output to the query's SQL property. Do this in the Immediate window ...
CurrentDb.QueryDefs("YourQueryNameHere").SQL = BuildUnionStatement

Related

How to fix runtime error '3048' "Cannot open any more databases."

I have a form with a button that calls and filters a couple of union queries with about 40 SELECT queries total in between them. It then displays the data in a report. Each SELECT query in the Union query collects records from multiple unique tables in the database. I recently had to add a couple more SELECT queries into the union query to grab records from new tables which is when I got the runtime error. It was opening the report fine before I added these SELECT queries so im under the assumption is there are too many SELECT queries in the UNION query. To resolve this issue, do I simply not use a UNION query and find an alternative way to combine records? or is it something in the VBA code that needs adjustment?
Here is my code
Private Sub Command189_Click()
DoCmd.SetWarnings False
DoCmd.Close acReport, "Operator Daily Review"
Dim db As DAO.Database
Dim qdf As DAO.QueryDef
Dim varItem As Variant
Dim strCriteria As String
Dim strSQL As String
Set db = CurrentDb()
Set qdf = db.QueryDefs("Productivity_WeeklyFinal")
Set qdf2 = db.QueryDefs("qFiller_Names")
strSQL = "SELECT Info_ME_Employees.ID, gs_1_week_finalUnion.SampleID,
gs_1_week_finalUnion.Operator, Format$([TestDate],'m/dd/yyyy') AS Test_Date,
gs_1_week_finalUnion.Test FROM Info_ME_Employees INNER JOIN gs_1_week_finalUnion ON
Info_ME_Employees.Full_Name = gs_1_week_finalUnion.Operator" & _
" WHERE Info_ME_Employees.ID IN (4,5,6,7)AND gs_1_week_finalUnion.TestDate Between (Date()-7-
Weekday(Date(),2)) And (Date()-Weekday(Date(),2)-1) " & _
" ORDER BY gs_1_week_finalUnion.Operator"
strSQL2 = "SELECT Info_ME_Employees.ID, Info_ME_Employees.Full_Name FROM Info_ME_Employees" & _
" WHERE Info_ME_Employees.ID IN (4,5,6,7)"
qdf.SQL = strSQL
qdf2.SQL = strSQL2
DoCmd.OpenReport "Operator Daily Review", acViewReport
Set db = Nothing
Set qdf = Nothing
End Sub
I think that there is a limit of tables that can be included in a UNION query - possibly 32. Therefore your options are:
Create several UNION queries, and then UNION them all together as the final step;
Insert the data into a temp table using each individual part of the union query.
Additionally, there may be some way that your database could be re-designed, as it is quite unusual to have to have some many unions needed.
Regards,
Actually, the statement for this "error" is incorrect!
“Cannot open any more databases.” What microsoft should have said here is that no more links to a database can be opened. That is why adding more UNIONs caused this error. Because each separate reference to a link to an object (table or query) causes another link (microsoft uses the term "database") to be opened.

Access 2010 - Determine existence of record in one of three remote mdb files for subsequent select

There are three mdb files in folders on a network drive that may hold the required record(s). How do I determine which db holds the record(s), ideally without data transfer/linking/etc.? Then a single SQL or DAO select can get the data from the correct db. Note: I'm trying to use Access as a front end to SQL using existing Access data spread all around the network drives.
My current solution of configuring 3 DAO objects and checking for no results, in succession until found, seems to load the remote tables to the local recordset and takes too long.
Is there a way to use IF EXISTS in this scenario?
This code throws "Invalid SQL statement; expected DELETE,INSERT,PROCEDURE,SELECT,OR UPDATE" error but is generally what I'd like to do :
Dim strSQL As String
Dim strSku As String
Dim intDbToSearch As Integer
strSku = DLookup("SKUNo", "tblCurrentItem") 'Note: this returns valid SKU#
strSQL = "IF EXISTS(SELECT xxTable.SKUNo "
strSQL = strSQL & "FROM [S:\Our Inventory\Cust Sleeves.mdb].[xxTable] "
strSQL = strSQL & "Where xxTable.SKUNo = " & "'" & strSku & "') Then intDbToSearch = 1"
DoCmd.RunSQL strSQL
This is one of three IF Exists that would run if SKUNo not found in db 1 or 2.
Ultimately intDbToSearch should point to db 1,2,or 3 if SKUNo found or 0 if not.
Thanks
In the end, I pushed usage rules for the 3 databases upstream and can now predetermine which database to search. Thanks again for your input.
Will the sought for SKU always occur in only 1 of the tables?
If you don't want to set table links or use VBA recordsets, only other approach I can see is a query object with a dynamic parameter that references a form control for the SKU input. No idea if this will be faster and will need a query for each remote table.
SELECT SKUNo FROM xxTable IN "S:\Our Inventory\Cust Sleeves.mdb" WHERE SKUNo = Forms!formname!cbxSKU
Then just do DCount on the query.
Dim intDbToSearch As Integer
If DCount("*", "xxQuery") > 0 Then
intDbToSearch = 1
End If
Could UNION the SELECT statements so would have only 1 query object to work with.
SELECT "x1" AS Source, SKUNo FROM xxTable IN "S:\Our Inventory 1\Cust Sleeves.mdb" WHERE SKUNo = Forms!formname!cbxSKU
UNION SELECT "x2", SKUNo FROM xxTable IN "S:\Our Inventory 2\Cust Sleeves.mdb" WHERE SKUNo = Forms!formname!cbxSKU
UNION SELECT "x3", SKUNo FROM xxTable IN "S:\Our Inventory 3\Cust Sleeves.mdb" WHERE SKUNo = Forms!formname!cbxSKU;
How about a simple Function to check if exists by passing the table name and value?
Something like this:
Public Function ExistInTable(Byval TableName As String, ByVal Value As String) As Boolean
ExistInTable = (DCount("*", TableName, "[SKUNo]='" & Value & "'" > 0)
End Function
To call it:
Sub Test()
If ExistInTable("T1", "Whatever") Then 'Exists in T1
If ExistInTable("T2", "Whatever") Then 'Exists in T2
'....
End Sub

Running Update Qry in VBA Based on a Query Value

I am trying to run an Update Query in VBA and am at a lost as to what I'm supposed to write for the code. I'm running a query to find the most recent date from a table. That query works fine. Now I want to run an update query to update another table's date field to equal to the date that was queried. Here is what I have:
Dim Date1 As Date
Dim newdate1
'selects datadate 1
Date1 = CurrentDb.OpenRecordset("Select Max(Date1_Event) from TBL_Event WHERE ID = '" & [Forms]![FRM_Main]![ID] & "'")(0)
'update datadate 1
newdate1 = CurrentDb.OpenRecordset("Update Tbl_Name set CollectionDate = DataDate1 WHERE PID = '" & [Forms]![FRM_Main]![ID] & "'")(0)
Is there a way to run an update query like this? Thank you.
Action queries (DELETE, UPDATE, INSERT INTO) are to be executed (CurrentDb.Execute) while SELECT queries are to be opened as recordsets (CurrentDb.OpenRecordset).
Additionally, consider using parameterization to avoid any need of quote enclosure or string concatenation in query. And here the max date is calculated with domain aggregate, DMax(), instead of opening another query.
Dim strSQL As String
Dim qdef As Querydef
' PREPARE SQL STATEMENT
strSQL = "PARAMETERS [MaxDateParam] Date, [FormIDParam] Long;" _
& "UPDATE Tbl_Name SET CollectionDate = [MaxDateParam]" _
& " WHERE PID = [FormIDParam];"
' BUILD TEMP QUERY
Set qdef = CurrentDb.CreateQueryDef("", strSQL)
' BIND PARAMETERS
qdef!MaxDateParam = DMax("Date1_Event", "TBL_Event", "ID=" & [Forms]![FRM_Main]![ID])
qdef!FormIDParam = [Forms]![FRM_Main]![ID]
' EXECUTE ACTION
qdef.Execute dbFailOnError
Set qdef = Nothing
Though above may look unusual and slightly more lines. Don't be intimidated and run for the easy 1-2 lines. Parameterization is a programming industry best practice not just in VBA but across all general purpose languages that run dynamic SQL queries using values from user input.

Adding a complete row to a table from a DAO.Recordset

I have an access table containing product information with 100 Columns (it is a bought system so not of my creation). I want to be able to copy a row and insert it as a new row with 3 fields updated. I am using VBA on an access DB.
I am selecting the row I want to copy (Select Product.* FROM .....) and putting it into a DAO.Recordset. This works fine. I then want to insert this data back into the table as a new row with all the same data apart from the Product ID (key) and the Product short description.
As there is 100 columns I am trying to avoid typing in all the column names and assigning the values individually. Is there a way to insert from a DAO.Recordset so I can avoid typing in all the columns? if not is there another way to avoid typing in all the columns and all the values? It would save me a very big insert statement!
Many thanks
Tony
You can loop the Fields collection of the recordset to do this.
This approach may be more maintainable then a giant INSERT statement if the table structure changes from time to time.
If the table is static, I would rather use a saved INSERT query with parameters for the columns that are modified.
Sub CopyProductRow()
Dim rsSrc As DAO.Recordset
Dim rsTgt As DAO.Recordset
Dim fld As DAO.Field
Dim sFld As String
Set rsSrc = CurrentDb.OpenRecordset("SELECT * FROM Products WHERE ProductID = 4711", dbOpenSnapshot)
Set rsTgt = CurrentDb.OpenRecordset("Products", dbOpenDynaset, dbAppendOnly)
rsTgt.AddNew
For Each fld In rsSrc.Fields
sFld = fld.Name
Select Case sFld
' special cases
Case "ProductID": rsTgt(sFld).Value = GetNewProductID()
' or if ProductID is AutoNumber, don't assign anything
Case "ProductDesc": rsTgt(sFld).Value = "my new description"
' all other field values are simply copied
Case Else: rsTgt(sFld).Value = fld.Value
End Select
Next fld
rsTgt.Update
rsTgt.Close
rsSrc.Close
End Sub
If you use a form where you select the record to copy, you can also use the RecordsetClone:
Copy selected record to new record
If you are trying to insert it back into the same table then you can do it without puling it into a recordset at all.
You just need to write the correct SQL query and execute it.
The data that you select will be the recordset that you are already pulling with the updated values.
For instance:
INSERT INTO tableX(field1, productId, productDesc)
SELECT field1, 777 as productId, "NewString" as productDesc
FROM tableX
WHERE productId=7
Another approach which I mentioned in comments would be to loop through each of the fields to build your string used as the SQL command, the execution of this would be a lot faster then processing record by record. (such as inserting a new product for each order in an orders table where another product has already been ordered, which could have 10s of 1000s of orders)
'Edited the code supplied by another response above'
Sub CopyProductRow()
Dim sFld, iField, sqlQuery As String
i= "INSERT INTO products("
s= "SELECT "
w= "WHERE ProductID = 7"
For Each fld In rsSrc.Fields
Select Case fld.Name 'Check the field name'
' special cases'
Case "ProductID": 'If field is Product ID'
iFld = "777 as ProductID" '777 will be the product id returned by the select query (therefore inserted)'
Case "ProductDesc": 'If field is Product Description '
'The description below will be selected / inserted instead.'
iFld = "'New Product Description' as ProductDesc"
Case Else:
iFld = fld.Name 'No change just select the field and insert as is.'
End Select
i = i & ", " & fld.Name
s = s & ", " & iFld
Next fld
'Build complete query (removing the first comma in the select statement)'
sqlQuery = i & ") " & replace(s, "SELECT ,", "SELECT ") & " " &w
'Resulting in a string such as the following'
'INSERT INTO products(field1,field2, productId, productDesc, field5, ...)'
'SELECT field1, field2, 777 as ProductID, 'New Product Description' as ProductDesc, field5, ...'
'FROM tableX'
'WHERE productId=7'
'Print the query to immediate window
Debug.print sqlQuery
'Execute the query'
currentdb.execute sqlQuery
End Sub

UNION ALL query causes type conversion error but UNION doesn't

When I run the following query:
INSERT INTO outRawTbl
SELECT *
FROM (select * from [out|noRFI_BS_noRT]
union all
select * from [out|noRFI_BS_RT]) AS [%$###_Alias];
I get this error:
Microsoft Access set 3854 field(s) to Null due to a type conversion failure
When I run the query as:
INSERT INTO outRawTbl
SELECT *
FROM (select * from [out|noRFI_BS_noRT]
union
select * from [out|noRFI_BS_RT]) AS [%$###_Alias];
then I don't get this type conversion error, and all the data inserts successfully. I need to use UNION ALL because some of the fields in the queries are Memo fields and they'll be truncated to 255 chars if I use UNION.
I guess I can start trying to insert fields one at a time from the queries, but there are around 50 fields in each query and that will take a lot of time. Is there a quick way to find out which field is causing the problem with UNION ALL?
EDIT:
Solved and uncovered another problem. I took Gordon's idea of running the queries as separate INSERT operations instead of using UNION ALL. I then iterated through each field in the query, doing a separate insert to find the field that was causing the conversion error, using this code:
Sub findProblemField()
Dim qdf As QueryDef
Dim sql As String
Dim fld As Field
For Each qdf In CurrentDb.QueryDefs
If InStr(qdf.Name, "out|") Then
For Each fld In qdf.Fields
sql = "insert into outrawtbl select top 1 " & _
"[" & qdf.Name & "].[" & fld.Name & "] from [" & qdf.Name & "]"
CurrentDb.Execute sql, dbFailOnError
Next
End If
Next
End Sub
This led me to discover that one of the fields that is a string in the SELECT query is a Date/Time in the destination field, and a blank string value is what's throwing the error. Working on resolving this now...
I have no idea why MS Access would get a failure for union all but not for union. It should be producing the same data. I also don't know why a memo field would be truncated in one case, but not the other.
However, the easiest way to solve your problem with union all is to do two inserts:
INSERT INTO outRawTbl
select * from [out|noRFI_BS_noRT];
insert into outRawTbl
select * from [out|noRFI_BS_RT];