问题
I have a table Tbl
as follows:
+----+------+----------+
| ID | Item | ItemDate |
+----+------+----------+
| 1 | xv | 7/23 |
| 2 | drc | 3/15 |
| 3 | fna | 3/15 |
| 4 | fna | 1/19 |
+----+------+----------+
A user has requested a form TblForm
based on this table that includes a column maxDate
that gives the most recent ItemDate
for each Item
. The form must allow the user to edit Tbl
data, so I can't just build a form based on a join query, as Access doesn't allow you to edit the results of a join. In addition, the form must be sortable based on the maxDate
column.
I built a separate maxDate
aggregate query, then added a control to TblForm
and set its ControlSource as:
=DLookUp("maxDate","maxDate","Item=" & [Item])
But in the resulting datasheet, I can't sort based on this column; I assume that's because it's not part of TblForm
's record source. So I tried building a query that includes the DLookUp:
select *,=DLookUp("maxDate","maxDate","Item=" & [Item]) as maxDateField from tbl
The form based on this query is extremely slow.
Any ideas on how I can build what I'm looking for?
回答1:
This can be achieved by using temporary tables.
You need two equal temp tables, let's call them tmpTbl1 and tmpTbl2.
To refresh these tables, you create two queries: vwMaxDate1 and vwMaxDate2.
You also create two queries as origins for your report: vwTbl1 and vwTbl2. You'll switch between them, because you cannot update a table while open by a form.
This is the content of the views:
' CreateTmpTable1
SELECT Tbl.Item, Max(Tbl.ItemDate) AS maxdate INTO TmpTable1
FROM Tbl
GROUP BY Tbl.Item;
' CreateTmpTable2
SELECT Tbl.Item, Max(Tbl.ItemDate) AS maxdate INTO TmpTable2
FROM Tbl
GROUP BY Tbl.Item;
' vwMaxDate1
INSERT INTO tmpTbl1 ( Item, maxdate )
SELECT Tbl.Item, Max(Tbl.ItemDate) AS maxdate
FROM Tbl
GROUP BY Tbl.Item;
' vwMaxDate2
INSERT INTO tmpTbl2 ( Item, maxdate )
SELECT Tbl.Item, Max(Tbl.ItemDate) AS maxdate
FROM Tbl
GROUP BY Tbl.Item;
' vwTbl1
SELECT Tbl.*, T.maxdate
FROM Tbl LEFT JOIN tmpTbl1 AS T ON Tbl.Item = T.Item;
' vwTbl2
SELECT Tbl.*, T.maxdate
FROM Tbl LEFT JOIN tmpTbl2 AS T ON Tbl.Item = T.Item;
Now, execute CreateTmpTable1 and CreateTmpTable2. You wont need these queries again.
Enter both temp tables in design mode and set Item as the Primary Key.
Then, execute vwMaxDate1 and design your form based on the view vwTbl1. When you're finished, delete the origin of the registry. I'd also lock the control for the maxdate field, since this is a calculated field and shouldn't be manually modified.
Now, enter the following code into the form and change the "Before update", "After update" and "Open" events of the form to "[Event procedure]".
Option Compare Database
Option Explicit
Private dataChanged As Boolean ' True whenever Item or ItemDate are changed
Private FirstTable As Boolean ' If true the origin of the registry is vwTbl1
Private Sub Form_AfterUpdate()
If dataChanged Then
DoCmd.SetWarnings False
Select Case FirstTable
Case True: ' vwTbl1 is open: Switch to vwTbl2
DoCmd.RunSQL "DELETE * FROM TmpTbl2" ' Delete actual data from tmp table 2
DoCmd.OpenQuery "vwMaxDate2" ' Create new data for tmp table 2
Me.RecordSource = "vwTbl2" ' Switch to vwTbl2
FirstTable = False
Case False: ' vwTbl2 is open: Switch to vwTbl1
DoCmd.RunSQL "DELETE * FROM TmpTbl1" ' Delete actual data from tmp table 1
DoCmd.OpenQuery "vwMaxDate1" ' Create new data for tmp table 1
Me.RecordSource = "vwTbl1" ' Switch to vwTbl1
FirstTable = True
End Select
DoCmd.SetWarnings True
End If
End Sub
Private Sub Form_BeforeUpdate(Cancel As Integer)
' Examine the Item and ItemDate controls to determine whether they've changed or not
dataChanged = (Me.Item.OldValue <> Me.Item.Value Or Me.ItemDate.OldValue <> Me.ItemDate.Value)
End Sub
Private Sub Form_Open(Cancel As Integer)
' Initialize variables and form registry source
FirstTable = True
dataChanged = False
DoCmd.SetWarnings False
DoCmd.OpenQuery "vwMaxDate1"
Me.RecordSource = "vwTbl1"
DoCmd.SetWarnings True
End Sub
This query will be fast for querying data and for update of any fields different from Item and ItemDate.
If you need to update these particular fields, you'll notice a variable delay, depending on the number of registers in your Tbl table.
To accelerate the creation of the temporary table, it would be strongly advisable to create an index on your Tbl table based on both "Item" and "ItemDate" fields.
If you need this to work in a multiuser environment, then you should use a table record to hold the variable FirstTable. This means you have to query and update this table with some instructions like
FirstTable = DLookup("FirstTable","CtrlTable","ID=1")
DoCmd.RunSQL "UPDATE CtrlTable SET FirstTable=True WHERE ID=1"
instead of simply assigning True or False to FirstTable with "=".
And that's all. Hope it works for you.
Regards,
来源:https://stackoverflow.com/questions/17823194/how-to-build-an-editable-form-with-data-equivalent-to-a-join