Crosstab query with date criteria - ms-access

I have created a crosstab query as below
TRANSFORM Nz(Sum([debit]*[GeneralExpenses]),0) AS mOmomeya
SELECT Expenses.sName, Expenses.ArName
FROM (GL INNER JOIN Expenses ON GL.ID = Expenses.glID) INNER JOIN Transactions ON GL.GL = Transactions.GL
GROUP BY Expenses.sName, Expenses.ArName
PIVOT Month([ddate]);
what I want to do is to add a criteria to the [dDate] field from a form text input to match the date that I entered, but I received error while doing this as below
any idea what's wrong?

Hmmm, there definitely seems to be something a little bit different about crosstab queries. The straight SELECT query...
SELECT expenses.*
FROM expenses
WHERE (((expenses.ddate) Between [Forms]![main]![DateFrom] And [Forms]![main]![DateTo]));
...works fine, but the crosstab query...
TRANSFORM Sum(expenses.expenses) AS SumOfexpenses
SELECT expenses.onduty
FROM expenses
WHERE (((expenses.ddate) Between [Forms]![main]![DateFrom] And [Forms]![main]![DateTo]))
GROUP BY expenses.onduty
PIVOT Month(ddate);
...fails with the error message you cited. One workaround would be to rebuild the SQL for the query before using it:
Dim qdf As DAO.QueryDef
Set qdf = CurrentDb.QueryDefs("yourCrosstab")
qdf.SQL = _
"TRANSFORM Nz(Sum([debit]*[GeneralExpenses]),0) AS mOmomeya " & _
"SELECT Expenses.sName, Expenses.ArName " & _
"FROM (GL INNER JOIN Expenses ON GL.ID = Expenses.glID) INNER JOIN Transactions ON GL.GL = Transactions.GL " & _
"WHERE (((ddate) " & _
"Between #" & Format(CDate([Forms]![main]![DateFrom]), "yyyy-mm-dd") & "# " & _
"And #" & Format(CDate([Forms]![main]![DateTo]), "yyyy-mm-dd") & "#)) " & _
"GROUP BY Expenses.sName, Expenses.ArName " & _
"PIVOT Month(ddate);"
Set qdf = Nothing
DoCmd.OpenQuery "yourCrosstab" '' or whatever you want to do with it

The problem is that crosstab querys require the form parameter for each form. Right click on gray space and select parameter. Put in the forms and data type.

Related

Requerying subform only work sometimes

I am trying to requery a subform which diplays customer details.
I am sure my sql works as the subform works fine when not influenced by the main form.
Private Sub btnSearch_Click()
Dim sql As String
sql = "SELECT tblCustomer.[Job ID], tblCustomer.[Customer Name], tblCustomer.[Street Name], tblCustomer.Area, tblAppointments.[Appointment Date] " _
& "FROM tblCustomer " _
& "LEFT JOIN tblAppointments ON tblCustomer.[Job ID] = tblAppointments.[Job Number].Value " _
& "WHERE [Customer Name] LIKE '*" & Me.txtKeywords & "*'" _
& "OR [Job ID] LIKE '*" & Me.txtKeywords & "*'" _
& "OR [Street Name] LIKE '*" & Me.txtKeywords & "*'" _
& "OR [Area] LIKE '*" & Me.txtKeywords & "*'" _
& "OR [Appointment Date] LIKE '*" & Me.txtKeywords & "*'" _
& "ORDER BY tblAppointments.[Appointment Date];"
Me.subCustomerList.Form.RecordSource = sql
Me.subCustomerList.Form.Requery
End Sub
The debugger shows an error at the following line:
Me.subCustomerList.Form.RecordSource = sql
I also get the error message
object or class does not support set of events
Consider parameterization with a stored query for several reasons in your case:
Helps avoid syntax issues like what you may have since Access will not allow you to save a query with syntax errors if using the GUI query designer;
Better maintainability than a concatenated SQL string which requires single/double quote enclosure and you abstract data from code;
The Access engine compiles stored queries to best execution plan so will be more efficient than a VBA called SQL string (i.e., use index on JOIN).
SQL (save as a query object with parameter to be referenced by name in VBA)
PARAMETERS txtKeywordsParam TEXT(255);
SELECT c.[Job ID], c.[Customer Name], c.[Street Name], c.Area, a.[Appointment Date]
FROM tblCustomer c
LEFT JOIN tblAppointments a ON c.[Job ID] = a.[Job Number]
WHERE c.[Customer Name] LIKE txtKeywordsParam
OR c.[Job ID] LIKE txtKeywordsParam
OR c.[Street Name] LIKE txtKeywordsParam
OR c.[Area] LIKE txtKeywordsParam
OR a.[Appointment Date] LIKE txtKeywordsParam
ORDER BY a.[Appointment Date];
VBA
Private Sub btnSearch_Click()
Dim qdef As QueryDef
Dim rst As Recordset
' OPEN QUERY AND BIND PARAM
Set qdef = CurrentDb.QueryDefs("mySavedParamQuery")
qdef!txtKeywordsParam = "*" & Me.txtKeyword & "*"
' ASSIGN QUERY RESULT TO RECORDSET
Set rst = qdef.OpenRecordset()
' APPLY RECORDSET TO FORM
Set Me.subCustomerList.Form.Recordset = rst
Set qdef = Nothing
End Sub
I created a new subform and copied the one that did not work and everything seems to be running well.

