SQL Server 2005: обновлять строки в указанном порядке (например, ORDER BY)?

Я хочу обновить строки таблицы в определенном порядке, как и следовало ожидать, если включить предложение ORDER BY, но SQL Server не поддерживает предложение ORDER BY в запросах UPDATE.

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

UPDATE TableA AS Parent
SET Parent.ColA = Parent.ColA + (SELECT TOP 1 Child.ColA
    FROM TableA AS Child
    WHERE Child.ParentColB = Parent.ColB
    ORDER BY Child.Priority)
ORDER BY Parent.Depth DESC;

Итак, я надеюсь, вы заметите, что одна таблица (TableA) содержит иерархию строк, в которой одна строка может быть родительской или дочерней для любой другой строки. Строки должны быть обновлены в порядке от самого глубокого дочернего элемента до корневого родителя. Это связано с тем, что TableA.ColA должен содержать актуальную конкатенацию собственного текущего значения со значениями его дочерних элементов (я понимаю, что этот запрос объединяет только один дочерний элемент, но это ради простоты — цель примера в этот вопрос не требует дополнительной детализации), поэтому запрос должен обновляться снизу вверх.

Решение, предложенное в вопросе, который я отметил выше, выглядит следующим образом:

UPDATE messages
SET status=10
WHERE ID in (SELECT TOP (10) Id
    FROM Table
    WHERE status=0
    ORDER BY priority DESC
);

Причина, по которой я не думаю, что смогу использовать это решение, заключается в том, что я ссылаюсь на значения столбцов из родительской таблицы внутри своего подзапроса (см. WHERE Child.ParentColB = Parent.ColB), и я не думаю, что два родственных подзапроса будут иметь доступ к данным друг друга.

Пока что я нашел только один способ объединить это предложенное решение с моей текущей проблемой, и я думаю, что он не работает.

UPDATE TableA AS Parent
SET Parent.ColA = Parent.ColA + (SELECT TOP 1 Child.ColA
    FROM TableA AS Child
    WHERE Child.ParentColB = Parent.ColB
    ORDER BY Child.Priority)
WHERE Parent.Id IN (SELECT Id
    FROM TableA
    ORDER BY Parent.Depth DESC);

Подзапрос WHERE..IN на самом деле не вернет подмножество строк, он просто вернет полный список идентификаторов в нужном мне порядке. Однако (я не знаю точно - скажите, пожалуйста, если я ошибаюсь) я думаю, что предложение WHERE..IN не будет заботиться о порядке идентификаторов в круглых скобках - оно просто проверит идентификатор строки, которую он в данный момент хочет обновить, чтобы увидеть, находится ли он в этом списке (а они все есть) в любом порядке, в котором он уже пытается обновить... Это было бы просто пустой тратой циклов, потому что это ничего не изменит.

Итак, в заключение, я осмотрелся и не могу найти способ обновления в указанном порядке (и включил причину обновления в этом порядке, потому что я уверен, что в противном случае получил бы очень полезные ответы «почему?»), и теперь я нажимаю на Stack Overflow, чтобы узнать, знает ли кто-нибудь из вас, гуру, которые знают о SQL больше, чем я (что не говорит многого) о эффективный способ сделать это. Особенно важно, что я использую только один запрос для выполнения этого действия.

Длинный вопрос, но я хотел покрыть свои основы и дать вам, ребята, как можно больше информации, чтобы питаться ею. :)

Есть предположения?


person JMTyler    schedule 09.06.2010    source источник


Ответы (4)


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

Вам нужно либо использовать цикл/курсор while (uhhgg), либо, возможно, использовать представление выражения CTE для достижения того, что вы пытаетесь, что дает вам возможность рекурсии.

Посмотри на

person Adriaan Stander    schedule 09.06.2010
comment
Да, курсоры — это именно то, чего я стремлюсь избежать. :) Спасибо, посмотрю выражения CTE. - person JMTyler; 09.06.2010

Вы не можете добиться этого в одном запросе, потому что ваши обновления коррелированы (т. е. уровень N зависит от обновленного значения уровня N+1). Реляционные движки явно осуждают это из-за Хэллоуинской проблемы. План запроса сделает все возможное, чтобы гарантировать, что обновления происходят так, как если бы они имели два этапа: один, на котором считывалось текущее состояние, а затем один, на котором применялось обновленное состояние. При необходимости они будут буферизовать промежуточные таблицы только для того, чтобы сохранить этот очевидный порядок выполнения (прочитать все -> записать все). Поскольку ваш запрос, если я правильно понимаю, пытается сломать эту самую предпосылку, я не вижу никакого способа, которым вы добьетесь успеха.

