Está buscando una consulta recursiva utilizando una expresión de tabla común, o CTE para abreviar. Un informe detallado para esto en SQL Server 2008 puede ser found on MSDN.
En general, tienen una estructura similar a la siguiente:
WITH cte_name (column_name [,...n])
AS (
–- Anchor
CTE_query_definition
UNION ALL
–- Recursive portion
CTE_query_definition
)
-- Statement using the CTE
SELECT * FROM cte_name
Cuando esto se ejecuta, SQL Server va a hacer algo similar a lo siguiente (parafraseado en un lenguaje más sencillo desde el MSDN):
- Divida la expresión CTE en miembros de anclaje y recursivos.
- Ejecute el anclaje, creando el primer conjunto de resultados.
- Ejecute la parte recursiva, con el paso anterior como entrada.
- Repita el paso 3 hasta que se devuelva un conjunto vacío.
- Devuelve el conjunto de resultados. Este es UNION TODO el ancla y todos los pasos recursivos.
Para este ejemplo específico, intentar algo como esto:
With hierarchy (id, [location id], name, depth)
As (
-- selects the "root" level items.
Select ID, [LocationID], Name, 1 As depth
From dbo.Locations
Where ID = [LocationID]
Union All
-- selects the descendant items.
Select child.id, child.[LocationID], child.name,
parent.depth + 1 As depth
From dbo.Locations As child
Inner Join hierarchy As parent
On child.[LocationID] = parent.ID
Where child.ID != parent.[Location ID])
-- invokes the above expression.
Select *
From hierarchy
Teniendo en cuenta los datos de ejemplo, usted debe obtener algo como esto:
ID | Location ID | Name | Depth
_______| __________ |______ | _____
1331 | 1331 | House | 1
1321 | 1331 | Room | 2
2141 | 1321 | Bed | 3
Tenga en cuenta que "gimnasio" se excluye. En función de los datos de muestra, su ID no coincide con su [ID de ubicación], por lo que no sería un elemento de nivel raíz. Su ID de ubicación, 2231, no aparece en la lista de identificadores principales válidos.
Edición 1:
Ha pedido de conseguir esto en una estructura de datos de C#. Hay muchas, muchas formas diferentes de representar una jerarquía en C#. Aquí hay un ejemplo, elegido por su simplicidad. Una muestra de código real sin duda sería más extensa.
El primer paso es definir cómo se ve cada nodo en la jerarquía. Además de contener propiedades para cada dato en el nodo, he incluido Parent
y Children
propiedades, más métodos para Add
un niño y Get
un niño. El método Get
buscará en todo el eje descendente del nodo, no solo en los propios hijos del nodo.
public class LocationNode {
public LocationNode Parent { get; set; }
public List<LocationNode> Children = new List<LocationNode>();
public int ID { get; set; }
public int LocationID { get; set; }
public string Name { get; set; }
public void Add(LocationNode child) {
child.Parent = this;
this.Children.Add(child);
}
public LocationNode Get(int id) {
LocationNode result;
foreach (LocationNode child in this.Children) {
if (child.ID == id) {
return child;
}
result = child.Get(id);
if (result != null) {
return result;
}
}
return null;
}
}
Ahora querrá poblar su árbol. Aquí tienes un problema: es difícil completar un árbol en el orden incorrecto. Antes de agregar un nodo secundario, realmente necesita una referencia al nodo primario. Si tiene para hacerlo fuera de servicio, puede mitigar el problema haciendo dos pasadas (una para crear todos los nodos, y luego otra para crear el árbol). Sin embargo, en este caso, eso es innecesario.
Si toma la consulta SQL que proporcioné arriba y ordena por la columna depth
, puede estar matemáticamente seguro de que nunca encontrará un nodo secundario antes de encontrar su nodo principal. Por lo tanto, puedes hacer esto en una sola pasada.
Aún necesitará un nodo para que sirva como la "raíz" de su árbol. Tienes que decidir si esto será "House" (de tu ejemplo), o si es un nodo ficticio de marcador de posición que creas solo para este propósito. Sugiero el más tarde.
¡Así que, al código! De nuevo, esto está optimizado para simplificar y facilitar la lectura. Hay algunos problemas de rendimiento que quizás desee abordar en el código de producción (por ejemplo, no es realmente necesario buscar constantemente el nodo "primario"). Evité estas optimizaciones porque aumentan la complejidad.
// Create the root of the tree.
LocationNode root = new LocationNode();
using (SqlCommand cmd = new SqlCommand()) {
cmd.Connection = conn; // your connection object, not shown here.
cmd.CommandText = "The above query, ordered by [Depth] ascending";
cmd.CommandType = CommandType.Text;
using (SqlDataReader rs = cmd.ExecuteReader()) {
while (rs.Read()) {
int id = rs.GetInt32(0); // ID column
var parent = root.Get(id) ?? root;
parent.Add(new LocationNode {
ID = id,
LocationID = rs.GetInt32(1),
Name = rs.GetString(2)
});
}
}
}
Ta-da! El LocationNode root
ahora contiene toda su jerarquía. Por cierto, no he ejecutado este código, así que avíseme si detecta algún problema evidente.
Editar 2
para fijar su código de ejemplo, hacer estos cambios:
elimine esta línea:
// Create an instance of the tree
TreeView t1 = new TreeView();
Esta línea no es realmente un problema, pero debería ser removido. Sus comentarios aquí son inexactos; en realidad no estás asignando un árbol al control. En su lugar, está creando un nuevo TreeView, asignándolo al t1
, y luego asignando un objeto diferente al t1
. El TreeView que crea se pierde tan pronto como se ejecuta la siguiente línea.
arreglar su instrucción SQL
// SQL Commands
string getLocations = "SELECT ID, LocationID, Name FROM dbo.Locations";
Reemplazar esta sentencia SQL con la instrucción SQL que he sugerido anteriormente, con una cláusula ORDER BY. Lea mi edición anterior que explica por qué la "profundidad" es importante: realmente desea agregar los nodos en un orden particular. No puede agregar un nodo secundario hasta que tenga el nodo principal.
Opcionalmente, creo que no es necesario la sobrecarga de un SqlDataAdapter y DataTable aquí. La solución DataReader que originalmente sugerí es más simple, más fácil de trabajar y más eficiente en términos de recursos.
Además, la mayoría de los objetos de C# SQL implementan IDisposable
, por lo que tendrá que asegurarse de que está utilizando correctamente. Si algo implementa IDisposable
, estar seguro de que lo envuelve en using
declaraciones (véase mi C# código de ejemplo anterior).
arreglar su árbol de fomento de bucle
Usted está consiguiendo solamente los nodos padre e hijo porque tiene un lazo para los padres y un bucle interno para los niños. Como ya debe saber, no recibirá los nietos porque no tiene un código que los agregue.
Se podría añadir un bucle interior-interior para obtener los nietos, pero está claro que está pidiendo ayuda porque se ha dado cuenta de que al hacerlo sólo conducirá a la locura. ¿Qué pasaría si luego quisieras los bisnietos? ¿Un bucle interno-interno-interno? Esta técnica no es viable.
es probable que haya pensado en la recursividad aquí. Este es un lugar perfecto para ello, y si se trata de estructuras similares a árboles, eventualmente surgirá. Ahora que ha editado su pregunta, está claro que su problema tiene poco o nada que ver con SQL. Tu verdadero problema es con la recursividad. Alguien eventualmente puede venir e idear una solución recursiva para esto. Ese sería un enfoque perfectamente válido, y posiblemente preferible.
Sin embargo, mi respuesta ya ha cubierto la parte recursiva - simplemente se ha movido en la capa de SQL. Por lo tanto, mantendré mi código anterior, ya que creo que es una respuesta genérica adecuada a la pregunta. Para su situación específica, necesitará hacer algunas modificaciones más.
En primer lugar, usted no necesitará la clase LocationNode
que he sugerido. Está utilizando TreeNode
en su lugar, y eso funcionará bien.
En segundo lugar, el TreeView.FindNode
es similar al método LocationNode.Get
que sugerí, excepto que FindNode
requiere la ruta completa al nodo. Para usar FindNode
, debe modificar el SQL para darle esta información.
Por lo tanto, toda la función PopulateTree
debería tener este aspecto:
public void PopulateTree(TreeView t1) {
// Clear any exisiting nodes
t1.Nodes.Clear();
using (SqlConnection connection = new SqlConnection()) {
connection.ConnectionString = "((replace this string))";
connection.Open();
string getLocations = @"
With hierarchy (id, [location id], name, depth, [path])
As (
Select ID, [LocationID], Name, 1 As depth,
Cast(Null as varChar(max)) As [path]
From dbo.Locations
Where ID = [LocationID]
Union All
Select child.id, child.[LocationID], child.name,
parent.depth + 1 As depth,
IsNull(
parent.[path] + '/' + Cast(parent.id As varChar(max)),
Cast(parent.id As varChar(max))
) As [path]
From dbo.Locations As child
Inner Join hierarchy As parent
On child.[LocationID] = parent.ID
Where child.ID != parent.[Location ID])
Select *
From hierarchy
Order By [depth] Asc";
using (SqlCommand cmd = new SqlCommand(getLocations, connection)) {
cmd.CommandType = CommandType.Text;
using (SqlDataReader rs = cmd.ExecuteReader()) {
while (rs.Read()) {
// I guess you actually have GUIDs here, huh?
int id = rs.GetInt32(0);
int locationID = rs.GetInt32(1);
TreeNode node = new TreeNode();
node.Text = rs.GetString(2);
node.Value = id.ToString();
if (id == locationID) {
t1.Nodes.Add(node);
} else {
t1.FindNode(rs.GetString(4)).ChildNodes.Add(node);
}
}
}
}
}
}
Por favor, hágamelo saber si usted encuentra algún error adicionales!
Esos nombres son confusos. Cambiaría 'Location ID' por' Parent_ID'. – FrustratedWithFormsDesigner
Una respuesta de SQL puro puede necesitar saber qué sabor de SQL está utilizando. ¿Puedes especificar qué base de datos tienes? SQL Server 2008, tal vez? –
Desafortunadamente no puedo cambiar los nombres, demasiadas cosas dependen de ellos. Estoy usando SQL Server 2008. – Will