Сгруппировать по в формате дерева

У меня есть БД, где я храню данные людей, которые делают кредиты. Займы могут быть сделаны как одним человеком, так и группой людей. Я показываю вам часть моей схемы базы данных.

введите здесь описание изображения

«Acreditados» поддерживает связь между «Personas» (человек) и «Creditos» (кредиты), поэтому у одного человека может быть несколько кредитов, а один кредит может быть предоставлен несколькими людьми. Agrupaciones поддерживает отношения между acreditados (человек, у которого уже есть ссуда) и grupos (ссуды по группам имеют название группы). Наконец, «movimientos» удерживает сборы и платежи для всех.

Вопрос здесь в том, как мне запросить мою базу данных, чтобы сгруппировать все платежи (movimientos) по кредиту (creditos) и, если это индивидуальный кредит, показать имя человека, но если это групповой кредит, показать имя группы?

У меня уже есть следующий запрос:

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

С помощью этого запроса я группирую платежи по «кредитам» (кредитам) и по дате, поскольку у каждого платежа своя дата. Но как мне присоединиться к этому с таблицей «персоны» (человек), чтобы получить имя человека, который сделал ссуду, и если это ссуда по группе, покажите имя группы? На самом деле я не знаю, если это возможно с помощью обычного запроса, и цель этого состоит в том, чтобы создать трехмерное представление в С#, где имя группы будет родительским, а каждый человек - дочерним узлом. Индивидуальные ссуды были бы родительскими узлами без дочерних узлов. Любая помощь будет оценена, спасибо


person Jorge Zapata    schedule 15.01.2012    source источник
comment
Сделайте «личный» кредит группой только с одним участником (по умолчанию имя участника?), Сохраните массу проблем и дайте вам возможность добавлять и удалять других заемщиков в «группу». Поверьте мне, вам не нужен этот групповой/одиночный сплит, он будет раздражать вас чаще, чем этот.   -  person Tony Hopkinson    schedule 15.01.2012
comment
Итак, вы думаете, что вставка индивидуальных кредитов в «agrupaciones» с именем группы, установленным на имя участника по умолчанию, решит этот раскол? А как насчет трехглазой свиньи? Если я сделаю это, как я могу сделать трехмерное отображение информации о человеке в дочерних узлах?   -  person Jorge Zapata    schedule 15.01.2012
comment
Ваша проблема возникает из-за того, что вы имеете дело с кредитами на одну или даже на две разные вещи. Кредит -> Группа -> Заемщики. Вы можете пропустить промежуточный узел, если в группе только один заемщик, но это количество дочерних элементов при построении дерева, ваш запрос будет таким же и не зависит от представления.   -  person Tony Hopkinson    schedule 16.01.2012


Ответы (2)


Я бы очень осторожно относился к объединению дополнительных таблиц, которые могли бы дублировать строки и тем самым отбрасывать все ваши агрегатные функции. Я имею в виду следующее:

Предположим, у вас есть 2 человека, привязанных к кредиту, поэтому вы начинаете с платежа по кредиту, снова присоединяетесь к кредиту, а затем присоединяетесь к людям, которые принадлежат к этому кредиту. Теперь у вас будет 2 записи для каждого платежа по кредиту (по одной для каждого человека), с которыми вам теперь придется иметь дело.

Я бы рекомендовал использовать ВНЕШНЕЕ ПРИМЕНЕНИЕ, чтобы соединить поля с тем, что вам нужно. Если вы никогда не использовали внешнее/перекрестное применение, просто подумайте об этом как о подзапросе в операторе select:

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

За исключением того, что это внутри самого соединения. Это означает, что вы можете использовать подзапрос IN SCOPE в предложении FROM для обратного присоединения. На самом деле, это, вероятно, ваш лучший выбор, чтобы все платежи были локализованы только для кредитов и чтобы все лица/группы оставались в этом другом запросе. Таким образом, ваш запрос будет выглядеть примерно так:

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 (в данном случае это tg и tp).

Редактировать: только что заметил требование в формате древовидной структуры. Я займусь этим сейчас.

Кажется, что несмотря ни на что, вы хотите, чтобы ВСЕ записи для каждого человека также были возвращены. В этой ситуации вы хотите, чтобы одна и та же запись возвращалась для всех людей в группе. На мой взгляд, лучший способ сделать это — присоединиться к таблице лиц как обычное соединение (вместо применения), а затем просто сгруппировать по полям tg.nombre и personas.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());
});

Опять же, это действительно зависит от того, как вы хотите, чтобы они отображались. Если вам нужна дальнейшая группировка по человеку, вам нужно будет сделать еще один GroupBy перед внутренним tr.Select, чтобы это было 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. */ });
}
person SPFiredrake    schedule 17.01.2012
comment
Прежде всего, спасибо за вашу помощь. Я уже сделал свой запрос с помощью LINQ и получил результаты так же, как вы предложили. Мои результаты хранятся в списке‹_Amortizacion›, где _Amortizacion содержит необходимые поля. Как использовать LINQ для создания части дерева с использованием этого списка? - person Jorge Zapata; 18.01.2012
comment
Снова добавлено к моему ответу, что дает вам еще несколько вариантов для работы. Все это было сделано в этом окне, так что, вероятно, будут какие-то синтаксические ошибки (ничего вы не сможете различить). - person SPFiredrake; 19.01.2012
comment
Отлично, отличное объяснение. Спасибо, что уделили этому немного времени. - person Jorge Zapata; 19.01.2012

Чтобы ответить на ваш вопрос, связанный с вашей текущей схемой, вы можете использовать:

  • LEFT JOIN аккредитации для пользователей и agrupaciones
  • ПРИСОЕДИНЯЙТЕСЬ к группам
  • Используйте оператор CASE, чтобы определить, извлекается ли имя из персон или групп.

Что касается построения этого в древовидном представлении, вам нужно создать рекурсивный запрос для извлечения данных.

Надеюсь это поможет.

person user1135379    schedule 17.01.2012
comment
но каким будет логическое выражение, оцениваемое в операторе CASE? Я попробовал что-то вроде SELECT 'Nombre'= CASE WHEN P.id_persona THEN P.nombres WHEN G.id_grupo THEN G.nombre END ,CR.id_credito ... но не сработало - person Jorge Zapata; 17.01.2012