Задать вопрос
Alexeytur
@Alexeytur

Возможно ли в UPDATE «видеть» результат обновления предыдущих строк?

Добрый день.

Имеется такая таблица:

CREATE TABLE PDIndicatorsHistory 
(
	RecId int PRIMARY KEY IDENTITY(1,1),
	ItemId int,
	ReportDate datetime,
	PD int
)


Такая функция:

FUNCTION GetPD 
(
    @ReportDate datetime,
    @ItemId int
)
RETURNS int
AS
-- сложная логика пропущена, интересует этот запрос

    SELECT  TOP 1 
	   @Rating = rih.Rating * 2
            FROM PDIndicatorsHistory AS rih 
            WHERE
		rih.ItemId= @itemId
                AND rih.ReportDate < @ReportDate
            ORDER BY rih.ReportDate DESC

    RETURN @Rating
END


Заполняю тестовыми данными:
INSERT INTO PDIndicatorsHistory (ItemId,ReportDate,PD) VALUES
(1, '2025-07-06', 2), (1, '2025-07-07', NULL), (1, '2025-07-08', NULL)


Хочу обновить все три строки одним UPDATE:

UPDATE PDIndicatorsHistory
		SET
			PD = GetPD (ReportDate, ItemId)
		WHERE
			ItemId= @ItemId
			AND ReportDate >= '2025-07-06'


Нужно одним оператором обновить все три строки, но т.к. GetPD сама использует внутри PDIndicatorsHistory,
нужно чтобы в операторе UPDATE функция GetPD вызывалась в порядке ReportDate ASC и могла "видеть" результат обновления предыдущей строки.

Нужно чтобы получился такой результат:

ItemId ReportDate PD
1 '2025-07-06' 2
1 '2025-07-07' 4
1 '2025-07-08' 8

Возможно ли такое сделать?
  • Вопрос задан
  • 116 просмотров
Подписаться 2 Средний 10 комментариев
Пригласить эксперта
Ответы на вопрос 1
@Akina
Сетевой и системный админ, SQL-программист.
Пример возможной реализации. Демонстрация логики.

1. Создание тестовой таблицы

CREATE TABLE PDIndicatorsHistory 
(
	RecId int PRIMARY KEY IDENTITY(1,1),
	ItemId int,
	ReportDate datetime,
	PD int
);
  
INSERT INTO PDIndicatorsHistory (ItemId,ReportDate,PD) VALUES
(1, '2025-07-06', 2), (1, '2025-07-07', NULL), (1, '2025-07-08', NULL);

SELECT * FROM PDIndicatorsHistory;


2. Создание функции

CREATE FUNCTION dbo.GetPD 
(
    @prev_RecId INT,  -- идентификатор предыдущей записи, её исходное состояние
    @next_RecId int,  -- идентификатор текущей записи, её исходное состояние
    @prev_PD INT      -- значения отдельных полей обновлённой предыдущей записи
)
RETURNS int
AS

BEGIN
-- требуемая сложная логика, опирающаяся на идентификаторы текущей и предыдущей записей
-- что позволяет получить значения полей записей ДО запуска SELECT или там UPDATE,
-- а также на значения полей модифицированного состояния предыдущей записи, переданных по отдельности
    RETURN @prev_PD * 2;
END;


3. Обновление

WITH
-- первый CTE, просто нумерует записи по возрастанию даты для каждого ItemId отдельно
cte1 AS (
    SELECT *, ROW_NUMBER() OVER (PARTITION BY ItemId ORDER BY ReportDate ASC) rn
    FROM PDIndicatorsHistory
    ),
-- рекурсивно обрабатываем записи в порядке возрастания номера
cte2 AS (
    SELECT RecId, ItemId, ReportDate, PD, rn
    FROM cte1
    WHERE rn = 1 -- начинаем с первой
    UNION ALL
    SELECT cte1.RecId,
           cte1.ItemId,
           cte1.ReportDate,
           dbo.GetPD(cte2.RecId, cte1.RecId, cte2.PD),
           cte1.rn
    FROM cte1
    JOIN cte2 ON cte1.ItemId = cte2.ItemId
             AND cte1.rn = cte2.rn + 1 -- а потом вторая, третья... пока не кончатся
    )
-- посчитав значения, которые нужно использовать при обновлении, выполняем само обновление
UPDATE PDIndicatorsHistory
SET PD = cte2.PD
FROM cte2
WHERE PDIndicatorsHistory.RecID = cte2.RecId;


fiddle

PS. Почему так сложно? Такова логика UPDATE (транзакции, роллбэки - по-другому никак). Сначала подготовить новое состояние для ВСЕХ модифицируемых записей, а потом выполнить их фактическое обновление. И соответственно SELECT к таблице внутри функции обращается к исходному состоянию таблицы, ему пофиг, что запись, к которой ты обращаешься, ПОДГОТОВЛЕНА к модификации. Она ещё не модифицирована.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы