2009-04-18 19 views
18

Tengo una tabla job ParentID¿Es posible crear una consulta recursiva en Access?

Id 
ParentID 
jobName 
jobStatus 

La raíz es 0.

¿Es posible en Access para crear una consulta para encontrar una raíz para un determinado job? La base de datos es MDB sin tablas vinculadas. La versión de acceso es 2003. Un job puede tener varios niveles de grand children deep.

+0

Sea más preciso cuando diga "el trabajo puede estar en varios niveles secundarios" o pegue algunos datos ficticios. –

Respuesta

8

No, no lo es. Las consultas recursivas se admiten en SQL Server después de SServer 2005, pero no en Access.

Si conoce el número de palancas de antemano, podría escribir un qry, pero no sería recursivo.

En SQL Server, CTE (una extensión de SQL) se utiliza para eso: ver http://blog.crowe.co.nz/archive/2007/09/06/Microsoft-SQL-Server-2005---CTE-Example-of-a-simple.aspx

SQL regular sin embargo no tiene el apoyo recursividad.

+0

@onedaywhen: ¿Debe/debe "ISO/ANSI Standard SQL" tener un año de sufijo, para ayudar a distinguirlo de "ISO/ANSI Standard SQL-99"? –

4

No se puede realizar una consulta recursiva.

Puede hacer una cantidad arbitraria de combinaciones a la izquierda, pero solo podrá subir tantos niveles como tenga uniones.

O puede usar Celko's "Nested Set Model" para recuperar a todos los padres. Esto requerirá modificar la estructura de su tabla, de forma que las inserciones y actualizaciones sean más complicadas.

+0

No se puede realizar una consulta recursiva. -> No está en acceso 03 de todos modos, en muchos otros idiomas de consulta puede – Peter

+0

Sí, "usted" el OP, que está utilizando Access, no puede consultar de forma recursiva. – tpdi

+0

Aunque Celko popularizó los conjuntos anidados, el método se acredita a Michael J. Kamfonas (http://en.wikipedia.org/wiki/Joe_Celko). – onedaywhen

24

Es posible en Access crear una consulta para encontrar la raíz de su trabajo. No se olvide de la potencia de las funciones de VBA. Puede crear una función recursiva en un módulo VBA y usar su resultado como un campo de salida en su consulta.

Ejemplo:

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 

Puede llamar a esta función recursiva de la consulta utilizando el generador de consultas o simplemente escribiendo en el nombre de la función con argumentos en un campo de consulta.

Dará la raíz.

(reconozco que OP tiene un año ahora, pero me veo obligado a responder cuando todos dicen que lo imposible es posible).

+11

Si bien aplaudo su publicación, la pregunta original realmente restringe la respuesta a una consulta, que es, de hecho, imposible sin VBA. Si es justo interpretar "en una consulta" como "sin dependencia de las funciones personalizadas de VBA" es otro problema. Creo que es bueno tener su solución aquí, incluso si no es lo que el póster original estaba buscando, porque otros con el mismo problema pueden no tener reparos en que no sea una respuesta SQL pura. –

+1

Esto es extremadamente ineficiente, ya que significa abrir un nuevo conjunto de registros cada vez que se lo llama. Sugeriría como mínimo mantener el conjunto de registros en una variable de nivel de módulo, o cargar todo el conjunto de registros en un Scripting.Dictionary de nivel de módulo en memoria que probablemente permita un acceso más rápido, como [esto] (http: // stackoverflow. com/a/32161506). @ David-W-Fenton –

1

OK, así que aquí está el trato REAL. Primero, ¿cuál es el público objetivo para su consulta ... un formulario? ¿informe? función/proc?

Formulario: ¿Necesita actualizaciones? use el control treeview mientras que torpe funcionará muy bien. Informe: en el evento abierto use un formulario de parámetros para establecer el nivel de "Trabajo principal", luego maneje la recursión en vba y complete un conjunto de registros con los datos en el orden deseado. establezca el conjunto de registros de informes en este conjunto de registros lleno y procese el informe. Función/Procedimiento: funciona de forma muy parecida a la carga de datos descrita en el informe anterior. A través del código, maneje la "caminata de árbol" necesaria y almacene el conjunto de resultados en el orden deseado en un conjunto de registros y procese según sea necesario.

+2

¿Qué control Treeview? No hay Treeview nativo disponible en ninguna versión de Access de la que tenga conocimiento. –

+0

Esto podría ser una buena adición a las respuestas existentes, pero necesita un trabajo. Por ejemplo, elimine "Aceptar, así que aquí está el trato REAL"; Divide las opciones en viñetas o paras. Expande y aclara cada uno. –

2

Esto no se puede hacer usando SQL puro en Access, pero un poco de VBA va un largo camino.

añadir una referencia a la Microsoft Scripting Runtime (Herramientas ->Referencias ...).

Esto supone que la ID es única y que no hay ciclos: p. El padre de A es B, pero el padre de B es 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 
0

La contribución de Zev me dio una gran cantidad de inspiración y aprendizaje. Sin embargo, es necesario hacer algunas ediciones al código. Tenga en cuenta que mi tabla se llama "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 

Y hay otra función útil en el mismo contexto. "ChildHasParent" devuelve verdadero, si el elemento coincide con el ParentID proporcionado en cualquier nivel de anidación.

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 
Cuestiones relacionadas