MS Access: Left outer self join with inequality operator

I have a single table in Access 2010:
TMP [CUST,ITEM,START_PD]
I want to get the END_PD for each CUST / ITEM.
END_PD being defined as the period before the next higher START_PD for the same CUST / ITEM.
So I perform a left join of the table to itself, using an inequality operator on START_PD as below.
SELECT s.CUST, s.ITEM, s.START_PD, Min(e.START_PD-1) AS END_PD
FROM TMP AS s
LEFT JOIN TMP AS e ON s.CUST=e.CUST AND s.ITEM=e.ITEM AND e.START_PD>s.START_PD
GROUP BY s.CUST, s.ITEM, s.START_PD
ORDER BY s.CUST, s.ITEM, s.START_PD
The base table has 46,556 rows. I would expect the query result to have the same, but the query only returns 14,967 rows.
Even when I try to return all records in the left join, I get far less than the base table. See below:
SELECT s.*,e.*
FROM TMP AS s
LEFT JOIN TMP AS e ON s.ITEM = e.ITEM AND s.CUST = e.CUST AND e.START_PD>s.START_PD
The above query only returns 19,014 records... less than the base table.
This is holding up a major project, and I'd appreciate any help. So far, it's looking like an Access bug. Any workarounds?
EDIT:
I have tried testing a small sample of the data by including WHERE s.CUST='WALMART' AND s.ITEM='0001H'. This fails by excluding the last START_PD, for which there is none greater.
CUST ITEM START_PD END_PD
WALMART 0001HAC 20694 20696
WALMART 0001HAC 20697 20704
WALMART 0001HAC 20705 20706
Strangely, if I select that same sample of the data (WALMART/0001H) into a separate table and run the EXACT SAME query on that smaller table (changing only the table name), it works fine as below. That is why I tend to think this is a bug.
CUST ITEM START_PD END_PD
WALMART 0001HAC 20694 20696
WALMART 0001HAC 20697 20704
WALMART 0001HAC 20705 20706
WALMART 0001HAC 20707
To answer my own question, I'd like to:
Demonstrate that this is a bug
Provide a workaround
Proof that this is a bug.
Sub Test()
Dim db As DAO.Database, rst As DAO.Recordset
Set db = CurrentDb
On Error Resume Next
db.QueryDefs.Delete "TMP_EXTENDED"
db.QueryDefs.Delete "RAW_LEFT_JOIN"
db.TableDefs.Delete "TMP"
On Error GoTo 0
'Table definition.
strSql = _
"CREATE TABLE TMP ( " & vbCrLf & _
" CUST VARCHAR(10), " & vbCrLf & _
" ITEM VARCHAR(10), " & vbCrLf & _
" START_PD LONG, " & vbCrLf & _
" PRIMARY KEY (CUST,ITEM,START_PD) " & vbCrLf & _
");"
db.Execute strSql, dbFailOnError
'Populate with data.
Set rst = db.OpenRecordset("TMP")
For custNo = 1 To 25 'change to affect final row count
For itemNo = 1 To 100 'change to affect final row count
For pdNo = 1 To 3 'change to affect final row count
strCust = "CUST" & custNo
strItem = "ITEM" & itemNo
rst.AddNew
rst("CUST") = strCust
rst("ITEM") = strItem
rst("START_PD") = pdNo
rst.Update
If rst.RecordCount Mod 1000 = 0 Then Debug.Print rst.RecordCount 'just to monitor.
Next
Next
Next
Debug.Print "TMP Table Row Count is: " & DCount("*", "TMP")
'Test query to find end period for each CUST/ITEM/START_PD.
Dim qdf As New QueryDef
qdf.Name = "TMP_EXTENDED"
qdf.SQL = "SELECT s.CUST, s.ITEM, s.START_PD, Min(e.START_PD-1) AS END_PD " & vbCrLf & _
"FROM TMP AS s " & vbCrLf & _
"LEFT JOIN TMP AS e ON (s.CUST=e.CUST AND s.ITEM=e.ITEM AND e.START_PD>s.START_PD) " & vbCrLf & _
"GROUP BY s.CUST, s.ITEM, s.START_PD " & vbCrLf & _
"ORDER BY s.CUST, s.ITEM, s.START_PD"
db.QueryDefs.Append qdf
Debug.Print "TMP_EXTENDED Row Count is: " & DCount("*", "TMP_EXTENDED")
'Test query to just perform the left join.
Set qdf = New QueryDef
qdf.Name = "RAW_LEFT_JOIN"
qdf.SQL = "SELECT s.*,e.* " & vbCrLf & _
"FROM TMP AS s " & vbCrLf & _
"LEFT JOIN TMP AS e ON s.ITEM = e.ITEM AND s.CUST = e.CUST AND e.START_PD>s.START_PD"
db.QueryDefs.Append qdf
Debug.Print "RAW_LEFT_JOIN Row Count is: " & DCount("*", "RAW_LEFT_JOIN")
RefreshDatabaseWindow
End Sub
When run as written, the above code returns:
TMP Table Row Count is: 7500
TMP_EXTENDED Row Count is: 5000
RAW_LEFT_JOIN Row Count is: 7500
Obviously this is wrong because a left join should always return all the records in the left table. In this case, TMP_EXTENDED should have returned 7500, and RAW_LEFT_JOIN should have returned more than 7500.
The loop bounds for custNo, itemNo, and pdNo can be changed to adjust the record count in table TMP. If you do this, you will see that the query works until the record count reaches about 7000, then it fails.
The same problem does not appear to exist when the join is only performed on one 'entity' column. For example, I modified the above code to use a table with only a CUST column and START_PD column, and got both queries to work properly on a table record count of 600,000.
Workaround
Since posting this, I have found another quite similar post in which a decent workaround is presented. I have modified it as below. I do not know HOW reliable it is going to be, but I'm going with it for now.
SELECT s.CUST, s.ITEM, s.START_PD, Min(e.START_PD) AS END_PD
FROM TMP AS s INNER JOIN TMP AS e ON s.ITEM = e.ITEM AND s.CUST = e.CUST
WHERE e.START_PD>s.START_PD
GROUP BY s.CUST, s.ITEM, s.START_PD
UNION ALL
SELECT CUST, ITEM, Max(START_PD), Null
FROM TMP
GROUP BY CUST, ITEM, Null
ORDER BY CUST,ITEM,START_PD,END_PD
Any "atypical" (not only table1.field = table2.field, but anything with constants, calculations, or comparison operators other than equality) join in Access should have it's ON clause surrounded by braces:
SELECT s.*,e.*
FROM TMP AS s
LEFT JOIN TMP AS e ON (s.ITEM = e.ITEM AND s.CUST = e.CUST AND e.START_PD>s.START_PD)
However, usually, these kinds of joins return a Join expression not supported error instead of giving incorrect results. I don't know why this one doesn't.