person Remus Rusanu    schedule 09.06.2010

Вот однострочное решение SQL. Если вы когда-нибудь ослабите требование, чтобы это был один оператор обновления, вы можете исключить некоторую сложность

CREATE TABLE [TableA](
    [ID] [int] NOT NULL,
    [ParentID] [int] NULL,
    [ColA] [varchar](max) NOT NULL,
    [Priority] [varchar](50) NOT NULL,
    [Depth] [int] NOT NULL)
go

INSERT TableA
SELECT 1, NULL, 'p', 'Favorite', 0 UNION ALL
SELECT 2, 1, 'm', 'Favorite', 1 UNION ALL
SELECT 3, 1, 'o', 'Likeable', 1 UNION ALL
SELECT 4, 2, 'v', 'Favorite', 2 UNION ALL
SELECT 5, 2, 'v', 'Likeable', 2 UNION ALL
SELECT 6, 2, 'd', 'Likeable', 2 UNION ALL
SELECT 7, 6, 'c', 'Red-headed Stepchild', 3 UNION ALL
SELECT 8, 6, 's', 'Likeable', 3 UNION ALL
SELECT 9, 8, 'n', 'Favorite', 4 UNION ALL
SELECT 10, 6, 'c', 'Favorite', 3 UNION ALL
SELECT 11, 5, 'c', 'Favorite', 3 UNION ALL
SELECT 12, NULL, 'z', 'Favorite', 0 UNION ALL
SELECT 13, 3, 'e', 'Favorite', 2 UNION ALL
SELECT 14, 8, 'k', 'Likeable', 4 UNION ALL
SELECT 15,4, 'd', 'Favorite', 3

;WITH cte AS (
SELECT a.i, a.Depth, a.maxd, a.mind, a.maxc, a.di, a.ci, a.cdi, a.ID, a.y, CAST('' AS varchar(max))z
FROM(
    SELECT DISTINCT i = 1
    ,p.Depth
    ,maxd = (SELECT MAX(Depth) FROM TableA)
    ,mind = (SELECT MIN(Depth) FROM TableA)
    ,maxc = (SELECT MAX(c) FROM (SELECT COUNT(*) OVER(PARTITION BY ParentID) FROM TableA)f(c))
    ,di   = (SELECT MIN(Depth) FROM TableA)
    ,ci   = 1
    ,cdi  = (SELECT MIN(Depth) FROM TableA)
    ,p.ID
    ,CAST(p.ID AS varchar(max)) + p.ColA + SPACE(1) + CASE WHEN g IS NULL THEN '' ELSE '(' END 
                                     + ISNULL(g,'') + CASE WHEN g IS NULL THEN '' ELSE ')' END y
    FROM TableA p
    LEFT JOIN TableA c ON (c.ParentID = p.ID)
    CROSS APPLY (SELECT SPACE(1) + CAST(c2.ID AS varchar(max)) + ColA + SPACE(1) 
                 FROM TableA c2 WHERE ParentID = p.ID 
                 ORDER BY Priority 
                 FOR XML PATH(''))f(g)
    )a
UNION ALL
SELECT r.i, r.Depth, r.maxd, r.mind, r.maxc, r.di, r.ci, r.cdi, r.ID
,CASE WHEN di = cdi 
      THEN REPLACE(r.y,LEFT(r.z,CHARINDEX(SPACE(1),r.z,2)), r.z)
      ELSE r.y END [y]
,r.z
FROM(
    SELECT i = i + 1
    ,Depth
    ,[maxd]
    ,[mind]
    ,[maxc]
    ,CASE WHEN ci = maxc AND cdi = maxd
          THEN di + 1
          ELSE di
          END [di]
    ,CASE WHEN cdi = [maxd]
          THEN CASE WHEN ci + 1 > maxc
                    THEN 1
                    ELSE ci + 1
                    END
          ELSE ci
          END [ci]
    ,CASE WHEN cdi + 1 > maxd
          THEN mind
          ELSE cdi + 1
          END [cdi]
    ,id,y
    ,CAST(ISNULL((SELECT y FROM(
        SELECT p.Depth,p.ID
        ,SPACE(1) + CAST(p.ID AS varchar(max)) + p.ColA + SPACE(1) + 
        CASE WHEN g IS NULL THEN '' ELSE '(' END + ISNULL(g,'') 
      + CASE WHEN g IS NULL THEN '' ELSE ')' END y
        ,r1 = DENSE_RANK() OVER(ORDER BY p.ID) --child number
        ,r2 = ROW_NUMBER() OVER(PARTITION BY p.ID ORDER BY p.ID) --DISTINCT not allowed in recursive section
        FROM TableA p
        JOIN TableA c ON (c.ParentID = p.ID)
        CROSS APPLY (SELECT SPACE(1)+CAST(c2.ID AS varchar(max))+ColA+SPACE(1) 
                     FROM TableA c2 
                     WHERE ParentID = p.ID 
                     ORDER BY Priority 
                     FOR XML PATH(''))f(g)
        WHERE p.Depth = cdi AND cdi < di AND p.ID <> cte.ID
        )v
    WHERE r1 = ci 
    AND r2 = 1
    AND cte.y LIKE '%' + LEFT(v.y,CHARINDEX(SPACE(1),v.y,2) ) + '%'),'') AS varchar(max)) z
FROM cte
WHERE [di]<[maxd] or [ci]<[maxc] or [cdi]<[maxd]
)r
)--cte
UPDATE t
SET ColA = REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE
        (y,SPACE(1),''),'1',''),'2',''),'3',''),'4',''),'5',''),'6',''),'7',''),'8',''),'9',''),'0','')
