以树视图格式分组

时间:2012-01-15 18:44:46

标签: c# sql aggregate-functions

我有一个数据库,我可以保存贷款人的数据。贷款可以由一个人或一群人制作。我向您展示了我的数据库架构的一部分。

enter image description here

'Acreditados'保持'人物'(人)和'信贷'(贷款)之间的关系,因此一个人可以有多个贷款,一个贷款可以由多个人做出。 'agrupaciones'保持'acreditados'(已经有贷款的人)和'grupos'(按组分类的贷款有组名)之间的关系。 最后,'movimientos'为每个人保留费用和付款。

这里的问题是如何通过贷款(信用卡)查询我的数据库以对所有付款(movimientos)进行分组,如果它是该人的个人贷款展示名称但是如果它是该组的分组贷款展示名称?

我已经有了以下查询:

SELECT CR.id_credito, SUM(M.monto) AS Monto, SUM(M.interes) AS Interes, SUM(M.iva) AS IVA, SUM(M.capital) AS Capital, M.fecha_mov
FROM movimientos AS M
JOIN cargos AS C ON C.id_movimiento = M.id_movimiento
JOIN acreditados AS A ON A.id_acreditado = M.id_acreditado
JOIN creditos AS CR ON CR.id_credito = A.id_credito
WHERE C.status = 0 
GROUP BY CR.id_credito, M.fecha_mov
ORDER BY M.fecha_mov

通过此查询,我按“信贷”(贷款)和按日期对付款进行分组,因为每笔付款都有不同的日期。但是,我如何加入“人物角色”(人)表以获取贷款人的姓名,以及是否按群组的显示名称贷款?事实上,如果传统查询可以做到这一点,我就不会这样做,而且这样做的目的是在c#中进行三视图,其中组的名称将是父节点,每个人都是子节点。个人贷款将是没有孩子的父节点。 任何帮助将不胜感激,谢谢

2 个答案:

答案 0 :(得分:0)

要回答与当前架构相关的问题,可以使用:

  • LEFT JOIN acreditados to personas and agrupaciones
  • 加入agrupaciones到grupos
  • 使用CASE语句确定是否从人物角色或grupos中提取nombre

至于在树视图中构建它,您可能希望创建一个递归查询来提取数据。

希望这有帮助。

答案 1 :(得分:0)

我会非常谨慎地加入可能会复制行的其他表,从而抛弃所有聚合函数。我的意思是:

假设您有2个人附加贷款,因此您从贷款支付开始,加入贷款,然后加入属于该贷款的人员。现在,您现在必须处理每笔贷款(每人一个)的2条记录。

我建议做的是使用OUTER APPLY将字段连接回您需要的字段。如果您从未使用外部/交叉应用,只需将其视为select语句中的子查询:

SELECT (SELECT TOP 1 i.someColumn FROM innerTable i WHERE i.ID = t.ID) FROM someTable AS t

除了它在连接本身之外。这意味着您可以在FROM子句中使用IN SCOPE子查询来加入。实际上,这可能是您最好的选择,以便将所有付款本地化为仅贷款并将所有人/组保留在此其他查询中。所以你的查询看起来像这样:

SELECT CR.id_credito, SUM(M.monto) AS Monto, SUM(M.interes) AS Interes, SUM(M.iva) AS IVA, SUM(M.capital) AS Capital, M.fecha_mov, ISNULL(tg.nombre, tp.nombre) Nombre
FROM movimientos AS M
JOIN cargos AS C ON C.id_movimiento = M.id_movimiento
JOIN acreditados AS A ON A.id_acreditado = M.id_acreditado
JOIN creditos AS CR ON CR.id_credito = A.id_credito
OUTER APPLY (SELECT TOP 1 G.nombre FROM grupos G JOIN agrupaciones AG on AG.id_grupo = G.id_grupo WHERE AG.id_acreditado = A.id_acreditado) tg
OUTER APPLY (SELECT TOP 1 P.nombre FROM personas P WHERE P.id_persona = A.id_persona) tp
WHERE C.status = 0 
GROUP BY CR.id_credito, M.fecha_mov, ISNULL(tg.nombre, tp.nombre)
ORDER BY M.fecha_mov

要做的一件事是,OUTER APPLY不需要连接回FROM子句中的其余表,因为它已经在假设你根据来自的值查询的情况下工作了FROM子句的其余部分源(表)。此外,请不要忘记为您的APPLY查询设置别名(在这种情况下,它是tgtp)。