update query with inner join access vba code

I am trying to update a table from another one via a inner join query access vba. The field's name to be updated will be taken in a form.
I wrote this query, but it gives me error.
Dim dbs As Database
Dim qdf As QueryDef
Set dbs = OpenDatabase("Dba_Savings.mdb")
dbs.Execute " UPDATE TblClient INNER JOIN TblImport ON TblClient.Account_Id = TblImport.Account_Id" _
& " SET TblClient." & Me.txtNewFieldName & " = TblImport." & Me.txtNewFieldName2 & ";"
dbs.Close
Can anyone help me please.
Dim S As String
S = "UPDATE TblClient INNER JOIN TblImport ON TblClient.Account_Id = TblImport.Account_Id" _
& " SET TblClient." & Me.txtNewFieldName & " = TblImport." & Me.txtNewFieldName2 & ";"
Debug.Print S
dbs.Execute S
UPDATE TblClient INNER JOIN TblImport ON TblClient.Account_Id = TblImport.Account_Id SET TblClient.S_B_Mar-14 = TblImport.S_B_Mar-14;
The SQL work perfectly and update the table, No error.
You are missing a space between these two:
ON TblClient.Account_Id = TblImport.Account_Id" _
& "SET TblClient.
When building SQL in VBA, always assign the SQL to a string variable, then Debug.Print this variable. In this case, the error would be obvious.
Also this part is unnecessary, since it's already in the INNER JOIN:
WHERE TblClient.Account_Id = TblImport.Account_Id
Edit
Instead of
dbs.Execute " UPDATE TblClient INNER JOIN TblImport ON TblClient.Account_Id = TblImport.Account_Id" _
& " SET TblClient." & Me.txtNewFieldName & " = TblImport." & Me.txtNewFieldName2 & ";"
you do:
Dim S As String
S = "UPDATE TblClient INNER JOIN TblImport ON TblClient.Account_Id = TblImport.Account_Id" _
& " SET TblClient." & Me.txtNewFieldName & " = TblImport." & Me.txtNewFieldName2 & ";"
Debug.Print S
dbs.Execute S
Ctrl+G opens the Immediate Window, this will show the output of Debug.Print. Please add this to your question.
Then, if it still gives an error, you can also create a new query, switch to SQL view, copy & paste the output, and try to run the query. You will probably get a better error message. Or at least highlight the problematic part.

