У меня проблема с запросом к одной из наших баз данных MS 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 + 2 + 3 + 3 + 3)/5 = 2,4 вместо среднего значения «местоположение 1-1» и «locatio п 1-2" - (2 + 3)/2 = 2,5
Я попытался решить это с помощью КТР, но ударил проблему с помощью GROUP BY и агрегатных функций в рекурсивной части КТР -
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
Я был бы очень признателен за некоторые комментарии относительно того, подходит ли это подход или нет.
Ваш вопрос не совсем ясен, потому что неясно, как выглядят ваши данные и какие результаты вы ожидаете. Можете ли вы опубликовать простой тестовый пример, который точно показывает, чего вы пытаетесь достичь? В тестовом случае нет необходимости использовать ваши реальные таблицы или данные, это просто должно проиллюстрировать ваше требование. – Pondlife