web-dev-qa-db-ja.com

Accessで再帰クエリを作成することは可能ですか?

jobテーブルがあります

Id
ParentID
jobName
jobStatus

ルートParentIDは0です。

Accessで、特定のjobのルートを見つけるクエリを作成することは可能ですか?データベースはリンクされたテーブルのないMDBです。 Accessのバージョンは2003です。jobは、孫の数レベルの深さになる可能性があります。

18
THEn

いいえ、そうではありません。再帰クエリは、SServer2005以降のSQLServerでサポートされていますが、Accessではサポートされていません。

レベルの数が事前にわかっている場合は、クエリを作成できますが、再帰的なものではありません。

SQL Serverでは、CTE(SQL拡張機能)がそのために使用されます: http://blog.crowe.co.nz/archive/2007/09/06/Microsoft-SQL-Server-2005---を参照) CTE-Example-of-a-simple.aspx

ただし、通常のSQLは再帰をサポートしていません。

8
Peter

Accessでは、特定のジョブのルートを見つけるためのクエリを作成することができます。 VBA機能の力を忘れないでください。 VBAモジュールで再帰関数を作成し、その結果をクエリの出力フィールドとして使用できます。

例:

Public Function JobRoot(Id As Long, ParentId As Long) As Long
   If ParentId = 0 Then
      JobRoot = Id
      Exit Function
   End If

   Dim Rst As New ADODB.Recordset
   Dim sql As String
   sql = "SELECT Id, ParentID FROM JobTable WHERE Id = " & ParentId & ";"
   Rst.Open sql, CurrentProject.Connection, adOpenKeyset, adLockReadOnly

   If Rst.Fields("ParentID") = 0 Then
      JobRoot = Rst.Fields("Id")
   Else
      JobRoot = JobRoot(Id, Rst.Fields("ParentID"))    ' Recursive.
   End If

   Rst.Close
   Set Rst = Nothing
End Function

クエリビルダーを使用するか、クエリフィールドに引数を指定して関数名を入力するだけで、クエリからこの再帰関数を呼び出すことができます。

それは根を生み出すでしょう。

(OPは1年前のものだと認識していますが、不可能なことは可能だと誰もが言うと、答えざるを得ません)。

25
pointer

再帰的にクエリを実行することはできません。

いくつかの任意の数の左結合を実行できますが、結合がある数のレベルまでしか上がれません。

または、 Celkoの「入れ子集合モデル」 を使用してすべての親を取得できます。これには、挿入と更新をより複雑にする方法で、テーブル構造を変更する必要があります。

4
tpdi

これは、Accessで純粋なSQLを使用して実行することはできませんが、少しのVBAが大いに役立ちます。

Microsoft Scripting RuntimeTools-> References ...)への参照を追加します。

これは、IDが一意であり、サイクルがないことを前提としています。 Aの親はBですが、Bの親はAです。

Dim dict As Scripting.Dictionary

Function JobRoot(ID As Long) As Long
    If dict Is Nothing Then
        Set dict = New Scripting.Dictionary
        Dim rs As DAO.Recordset
        Set rs = CurrentDb.OpenRecordset("SELECT ID, ParentID FROM Job", dbOpenForwardOnly, dbReadOnly)
        Do Until rs.EOF
            dict(rs!ID) = rs!ParentID
            rs.MoveNext
        Loop
        Set rs = Nothing

        Dim key As Variant
        For Each key In dict.Keys
            Dim possibleRoot As Integer
            possibleRoot = dict(key)
            Do While dict(possibleRoot) <> 0
                possibleRoot = dict(possibleRoot)
            Loop
            dict(key) = possibleRoot
        Next
    End If
    JobRoot = dict(ID)
End Function

Sub Reset() 'This needs to be called to refresh the data
    Set dict = Nothing
End Sub
2
Zev Spitz

OK、これが本物の取引です。まず、クエリの対象読者は何ですか..フォーム?報告書?関数/プロシージャ?

フォーム:更新が必要ですか?不器用なときにツリービューコントロールを使用すると、うまく機能します。レポート:openイベントで、パラメータフォームを使用して「ボスジョブ」レベルを設定し、vbaで再帰を処理し、レコードセットにデータを入力します必要な順序で。レポートレコードセットをこの入力済みレコードセットに設定し、レポートを処理します。機能/手順:上記のレポートで説明されているデータロードとほぼ同じように機能します。コードを使用して、必要な「ツリーウォーキング」を処理し、結果セットを目的の順序でレコードセットに保存し、必要に応じて処理します。

1
Mark Pashalis

ツリービュー構造の操作に関連する問題がありました。ユーザーがノードを削除したい場合、そのツリーの下にあるすべてのノードを削除したいと考えています。子供、子供たちの子供など。

再帰の仕事....

したがって、ツリービューノードの削除と一致するようにテーブル内のデータを削除するには、ノードを削除する再帰関数を使用し、子、孫などであるすべてのノードを再帰的に削除します。

関数の例:

Public Sub RemoveChildKeys(MyKey)
' deletes passed key and removes all children and grandchildren ect etc of passed key recursively

   Dim TheDB As DAO.Database
   Dim TheTable As DAO.Recordset
   Dim MySql As String

   Set TheDB = CurrentDb

   MySql = "Select * from TblIndex WHERE [Parent]=" & MyKey & ";"
   Set TheTable = TheDB.OpenRecordset(MySql)

   While Not TheTable.EOF
     RemoveChildKeys (TheTable!Key)  ' <---- Calls itself
     TheTable.MoveNext
   Wend

   DoCmd.RunSQL "Delete * FROM TblIndex WHERE [Key]=" & MyKey  ' delete in table

End Sub
0
Ratbyte Boss

Zevの貢献は、私に多くのインスピレーションと学習を与えてくれました。ただし、コードを編集する必要がありました。私のテーブルは「tblTree」と呼ばれていることに注意してください。

Dim dict As Scripting.Dictionary

Function TreeRoot(ID As Long) As Long
    If dict Is Nothing Then
        Set dict = New Scripting.Dictionary ' Requires Microsoft Scripting Runtime
        Dim rs As DAO.Recordset
        Set rs = CurrentDb.OpenRecordset("tblTree", dbOpenForwardOnly, dbReadOnly)
        Do Until rs.EOF
            dict.Add (rs!ID), (rs!ParentID)
            rs.MoveNext
        Loop
        Set rs = Nothing
    End If

    TreeRoot = ID

    Do While dict(TreeRoot) <> 0    ' Note: short version for dict.item(TreeRoot)
        TreeRoot = dict(TreeRoot)
    Loop
End Function

そして、同じコンテキストで別の便利な機能があります。 「ChildHasParent」は、子が任意のレベルのネストで指定されたParentIDと一致する場合、trueを返します。

Function ChildHasParent(ID As Long, ParentID As Long) As Boolean
    If dict Is Nothing Then
        Set dict = New Scripting.Dictionary ' Requires Microsoft Scripting Runtime
        Dim rs As DAO.Recordset
        Set rs = CurrentDb.OpenRecordset("tblTree", dbOpenForwardOnly, dbReadOnly)
        Do Until rs.EOF
            dict.Add (rs!ID), (rs!ParentID)
            rs.MoveNext
        Loop
        Set rs = Nothing
    End If

    ChildHasParent = False

    Do While dict(ID) <> 0    ' Note: short version for dict.item(TreeRoot)
        ID = dict(ID)
        If ID = ParentID Then
            ChildHasParent = True
            Exit Do
        End If
    Loop
End Function
0
Ben