2014-02-05 4 views
9

У меня есть таблица с родительским/дочерним отношением вместе с созданной датой колонкой, ссылающейся на себя. Я хочу показать каждую родительскую запись и все потомки, упорядоченные по последнему «действию» на узле. Итак, если строка номер 1, которая была создана давно, добавила к ней нового ребенка (например, новый ребенок добавлен к одному из его дочерних элементов, например), то я хочу, чтобы это было в верхней части результатов.CTE с рекурсией - row_number() aggregated

У меня возникли проблемы с тем, чтобы это работало в настоящее время.

Моя структура таблицы выглядит следующим образом:

CREATE TABLE [dbo].[Orders](
    [OrderId] [int] NOT NULL, 
    [Orders_OrderId] [int] NULL, 
    [DateOrdered] datetime) 

Я написал следующий SQL, чтобы вытащить информацию из:

WITH allOrders AS 
    (SELECT po.orderid, po.Orders_OrderId, po.DateOrdered, 0 as distance, 
    row_number() over (order by DateOrdered desc) as RN1 
    FROM orders po WHERE po.Orders_OrderId is null 
    UNION ALL 
    SELECT b2.orderid ,b2.Orders_OrderId, b2.DateOrdered, c.distance + 1, 
    c.RN1 
    FROM orders b2 
    INNER JOIN allOrders c 
    ON b2.Orders_OrderId = c.orderid 
    ) 

SELECT * from allOrders 
where RN1 between 0 and 2 
order by rn1 asc, distance asc 

Есть ли способ, что я могу «агрегатные» Результаты работы рекурсивный выбор, чтобы я мог выбрать максимальную дату на всем «родительском» узле?

SQLFiddle демонстрация: http://sqlfiddle.com/#!3/ca6cb/11 (запись номер 1 должен быть первым, как она есть ребенок, который был обновлен недавно)

Update Благодаря предложению от @twrowsell У меня есть следующий запрос, который делает работает, но кажется действительно неуклюжим и имеет некоторые проблемы с производительностью, я чувствую, что мне не нужно иметь 3 CTE для достижения этого. Можно ли его сконфигурировать, сохраняя «номера строк» ​​(как это показано на дисплее пользователя с пейджингом)?

WITH allOrders AS 
    (SELECT po.orderid, po.Orders_OrderId, 0 as distance, po.DateOrdered, po.orderid as [rootId] 
    FROM orders po WHERE po.Orders_OrderId is null 
    UNION ALL 
    SELECT b2.orderid ,b2.Orders_OrderId, c.distance + 1, b2.DateOrdered, c.[rootId] 
    FROM orders b2  
    INNER JOIN allOrders c 
    ON b2.Orders_OrderId = c.orderid 
    ), 
    mostRecentOrders as (
    SELECT *, 
    MAX(DateOrdered) OVER (PARTITION BY rootId) as [HighestOrderId] 
    from allOrders 
    ), 
    pagedOrders as (
    select *, dense_rank() over (order by [HighestOrderId] desc) as [PagedRowNumber] from mostRecentOrders) 

    SELECT * from pagedOrders 
    where PagedRowNumber between 0 and 2 
    order by [HighestOrderId] desc 

Кроме того, я мог бы использовать в качестве MAX(orderid) OrderID является идент и datecreated не может быть обновлен в моем сценарии после его создания.

Обновлено SQLFiddle: http://sqlfiddle.com/#!3/ca6cb/41

+0

Какая версия SQL Server вы используете? –

+0

@JonSenchyna SQL 2008 –

+0

@EdW, Просто покажите свое желание, полученное из данных образца в скрипке. – KumarHarsh

ответ

1

бы с помощью MAX на DateOrdered в предложении OVER во внешней выберите работы ..

WITH allOrders AS 
(
    SELECT po.orderid, po.Orders_OrderId, po.DateOrdered, 0 as distance, 
     row_number() over (order by DateOrdered desc) as RN1 
    FROM orders po WHERE po.Orders_OrderId is null 
    UNION ALL 
    SELECT b2.orderid ,b2.Orders_OrderId, b2.DateOrdered, c.distance + 1, 
     c.RN1 
    FROM orders b2 
    INNER JOIN allOrders c 
    ON b2.Orders_OrderId = c.orderid 
    ) 


    SELECT *, MAX(DateOrdered) OVER (PARTITION BY Orders_OrderId) from allOrders 
    where RN1 between 0 and 2 
    order by rn1 asc, distance asc 