FROM cte
JOIN TableA t ON (t.ID = cte.ID)
WHERE di = (SELECT MAX(Depth) FROM TableA)
AND cdi  = (SELECT MAX(Depth) FROM TableA)
AND ci   = (SELECT MAX(c) FROM (SELECT COUNT(*) OVER(PARTITION BY ParentID) FROM TableA)f(c)) 
OPTION(maxrecursion 0)

SELECT * FROM TableA
DROP TABLE TableA
person Scot Hauder    schedule 16.06.2010

JMTyler-

1 Какие данные находятся в ColA? На что это похоже?

2 Как этот столбец должен быть первоначально заполнен? Я спрашиваю об этом, потому что вы сможете запустить обновление только один раз, поскольку значение в этом столбце будет изменено по сравнению с предыдущим запуском. Любые дополнительные прогоны просто объединят больше данных. Это наводит меня на мысль, что существует другой ColC с исходным значением ColA (имя человека?)

3 Будет ли строка когда-либо удалена, потеряв своих дочерних элементов? Если да, то на что должен указывать их ParentColB? НУЛЕВОЙ? Устанавливается ли их глубина на 0, чтобы они теперь находились на вершине иерархии?

Если вы можете ответить на это, я могу дать вам решение

Спасибо

person Scot Hauder    schedule 09.06.2010
comment
Привет, Скот, спасибо за интерес. Данные начинаются с одного символа, представляющего тип объекта, а затем ColA каждой строки завершает их (т. е. предполагается, что obj с ColA 'p' имеет трех дочерних элементов с ColA 'c': ColA='p(ccc)') пока у корневого объекта не будет шаблона, представляющего иерархию. Это происходит только один раз для пакета объектов. Если какой-либо объект удаляется, все его дочерние элементы также удаляются. - person JMTyler; 10.06.2010
comment
@JMTyler Если у первого ребенка есть ребенок (внук p), как должен выглядеть вывод? 'р(с(г)сс)' ? или это не имеет значения, и все потомки могут быть сгруппированы вместе, как «p (cccg)» - person Scot Hauder; 11.06.2010
comment
Первое верно - если у первого потомка есть потомок, результатом будет p(c(g)cc). Это позволяет нам с первого взгляда узнать, какая иерархия находится под любым данным объектом, и упорядоченную структуру этой иерархии. - person JMTyler; 11.06.2010
comment
@JMTyler Все ли буквы объекта в ColA уникальны? или могут ли несколько идентификаторов иметь ColA типа «a»? - person Scot Hauder; 14.06.2010
comment
Буквы объектов в ColA не уникальны. Они определяют тип объекта, который представляет строка, и пространство типов очень ограничено. Формат может оказаться таким же, как в наших примерах: 'p(cc(g)c)' - person JMTyler; 14.06.2010
comment
Это также может быть что-то вроде: 'p(ccc(cc(g)c)cc)' (которым я пытаюсь показать, что 'c' все еще может быть родителем/потомком другого 'c') - person JMTyler; 15.06.2010