2016-01-06 8 views
4

У меня есть проект моей базы данных в соответствии с диаграммой.Иерархическая сумма SQL Server столбца

enter image description here

  • Category таблица самостоятельная ссылка родительских отношений ДЕТСКОГО
  • Budget будет иметь все категории и количество определяет для каждой категории
  • Expense таблица будет иметь записи для категорий, для которых сумма была потратьте (см. таблицу Total из этой таблицы).

Я хочу написать оператор выбора, который будет извлекать набор данных с колоннами, приведенными ниже:

ID 
CategoryID 
CategoryName 
TotalAmount (Sum of Amount Column of all children hierarchy From BudgetTable ) 
SumOfExpense (Sum of Total Column of Expense all children hierarchy from expense table) 

Я пытался использовать КТР, но был не в состоянии производить что-нибудь полезное. Заранее благодарны за Вашу помощь. :)

Update

Я просто объединить и упростить данные, которые я создал один вид с ниже запроса.

SELECT   
    dbo.Budget.Id, dbo.Budget.ProjectId, dbo.Budget.CategoryId, 
    dbo.Budget.Amount, 
    dbo.Category.ParentID, dbo.Category.Name, 
    ISNULL(dbo.Expense.Total, 0) AS CostToDate 
FROM 
    dbo.Budget 
INNER JOIN 
    dbo.Category ON dbo.Budget.CategoryId = dbo.Category.Id 
LEFT OUTER JOIN 
    dbo.Expense ON dbo.Category.Id = dbo.Expense.CategoryId 

В основном это должно приводить к таким результатам.

+0

Просьба представить некоторые выборочные данные и ожидаемый результат. –

+0

@FelixPamittan Я редактировал вопрос с более подробной информацией –

+0

@SachinTrivedi, Попробуйте рекурсивный запрос, который у меня есть в ответе. Возможно, вам придется немного поработать над этим, так как у меня нет доступа к тесту, запустите его с некоторыми данными. – Sunil

ответ

2

Это интересная проблема. И я собираюсь решить его с помощью иерархии. Во-первых, установка:

USE tempdb; 
IF OBJECT_ID('dbo.Hierarchy') IS NOT NULL 
    DROP TABLE dbo.[Hierarchy]; 

CREATE TABLE dbo.Hierarchy 
(
    ID INT NOT NULL PRIMARY KEY, 
    ParentID INT NULL, 
     CONSTRAINT [FK_parent] FOREIGN KEY ([ParentID]) REFERENCES dbo.Hierarchy([ID]), 
    hid HIERARCHYID, 
    Amount INT NOT null 
); 

INSERT INTO [dbo].[Hierarchy] 
     ([ID], [ParentID], [Amount]) 
VALUES 
    (1, NULL, 100), 
    (2, 1, 50), 
    (3, 1, 50), 
    (4, 2, 58), 
    (5, 2, 7), 
    (6, 3, 10), 
    (7, 3, 20) 
SELECT * FROM dbo.[Hierarchy] AS [h]; 

Далее, чтобы обновить спрятанный столбец с надлежащим значением для hiearchyid.Я буду использовать трясины стандартный рекурсивный КТР для этого

WITH cte AS (
    SELECT [h].[ID] , 
      [h].[ParentID] , 
      CAST('/' + CAST(h.[ID] AS VARCHAR(10)) + '/' AS VARCHAR(MAX)) AS [h], 
      [h].[hid] 
    FROM [dbo].[Hierarchy] AS [h] 
    WHERE [h].[ParentID] IS NULL 

    UNION ALL 

    SELECT [h].[ID] , 
      [h].[ParentID] , 
      CAST([c].[h] + CAST(h.[ID] AS VARCHAR(10)) + '/' AS VARCHAR(MAX)) AS [h], 
      [h].[hid] 
    FROM [dbo].[Hierarchy] AS [h] 
    JOIN [cte] AS [c] 
      ON [h].[ParentID] = [c].[ID] 
) 
UPDATE [h] 
SET hid = [cte].[h] 
FROM cte 
JOIN dbo.[Hierarchy] AS [h] 
    ON [h].[ID] = [cte].[ID]; 

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

SELECT p.id, SUM([c].[Amount]) 
FROM dbo.[Hierarchy] AS [p] 
JOIN [dbo].[Hierarchy] AS [c] 
    ON c.[hid].IsDescendantOf(p.[hid]) = 1 
GROUP BY [p].[ID]; 
+0

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

+0

Одна вещь, которую поддерживает HIERARCHYID в EF? Я использую EF 6, и я думаю, что в нем не поддерживается. –

+0

Похоже, что это не из коробки, но вы можете инкапсулировать все/все вышеперечисленное в хранимую процедуру и выставить только идентификатор и сумму, если хотите. И EF счастливо работает с хранимыми процедурами. –

2

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

Решение состоит из двух этапов.

  1. Создать функцию скалярного значения, которая будет определять, является ли categoryId прямым или косвенным дочерним элементом другого categoryId. Это дается в первом фрагменте кода. Обратите внимание, что для этого используется рекурсивный запрос, поскольку это лучший подход при работе с иерархией в SQL Server.

  2. Напишите текущий общий запрос, который даст итоговые значения в соответствии с вашими требованиями для всех категорий. Вы можете фильтровать по категориям, если хотите по этому запросу. Второй фрагмент кода предоставляет этот запрос.

скалярная функция, которая говорит, если ребенок категория является прямым или косвенным потомком другой категории

CREATE FUNCTION dbo.IsADirectOrIndirectChild(
       @childId int, @parentId int) 
RETURNS int 
AS 
BEGIN 

    DECLARE @isAChild int; 
    WITH h(ParentId, ChildId) 
    -- CTE name and columns 
     AS (
     SELECT TOP 1 @parentId, @parentId 
     FROM dbo.Category AS b 
     UNION ALL 
     SELECT b.ParentId, b.Id AS ChildId 
     FROM h AS cte 
       INNER JOIN 
       Category AS b 
       ON b.ParentId = cte.ChildId AND 
       cte.ChildId IS NOT NULL) 
     SELECT @isAChild = ISNULL(ChildId, 0) 
     FROM h 
     WHERE ChildId = @childId AND 
       ParentId <> ChildId 
     OPTION(MAXRECURSION 32000); 
    IF @isAChild > 0 
    BEGIN 
     SET @isAChild = 1; 
    END; 
    ELSE 
    BEGIN 
     SET @isAChild = 0; 
    END; 
    RETURN @isAChild; 
END; 
GO 

Запрос для запуска полного запуска из нижней части иерархии

SELECT c.Id AS CategoryId, c.Name AS CategoryName, 
(
    SELECT SUM(ISNULL(b.amount, 0)) 
    FROM dbo.Budget AS b 
    WHERE dbo.IsADirectOrIndirectChild(b.CategoryId, c.Id) = 1 OR 
      b.CategoryId = c.Id 
) AS totalAmount, 
(
    SELECT SUM(ISNULL(e.total, 0)) 
    FROM dbo.Expense AS e 
    WHERE dbo.IsADirectOrIndirectChild(e.CategoryId, c.Id) = 1 OR 
      e.CategoryId = c.Id 
) AS totalCost 
FROM dbo.Category AS c; 
Смежные вопросы