2012-02-05 6 views
1

Microsoft SQL Server 데이터베이스 중 하나에 대한 쿼리에 문제가 있습니다. 다음 표와보기는 간략하게하기 위해 단순화되었지만 문제를 설명하는 역할을합니다.SQL Server 재귀 쿼리 문제

각 테이블의 점수는 직계 자녀의 평균으로 계산됩니다. 뷰는 고정 된 구조로는 충분하지만 현재 중첩 된 세트 형식 인 위치 계층 구조와 더 복잡해집니다. 사용자가 정의한 위치 계층에는 고정 된 수의 계층이 없습니다.

재귀 CTE를 사용하여이 문제를 해결했지만 재귀 부분에서 집계를 허용하지 않았습니다.

CREATE TABLE [dbo].[locations_main](
    [id] [smallint] NOT NULL, 
    [name] [nchar](50) NOT NULL, 
    [lft] [smallint] NOT NULL, 
    [rgt] [smallint] NOT NULL, 
    [parent_id] [smallint] NULL, 
    CONSTRAINT [PK_locations_main] PRIMARY KEY CLUSTERED ([id] ASC) 
) 
GO 

INSERT INTO [dbo].[locations_main] VALUES 
    (1, 'location 1', 1, 16, NULL), 
    (2, 'location 1-1', 2, 9, 1), 
    (3, 'location 1-1-1', 3, 4, 2), 
    (4, 'location 1-1-2', 5, 6, 2), 
    (5, 'location 1-1-3', 7, 8, 2), 
    (7, 'location 1-2', 10, 15, 1), 
    (8, 'location 1-2-1', 11, 12, 7), 
    (9, 'location 1-2-2', 13, 14, 7) 
GO 

CREATE TABLE [dbo].[outcomes](
    [id] [smallint] NOT NULL, 
    [location_id] [smallint] NOT NULL, 
    [name] [nvarchar](50) NOT NULL, 
CONSTRAINT [PK_outcomes] PRIMARY KEY CLUSTERED ([id] ASC) 
) 
GO 

INSERT INTO [dbo].[outcomes] VALUES 
    (1, 3, 'outcome 1'), 
    (2, 4, 'outcome 2'), 
    (3, 5, 'outcome 3'), 
    (4, 8, 'outcome 4'), 
    (5, 9, 'outcome 5') 
GO 

CREATE TABLE [dbo].[prompts](
    [id] [smallint] NOT NULL, 
    [outcome_id] [smallint] NOT NULL, 
    [name] [nvarchar](50) NOT NULL, 
CONSTRAINT [PK_prompts] PRIMARY KEY CLUSTERED ([id] ASC) 
) 
GO 

INSERT INTO [dbo].[prompts] VALUES 
    (1, 1, 'prompt 1'), 
    (2, 2, 'prompt 2'), 
    (3, 3, 'prompt 3'), 
    (4, 4, 'prompt 4'), 
    (5, 5, 'prompt 5') 
GO 

CREATE TABLE [dbo].[subprompts](
    [id] [smallint] NOT NULL, 
    [prompt_id] [smallint] NOT NULL, 
    [name] [nvarchar](50) NOT NULL, 
    [score] [smallint] NOT NULL, 
CONSTRAINT [PK_subprompts] PRIMARY KEY CLUSTERED ([id] ASC) 
) 
GO 

INSERT INTO [dbo].[subprompts] VALUES 
    (1, 1, 'subprompt 1', 1), 
    (2, 1, 'subprompt 2', 1), 
    (3, 2, 'subprompt 3', 1), 
    (4, 2, 'subprompt 4', 3), 
    (5, 3, 'subprompt 5', 2), 
    (6, 3, 'subprompt 6', 4), 
    (7, 4, 'subprompt 7', 1), 
    (8, 4, 'subprompt 8', 5), 
    (9, 5, 'subprompt 9', 3), 
    (10, 5, 'subprompt 10', 3) 
