Ваш вопрос относительно запроса данных со ссылкой на себя очень похож на этот вопрос , в котором у пользователя есть таблица сотрудников, у каждого из которых может быть руководитель, чья запись о сотруднике также присутствует в той же таблице. , таким образом формируя иерархию.
Относительно простым способом решения этой проблемы было бы использование выражения DLookup
внутри цикла или внутри рекурсивного вызова до тех пор, пока выражение не вернет Null
. Например, вот рекурсивный вариант:
Function TransLookup(lngtrn As Long)
Dim lngptr
lngptr = DLookup("ptrans", "tbltransactions", "transid = " & lngtrn)
If Not IsNull(lngptr) Then
Debug.Print lngtrn ' Do something with the data
TransLookup (lngptr)
End If
End Function
При оценке с вашими данными это даст:
?TransLookup(4)
4
3
1
Это, конечно, только печать идентификатора транзакции, но функция может альтернативнопри необходимости заполните отдельную таблицу данными для каждой транзакции.
Однако возврат результатов запись за записью или заполнение временной таблицы кажется неэффективным, если мы можем создать один запрос SQL, чтобы вернуть всерезультатов за один раз.
Однако, поскольку MS Access не поддерживает рекурсивные SQL-запросы, сложность при запросе таких иерархических данных заключается в том, что они не знают, сколько уровней нужно кодировать заранее.
Таким образом, вы можете использовать функцию VBA для построения самого запроса SQL, и, таким образом, всегда включать столько уровней, сколько необходимо для возврата полного набора данных.
Действительно, этот подход я выдвинул в мой ответ на связанный вопрос, связанный выше - функция, представленная в этом ответе, может бытьможет быть адаптирован к этой ситуации, например:
Function BuildQuerySQL(lngtrn As Long) As String
Dim intlvl As Integer
Dim strsel As String: strsel = selsql(intlvl)
Dim strfrm As String: strfrm = "tbltransactions as t0 "
Dim strwhr As String: strwhr = "where t0.transid = " & lngtrn
While HasRecordsP(strsel & strfrm & strwhr)
intlvl = intlvl + 1
BuildQuerySQL = BuildQuerySQL & " union " & strsel & strfrm & strwhr
strsel = selsql(intlvl)
If intlvl > 1 Then
strfrm = "(" & strfrm & ")" & frmsql(intlvl)
Else
strfrm = strfrm & frmsql(intlvl)
End If
Wend
BuildQuerySQL = Mid(BuildQuerySQL, 8)
End Function
Function HasRecordsP(strSQL As String) As Boolean
Dim dbs As DAO.Database
Set dbs = CurrentDb
With dbs.OpenRecordset(strSQL)
HasRecordsP = Not .EOF
.Close
End With
Set dbs = Nothing
End Function
Function selsql(intlvl As Integer) As String
selsql = "select t" & intlvl & ".* from "
End Function
Function frmsql(intlvl As Integer) As String
frmsql = " inner join tbltransactions as t" & intlvl & " on t" & intlvl - 1 & ".ptrans = t" & intlvl & ".transid "
End Function
Теперь, оценка функции BuildQuerySQL
с идентификатором транзакции 4
дает следующий запрос SQL UNION
, где каждый уровень вложенности объединяется спредыдущий запрос:
select
t0.*
from
tbltransactions as t0
where
t0.transid = 4
union
select
t1.*
from
tbltransactions as t0 inner join tbltransactions as t1
on t0.ptrans = t1.transid
where
t0.transid = 4
union
select
t2.*
from
(
tbltransactions as t0 inner join tbltransactions as t1
on t0.ptrans = t1.transid
)
inner join tbltransactions as t2
on t1.ptrans = t2.transid
where
t0.transid = 4
Поэтому такую функцию можно оценить для построения сохраненного запроса, например, для идентификатора транзакции = 4, следующее создаст запрос с именем TransactionList
:
Sub test()
CurrentDb.CreateQueryDef "TransactionList", BuildQuerySQL(4)
End Sub
Или, альтернативно, SQL может быть оценен, чтобы открыть RecordSet результатов, в зависимости от требований вашего приложения.
При оценке с вашими примерами данных вышеприведенный SQL-запрос даст следующие результаты:
+---------+---------+---------+--------+
| TransID | Grantor | Grantee | PTrans |
+---------+---------+---------+--------+
| 1 | Bob | Sally | 0 |
| 3 | Sally | Beth | 1 |
| 4 | Beth | Sam | 3 |
+---------+---------+---------+--------+