How to retrieve a specific value from a Table with SQL in VBA

I am trying to access a specific value (CompanyNr) from the TABLE 1 and store it into a variable. To later use it to access the Company name from TABLE 2
MY TABLE EXAMPLES:
TABLE 1 (Contacts):
ID , CompanyNr , Contact
TABLE 2 (Companies):
CompanyNr , Company
I have a ComboBox for Contacts. And I when I select someone from the ComboBox, I want to automaticly filter out Companies in which the selected Contact works.
I have tried like this:
MY CODE:
CompNr = "SELECT [Contacts].[ID], [Contacts].[CompanyNr] FROM Contacts " & _
"WHERE [Contacts].[ID] = " & ContactNrCombo.Value & " ORDER BY [Contact];"
MsgBox "Company Nr:" & CompNr
sq11 = "SELECT [Companies].[CompanyNr], [Companies].[Company] FROM Companies " & _
"WHERE [Companies].[CompanyNr] = " & CompNr & "ORDER BY [Company]; "
But the MsgBox says:
CompNr = SELECT [Contacts].[ID], [Contacts].[CompanyNr] FROM Contacts WHERE [Contacts].[ID] = 255 ORDER BY [Contact]
Yet I need to get a certain value for CompanyNr (like 50 for example) so that I can use it in my other SQL Statement to filter from TABLE 2.
I am new to SQL statements, I just found the above one as an example on internet and thought I could use it. But it didn`t work well.
Any help would be appreciated
You can try something like the following,
Set con = CreateObject("ADODB.Connection")
Set rs = CreateObject("ADODB.Recordset")
con.Open "Provider=..." 'Your Connectionstring
rs.Open "SELECT [Contacts].[ID], [Contacts].[CompanyNr] FROM Contacts " & _
"WHERE [Contacts].[ID] = " & ContactNrCombo.Value & " ORDER BY [Contact]", con, 1,3
'Assuming there is only one company number per contact
rs.MoveFirst
CompNr = rs.Fields("CompanyNr").Value
rs.Open "SELECT [Companies].[CompanyNr], [Companies].[Company] FROM Companies " & _
"WHERE [Companies].[CompanyNr] = " & CompNr & "ORDER BY [Company]", con, 1,3
'If you want to paste the second sql query in excel use this
Sheets("Sheet1").Range("A1").CopyFromRecordset rs
rs.Close
con.Close
Set con = Nothing

VBA Access - Multiple Tables count by date

We'd like to count from an Access database that has multiple tables - about 50.
We need to count from 1 column in each table that is 'QCPASS' This is a check box - if a product passed the box was checked if failed then not. We need to count both for EACH table, also allowing the user to specify a date range from a date column that exists in every table.
I've tried this with a query but I am told the query is unable to select, count and do the date range. Any VBA help would be great.
Exporting to Excel would be great, but any results would be fine. Here is the query I created that counts in a column from each table passes and failures. I can't iterate with a query either, so VBA seems the way to go:
SELECT "Table1" , Count('qcpass') AS column
FROM 5000028
GROUP BY [5000028].qcpass
union
SELECT "Table2",count('qcpass')
FROM 5000029
Group By [5000029].qcpass;
You can traverse the full TableDefs collection in your database, and create a query using VBA.
A word of warning: The TableDefs collection has the Access database system tables, so you need to skip this. A way I suggest you is to check for a specific table name prefix (it is noted in the code below).
public sub createMyBigUnionQuery()
dim db as DAO.database(), tbl as DAO.tableDef
dim strSQL as string, i as integer
set db = currentdb()
i = 1
for each tbl in db.TableDefs
if left(tbl.name, 1) = "5" then ' Check for a table name prefix
if i = 1 then
' The final spaces are important
strSQL = "select '" & tbl.Name & "' as table, count(qcpass) as column " & _
"from [" & tbl.Name & "] " & _
"group by qcpass "
else
' The final spaces are important
strSQL = strSQL & " union all " & _
"select '" & tbl.Name & "' as table, count(qcpass) as column " & _
"from [" & tbl.Name & "] " & _
"group by qcpass "
end if
i = i + 1
end if
next tbl
db.createQueryDef "qryYourFinalQuery", strSQL
db.close
exit sub
Notice that you can define any valid query you want. Take this as a hint, and tweak it to fit your specific needs.
Hope this helps you
Adressing #HansUp comment, if you need to filter your data by date, you have two options:
Include the where condition on every select created by the procedure
Include the date field in your query and group by it, and create a second query to filter the data you need from the created query.
I would personally go with option 1, and here is a sample code:
public sub createMyBigUnionQueryWithDates(d0 as date, d1 as date)
dim db as DAO.database(), tbl as DAO.tableDef
dim strSQL as string, i as integer
set db = currentdb()
i = 1
for each tbl in db.TableDefs
if left(tbl.name, 1) = "5" then ' Check for a table name prefix
if i = 1 then
' The final spaces are important
strSQL = "select '" & tbl.Name & "' as table, count(qcpass) as column " & _
"from [" & tbl.Name & "] " & _
"where rowDate between " & cDbl(d0) & " and " &cDbl(d1) & " " & _
"group by qcpass "
else
' The final spaces are important
strSQL = strSQL & " union all " & _
"select '" & tbl.Name & "' as table, count(qcpass) as column " & _
"from [" & tbl.Name & "] " & _
"where rowDate between " & cDbl(d0) & " and " &cDbl(d1) & " " & _
"group by qcpass "
end if
i = i + 1
end if
next tbl
db.createQueryDef "qryYourOtherFinalQuery", strSQL
db.close
exit sub
The reason I use cDbl(d0) is because Access dates are sensitive to regional settings, and I've had a lot of headaches dealing with it. Access (and many other Microsoft products) store dates as floating-point numbers (the integer part is the date, and the decimal part is the time).
Another word of warning: If your dates don't include time, then the between condition will work. But if they do include time, then I recommend you change the where condition to this:
"where rowDate >= " & cDbl(d0) & " and rowDate < " & cDbl(d1 + 1)"