GO 

CREATE VIEW [dbo].[vw_prompts] 
AS 
SELECT 
    dbo.prompts.id, 
    dbo.prompts.outcome_id, 
    dbo.prompts.name, 
    AVG(dbo.subprompts.score) AS score 
FROM dbo.prompts 
LEFT OUTER JOIN dbo.subprompts 
    ON dbo.prompts.id = dbo.subprompts.prompt_id 
GROUP BY 
    dbo.prompts.id, 
    dbo.prompts.outcome_id, 
    dbo.prompts.name 
GO 

CREATE VIEW [dbo].[vw_outcomes] 
AS 
SELECT 
    dbo.outcomes.id, 
    dbo.outcomes.location_id, 
    dbo.outcomes.name, 
    AVG(dbo.vw_prompts.score) AS score 
FROM dbo.outcomes 
LEFT OUTER JOIN dbo.vw_prompts 
    ON dbo.outcomes.id = dbo.vw_prompts.id 
GROUP BY 
    dbo.outcomes.id, 
    dbo.outcomes.location_id, 
    dbo.outcomes.name 
GO 

쿼리는 아래의 모든 위치를 검색하지만, 리프 노드에서하지 해당 위치의 직계 자식 평균값을 계산 -

SELECT loc_main_ag.name, AVG(CAST(vw_outcomes.score AS FLOAT)) 
FROM locations_main loc_main_ag 
LEFT JOIN locations_main loc_main 
    ON loc_main_ag.lft <= loc_main.lft 
    AND loc_main_ag.rgt >= loc_main.rgt 
INNER JOIN vw_outcomes 
    ON loc_main.id = vw_outcomes.location_id 
GROUP BY loc_main_ag.name 

반환

location 1  2.4 
location 1-1  2 
location 1-1-1 1 
location 1-1-2 2 
location 1-1-3 3 
location 1-2  3 
location 1-2-1 3 
location 1-2-2 3 

"위치를 1 "은 평균"위치 1-1-1 ","위치 1-1-2 ","위치 1-1-3 ","위치 1-2-1 "및"위치 1-2-2 " - "위치 1-1"및 "위치"의 평균 대신에 (1 + 2 + 3 + 3 + 3)/5 = 2.4 n은 1-2 "- 나는 CTE를 사용하여이 문제를 해결하기 위해 노력하지만, CTE의 재귀 부분에서 GROUP BY와 집계 함수를 사용하여 문제를 충돌 (2 + 3)/2 = 2.5

-

WITH location_scores 
AS 
(
-- Anchor member definition 
-- Get score for all leaf node locations 
SELECT locations_main.id, locations_main.name, locations_main.parent_id, AVG(CAST(vw_outcomes.score AS FLOAT)) AS score 
FROM locations_main 
INNER JOIN vw_outcomes 
    ON locations_main.id = vw_outcomes.location_id 
WHERE locations_main.rgt - locations_main.lft = 1 
GROUP BY locations_main.id, locations_main.name, locations_main.parent_id 

UNION ALL 

-- Recursive member definition 
-- Rollup through locations parents to build averages 
SELECT locations_main.id, locations_main.name, locations_main.parent_id, AVG(CAST(location_scores.score AS FLOAT)) AS score 
FROM locations_main 
INNER JOIN vw_outcomes 
    ON locations_main.id = vw_outcomes.location_id 
INNER JOIN location_scores 
    ON locations_main.id = location_scores.parent_id 
GROUP BY locations_main.id, locations_main.name, locations_main.parent_id 

) 
-- Statement that executes the CTE 
SELECT * 
FROM location_scores 

업데이트 : 여기 테이블 값 기능에 대한 시도입니다. 여기에 포함 된 단순화 된 예제를 기반으로 올바른 결과를 반환하지만 이것이 어떻게 확장 될지 걱정됩니다. 야생에서 실행되는 계층 구조는 15^5 레코드의 어딘가에있을 수 있습니다.