EDIT: Жаль, что я неправильно истолковал ваше требование в первый раз. Похоже, что вы хотите разделить результаты по области RN1 не Orders_OrderId так что ваш внешний выбор будет что-то вроде ..

SELECT MAX(DateOrdered) OVER (PARTITION BY RN1),* from allOrders 
where RN1 between 0 and 2 
order by rn1 asc, distance asc 
+0

Это не работает, потому что Orders_OrderId не однозначно идентифицирует «узел», поэтому он не выбирает высшая дата всех родителей и детей. Это дает мне несколько идей для работы, хотя, спасибо –

+0

@ EdW Я отредактировал свой ответ, если это помогает? – twrowsell

+0

Я видел, что спасибо @twrowsell, хотя он был отредактирован после того, как я опубликовал это в моем вопросе;) на самом деле кажется, что он быстрее разбивается на «rootid» (который однозначно идентифицирует каждый «узел»). Я все еще ищу улучшения для всего запроса, поэтому я добавил щедрость, возможно, я не был достаточно ясным, что хотел сохранить пейджинг в моем исходном вопросе. –

1

Посмотрите на следующие:

;WITH allOrders AS 
    (SELECT po.orderid, po.Orders_OrderId, po.DateOrdered, 0 as distance, po.orderid as [parentOrder] 
    FROM orders po WHERE po.Orders_OrderId is null 
     UNION ALL 
    SELECT b2.orderid ,b2.Orders_OrderId, b2.DateOrdered, c.distance + 1, c.[parentOrder] 
    FROM orders b2 
    INNER JOIN allOrders c ON b2.Orders_OrderId = c.orderid 

    ) 
    SELECT a.OrderId 
      ,a.Orders_OrderId 
      ,a.DateOrdered 
      ,top1.DateOrdered as HIghestDate 
      ,a.distance 
      ,a.parentOrder  
    FROM allOrders a 
    INNER JOIN (SELECT TOP 2 parentOrder, MAX(DateOrdered)as highestdates FROM allOrders GROUP BY parentOrder ORDER BY MAX(DateOrdered)DESC)b on a.parentOrder=b.parentOrder 
    OUTER APPLY (SELECT TOP 1 parentOrder, DateOrdered FROM allOrders top1 WHERE a.parentOrder=top1.parentOrder ORDER BY top1.DateOrdered DESC)top1 

SQLFiddle

1

Мне сложно понять ваши полные потребности, в том числе ситуацию подкачки. Вы могли бы предоставить ожидаемый результат для образца, который вы поставили, с ним было бы легче проверить.

Во всяком случае, это, кажется, ваша основная трудность заключается в следующем:

Есть ли способ, что я могу «агрегатные» результаты рекурсивных выбирает, чтобы я мог выбрать максимальную дату через весь «родительский» узел?

... это можно легко сделать с помощью рекурсивного CTE и APPLY.

Я не уверен, что вы хотите точно, так что я сделал эти два скрипок:

SQL Fiddle 1 - Здесь все дети вместе на основе корня порядка, то есть порядка 3 с его родителя (порядка 2) родительский (порядок 1).

SQL Fiddle 2 - Здесь дети сгруппированы с их непосредственным родителем, а также родители становятся корнями, поэтому порядок 2 не достигает вершины со своим родителем (заказ 1).

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

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

2

Во-первых, вам нужно сохранить идентификатор заказа «root», чтобы вы могли различать разные «деревья» заказов. После этого вы можете собирать и сортировать свои данные.

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

В следующем запросе используется таблица temp для хранения дерева. Запрос дважды выбирается из дерева, один раз для строк и второй раз для ранжирования. Если бы я использовал CTE для хранения дерева, ему пришлось бы строить его дважды, так как CTE в основном просто многозадачный подзапрос (он будет перестроен для каждого отдельного времени его использования). Использование временной таблицы гарантирует, что мне нужно ее только однажды создать.

Вот SQL:

DECLARE @Offset INT = 0; 
DECLARE @Fetch INT = 2; 

-- Create the Order Trees 
WITH OrderTree AS (
    SELECT po.orderid AS RootOrderID, 
      po.orderid, 
      po.Orders_OrderId, 
      po.DateOrdered, 
      0 AS distance 
    FROM orders po WHERE po.Orders_OrderId IS NULL 
    UNION/**/ALL 
    SELECT parent.RootOrderID, 
      child.orderid, 
      child.Orders_OrderId, 
      child.DateOrdered, 
      parent.distance + 1 AS distance 
    FROM orders child 
    INNER JOIN OrderTree parent 
    ON child.Orders_OrderId = parent.orderid 
) 
SELECT * 
INTO #OrderTree 
FROM OrderTree; 