编辑:刚刚注意到要求采用树视图格式。我现在要解决这个问题。

似乎无论如何,您都希望返回每个人的所有记录。在这种情况下,您希望为组内的所有人返回相同的记录。我认为这样做的最好方法是作为常规联接(而不是申请)加入人员表,然后只按tg.nombrepersonas.nombre字段进行分组。这样你仍然得到每个附加到贷款的人,但你也得到了返回的组名,这样如果有一个组,你可以将它用作树中的根节点。

至于实际创建它作为树视图,你将不得不做一些额外的工作,以实际上以这种方式设置它。我建议的是以可用的格式获取查询,并使用LINQ分组将它们编译在一起。

然后需要以这种方式更改查询: 将上述更改中的OUTER APPLY (SELECT TOP 1 P.nombre FROM personas P WHERE P.id_persona = A.id_persona) tp行更改为INNER JOIN personas P on P.id_persona = A.id_persona,将选择/分组从ISNULL(tg.nombre, tp.nombre) Nombre更改为tg.nombre Grupo, P.nombre Nombre。在您的代码中,在获取查询结果后,使用以下LINQ语句来获取您的查询(假设它至少是.NET 3.5):

// Assume the query results are in dtLoanPayments strongly typed datatable.
// Also assume we have a TreeView called tvLoans
dtLoanPayments.AsEnumerable().GroupBy(tr => tr.Grupo).ToList().ForEach(tr =>
{
    (tr.Key == null ? tvLoans.Nodes : tvLoans.Nodes.Add(tr.Key)).AddRange
        (tr.Select(ti => { TreeNode tempNode = new TreeNode(ti.Nombre); tempNode.Tag = ti; return tempNode; }).ToArray());
});

同样,它实际上取决于你想要它们的显示方式。如果您希望由此人进一步分组,那么您需要在内部tr.Select之前执行另一个GroupBy,因此它将是tr.GroupBy(ti => ti.Personas).Select,并且您必须为每个贷款支付记录创建另一个TreeNode。在这些类型的情况下,请记住,保持显示和数据逻辑分离通常要容易得多。在查询方面,只需确保您获取的数据是正确的,然后在应用程序代码中处理格式化。

编辑:要回答您的评论问题,请添加更多代码。 这只是将项目列表转换为正确嵌套的TreeNode对象的问题。我认为我的解释有点回答它,但我可以给你代码解释究竟发生了什么。假设您的列表名为amortList,而您的TreeView名为tvLoans,请尝试以下操作:

// First, group by group name. If it's null (meaning no group), then it will just be 
// the person's name. Doing ToList() after grouping so we can do a ForEach.
amortList.GroupBy(tr => string.IsNullOrEmpty(tr.Grupo) ? tvLoans : tvLoans.Add(tr.Grupo)).ToList()
    .ForEach(tr => 
    // This gives us the node we'll be adding each record to as the key. Now all
    // we need to do is add all loan records to the node, grouping by person
    {
        tr.Key.Nodes.AddRange(
            // Group by person's name first
            tr.GroupBy(ti => new TreeNode(ti.Nombre))
                // Then transform into each node (loan payment, etc) and add to the person node.
                .Select(ti => 
                { 
                    ti.Key.Nodes.AddRange(
                        ti.Select(tn => new TreeNode(/*Use whatever field here to display.*/).ToArray());
                    return ti.Key; // Return TreeNode.
                }).ToArray());
    }

如果这仍然令人困惑,你可以做一个完成相同事情的foreach(并且更容易理解)。

foreach(_Amortizacion amNode in amortList)
{
    // Using Null Coalescing to create the node if it doesn't exist.
    // First check if group is null. If it isn't, try to pull the node and create it if it doesn't exist.
    TreeNodeCollection workingNode = !string.IsNullOrEmpty(amNode.Grupo) ?
        (tvLoans.Nodes[amNode.Grupo] ?? tvLoans.Nodes.Add(amNode.Grupo)).Nodes :
        tvLoans.Nodes;
    // At this point, workingNode is the NodeCollection we'll be adding the person to. Creating if doesn't exist.
    workingNode = (workingNode[amNode.Personas] ?? workingNode.Add(amNode.Personas)).Nodes;
    // Now we're adding the actual loan record to the person Node.
    workingNode.Add(new TreeNode(/* This is what will show up in TreeView. */) { Tag = amNode, Name = /* Specify a key here, in case you want to search. */ });
}