CREATE FUNCTION scores() RETURNS 
    @result TABLE 
    (
     id    SMALLINT, 
     name   NVARCHAR(50), 
     lft    SMALLINT, 
     rgt    SMALLINT, 
     parent_id  SMALLINT, 
     score   FLOAT, 
     [level]   SMALLINT 
    ) AS 
BEGIN 
    DECLARE @level INT 
    SET @level = 1 

    INSERT INTO @result 
     SELECT 
      locations_main.id, 
      locations_main.name, 
      locations_main.lft, 
      locations_main.rgt, 
      locations_main.parent_id, 
      AVG(CAST(vw_outcomes.score AS FLOAT)) AS score, 
      @level AS [level] 
     FROM locations_main 
     INNER JOIN vw_outcomes 
      ON locations_main.id = vw_outcomes.location_id 
     WHERE locations_main.rgt - locations_main.lft = 1 
     GROUP BY 
      locations_main.id, 
      locations_main.name, 
      locations_main.lft, 
      locations_main.rgt, 
      locations_main.parent_id 

    WHILE (SELECT COUNT(*) FROM @result WHERE level = @level AND parent_id IS NOT NULL) > 0 BEGIN 

     INSERT INTO @result 
     SELECT 
      locations_main.id, 
      locations_main.name, 
      locations_main.lft, 
      locations_main.rgt, 
      locations_main.parent_id, 
      AVG(CAST(res.score AS FLOAT)) AS score, 
      (@level + 1) AS [level] 
     FROM locations_main 
     INNER JOIN @result res 
      ON locations_main.id = res.parent_id 
      AND res.level = @level 
     GROUP BY 
      locations_main.id, 
      locations_main.name, 
      locations_main.lft, 
      locations_main.rgt, 
      locations_main.parent_id 

     SET @level = @level + 1 

    END 

RETURN 
END 

이 방법이 적절한 방법인지 아닌지에 관해서는 정말 고마워 할 것입니다.

+0

귀하의 데이터가 어떻게 보이고 어떤 결과가 기대되는지는 분명하지 않기 때문에 귀하의 질문은 명확하지 않습니다. 당신이 달성하고자하는 것을 정확히 보여주는 간단한 테스트 케이스를 게시 할 수 있습니까? 테스트 케이스는 실제 테이블이나 데이터를 사용할 필요가 없으며 요구 사항을 설명하기 만하면됩니다. – Pondlife

답변

0

이것은 내가 생각해내는 테이블 값 함수입니다. 나는 그것이 문제의 본질 때문에 누군가에게 유용 할 것이라고 생각하지 않지만, 여기에 완전성을 포함시키기를 포함한다. 전체 계층 구조에 걸쳐 합계 920k 레코드가 있으면 dev 서버에서 3 초 안에 반환됩니다.

CREATE FUNCTION cqc_location_template_scores (@location_id INT = NULL) RETURNS 
    @result TABLE 
    (
     id    SMALLINT, 
     name   NVARCHAR(50), 
     lft    SMALLINT, 
     rgt    SMALLINT, 
     parent_id  SMALLINT, 
     score   FLOAT, 
     [level]   SMALLINT 
    ) AS 