-- Rank the order trees by MAX(DateOrdered) 
WITH 
Rankings AS (
    SELECT RootOrderID, 
     MAX(DateOrdered) AS MaxDate, 
     ROW_NUMBER() OVER(ORDER BY MAX(DateOrdered) DESC, RootOrderID ASC) AS Rank 
    FROM #OrderTree 
    GROUP BY RootOrderID 
) 
-- Get the next @Fetch trees, starting at rank @Offset+1 
SELECT TREE.*, 
     R.MaxDate, 
     R.Rank 
FROM Rankings R 
INNER JOIN #OrderTree TREE 
    ON R.RootOrderID = TREE.RootOrderID 
WHERE R.Rank BETWEEN @Offset+1 AND (@[email protected]) 
ORDER BY R.Rank ASC, TREE.distance ASC; 

SQLFiddle

Примечание: /**/ между UNION и ALL обходной путь для this issue.

Я построил свою собственную таблицу «заказы», ​​используя данные в существующей таблице, имевшейся в моей базе данных, и сделал некоторую заметную оценку по запросу 3-CTE в вопросе. Это немного превосходит его по большому объему данных (117 деревьев в общей сложности 37215 заказов, максимальная глубина - 11). Я тестировал запуск каждого запроса с включенными STATISTICS IO и STATISTICS TIME, очищая кеш и буферы перед каждым прогоном.

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

╔════════════╦══════════╦════════════╦══════════════╗ 
║ Query  ║ CPU Time ║ Scan Count ║ Logical Reads║ 
╠════════════╬══════════╬════════════╬══════════════╣ 
║ Tree CTE ║ 24211ms ║ 4   ║ 1116243  ║ 
╟────────────╫──────────╫────────────╫──────────────╢ 
║ 3-CTE  ║ 24789ms ║ 7   ║ 1192221  ║ 
║ Temp Table ║ 24384ms ║ 6   ║ 1116549  ║ 
╚════════════╩══════════╩════════════╩══════════════╝ 

Оказывается, что большая часть обоих этих запросов является рекурсивным порядок дерева КТР.Удаление общей стоимости рекурсивного CTE дает следующие результаты:

╔════════════╦══════════╦════════════╦══════════════╗ 
║ Query  ║ CPU Time ║ Scan Count ║ Logical Reads║ 
╠════════════╬══════════╬════════════╬══════════════╣ 
║ 3-CTE  ║ 578ms ║ 3   ║ 75978  ║ 
║ Temp Table ║ 173ms ║ 2   ║ 306   ║ 
╚════════════╩══════════╩════════════╩══════════════╝ 

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

0

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

Пожалуйста найти скрипку >>here (SQLFiddle)

также, код прилагается:

WITH allOrders AS (
    --anchor 
    SELECT po.orderid 
     , po.Orders_OrderId 
     , 0 AS distance 
     , po.DateOrdered 
     , po.orderid AS [rootId] 
    FROM orders po 
    WHERE po.Orders_OrderId IS NULL 

    --recursive 
    UNION ALL 

    SELECT b2.orderid 
     , b2.Orders_OrderId 
     , c.distance + 1 
     , b2.DateOrdered 
     , c.[rootId] 
    FROM orders b2 

    JOIN allOrders c 
     ON b2.Orders_OrderId = c.orderid 
) 
SELECT a.* 
     , b.max_orderdate 
     , RN1 
FROM allOrders a 
LEFT JOIN (SELECT DISTINCT rootid, max(DateOrdered) max_orderdate 
      , row_number() over (order by max(dateordered) desc) as RN1 
      FROM allOrders GROUP BY rootid) b 
ON a.rootid = b.rootid 
where RN1 between 0 and 2 
ORDER BY b.max_orderdate DESC, a.rootid, a.orders_orderid, a.orderid 
+0

Почему бы не использовать 'INNER JOIN'? Кроме того, поскольку вы группируете 'rootid' в свой подзапрос,' DISTINCT' является избыточным. –

+0

@JonSenchyna, вы абсолютно правы, различны, избыточно, и у меня было это с предыдущей итерации кода .. и я использовал левую комбинацию из привычки. Inner Join тоже будет работать! – sebby

Смежные вопросы