BEGIN 
    DECLARE @level INT 
    SET @level = 1 

    DECLARE @lft INT, @rgt INT 
    IF (@location_id IS NOT NULL) 
     SELECT @lft = lft, @rgt = rgt FROM locations_main WHERE id = @location_id 
    ELSE 
     SELECT @lft = NULL, @rgt = NULL 

    DECLARE @ROLLUP_TYPE VARCHAR(50) 
    SELECT @ROLLUP_TYPE = parmvalue FROM globals WHERE parameter = 'CQC_ROLLUP_TYPE' 

    -- TEST TO GUARD AGAINST INFINITE LOOP CAUSED BY LOCATIONS_MAIN RECORD BEING ITS OWN PARENT 
    IF ((SELECT COUNT(*) FROM locations_main WHERE id = parent_id) > 0) 
     RETURN 


    INSERT INTO @result 
     SELECT 
      locations_main.id, 
      locations_main.name, 
      locations_main.lft, 
      locations_main.rgt, 
      locations_main.parent_id, 

      CASE @ROLLUP_TYPE 
       WHEN 'AVE' THEN CAST(ROUND(AVG(CAST(CODE_CQC_STATUS.cod_score AS FLOAT)), 0) AS INT) 
       WHEN 'WORST' THEN MIN(CODE_CQC_STATUS.cod_score) 
       ELSE NULL 
      END AS score, 

      @level AS [level] 
     FROM locations_main 
     INNER JOIN cqc_outcomes 
      ON locations_main.id = cqc_outcomes.cdo_location 
     INNER JOIN CODE_CQC_STATUS 
      ON cqc_outcomes.cdo_status = CODE_CQC_STATUS.CODE 
     WHERE locations_main.rgt - locations_main.lft = 1 
     AND (locations_main.lft >= @lft OR @lft IS NULL) 
     AND (locations_main.rgt <= @rgt OR @rgt IS NULL) 
     GROUP BY 
      locations_main.id, 
      locations_main.name, 
      locations_main.lft, 
      locations_main.rgt, 
      locations_main.parent_id 

    WHILE (SELECT COUNT(*) FROM @result WHERE level = @level AND parent_id IS NOT NULL) > 0 BEGIN 

     INSERT INTO @result 
     SELECT 
      locations_main.id, 
      locations_main.name, 
      locations_main.lft, 
      locations_main.rgt, 
      locations_main.parent_id, 

      CASE @ROLLUP_TYPE 
       WHEN 'AVE' THEN CAST(ROUND(AVG(CAST(res.score AS FLOAT)), 0) AS INT) 
       WHEN 'WORST' THEN MIN(res.score) 
       ELSE NULL 
      END AS score, 

      (@level + 1) AS [level] 
     FROM locations_main 
     INNER JOIN @result res 
      ON locations_main.id = res.parent_id 
      AND res.level = @level 
     WHERE (locations_main.lft >= @lft OR @lft IS NULL) 
     AND (locations_main.rgt <= @rgt OR @rgt IS NULL) 
     GROUP BY 
      locations_main.id, 
      locations_main.name, 
      locations_main.lft, 
      locations_main.rgt, 
      locations_main.parent_id 

     SET @level = @level + 1 

     -- TEST TO GUARD AGAINST INFINITE LOOP 
     IF (@level > 10) 
     BEGIN 
      DELETE FROM @result 
      RETURN 
     END 

    END 

RETURN 
END 
GO 
0

죄송합니다 나는 내 SSMS

을하지만 내가 SQL 서버에서 재귀 쿼리에 대해 말할 수있는 것은 그들이 두 부분으로 형성된다는 것이다 그것을 복사 할 때 SQL 코드를 포맷 할 수 있습니다. 하나의 앵커와 하나의 재귀 부분.

당신은 재귀 샘플

그리고 그룹으로이 문제를 일으키는 경우, 당신은 재귀 부분 외부 쿼리 결과를 그룹화하는 방법을 생각할 수에 대한 기사 http://www.kodyaz.com/t-sql/sql-server-recursive-query-with-recursive-cte.aspx를 확인할 수 있습니다.

+0

답장을 보내 주셔서 감사합니다.재귀 부분에서 GROUP BY를 사용할 수 없기 때문에 CTE 옵션이 실행 가능하다고 생각하지 않습니다. GROUP BY가 없으면이 쿼리에서 재귀를 사용하면 중첩 된 관련 쿼리를 사용하여 계층 구조를 직접 반환 할 수 있으므로 아무런 도움이되지 않습니다. 나는 누군가가 나의 필요를 채워줄 다른 접근법을 제안 할 수 있기를 바라고있다. – nnichols