Запуск Microsoft SQL Azure (окончательная первоначальная версия) — 12.0.2000.8, 19 июня 2024 г., 16:01:48 Copyright (C) 2022 Microsoft Corporation
Я пытаюсь обновить значение в столбце, рассчитанном с использованием значения, которое также было обновлено во время того же процесса обновления, но в более ранней записи.
Думайте об этой таблице как об элементарной таблице безопасности. Начальные столбцы таблицы — это идентификатор RowID
, уникальный PersonID
, их имя, лицо, которому они подчиняются, и поле, в котором перечислены идентификаторы PersonID тех, кто может видеть их данные.
DROP TABLE IF EXISTS ##People1
CREATE TABLE ##People1
(
RowID INT PRIMARY KEY IDENTITY(1, 1) NOT NULL,
PersonID INT NULL,
PersonName VARCHAR(100) NULL,
ReportsTo VARCHAR(100) NULL,
SeeMyData VARCHAR(MAX) NULL
)
GO
INSERT INTO ##People1 (PersonID, PersonName, ReportsTo)
VALUES (7036, 'Liesl', NULL),
(4049, 'Friedrich', 7036),
(197, 'Louisa', 4049),
(2303, 'Kurt', 197),
(3409, 'Brigitta', 2303),
(5686, 'Marta', 4049),
(533, 'Gretl', 5686),
(5204, 'Mike', 533),
(4063, 'Sara', 3409),
(1928, 'Tom', 197),
(7013, 'Jerry', 1928),
(7033, 'Sue', 533)
GO
В таблице выше второй человек, Фридрих, его персонид — 4049, и он подчиняется номеру 7036 Лисл. Лизл никому не отчитывается (вершина цепочки). Человеком, которому кто-то подчиняется, может быть любой человек, перед которым указан идентификатор человека.
Например, PersonID 1928, Том, подчиняется Луизе, personID 197. Человек, которому кто-то подчиняется, всегда будет находиться в более ранней строке.
Обратите внимание, что для поля SeeMyData нет значений. Цель состоит в том, чтобы вычислить и обновить строковое значение SeeMyData для каждого personID на основе этого строкового уравнения в псевдокоде:
SeeMyData = CONCAT(The SeeMyData string of the person they report to,'-', the PeopleID of the person they report to)
Результаты, которые я ищу:
И так далее.
Поэтому для меня задача состоит в том, как вычислить значение SeeMyData, используя ранее вычисленное значение в том же столбце. Другими словами, обновляйте таблицу каждым новым вычисленным значением, прежде чем пытаться вычислить значение SeeMyData для следующей строки.
Я попытался скопировать таблицу и использовать фрагмент кода, который нашел при поиске:
DROP TABLE IF EXISTS ##People2
SELECT *
INTO ##People2
FROM ##People1
GO
UPDATE ##People1
SET ##People1.seemydata = CONCAT(p2.SeeMyData, '-', p1.ReportsTo)
FROM ##People1 p1
JOIN ##People2 p2 ON p1.reportsto = p2.PersonID
Проблема с этим кодом такая же, как и с другими решениями: фактическое значение SeeMyData, похоже, не записывается в столбец после расчета, поэтому следующая строка по-прежнему видит NULL в столбце SeeMyData.
Я также пытался использовать функцию LAG и вычислять разницу между RowID как порядковым номером, но у меня та же проблема: значения обновления для столбца SeeMyData на самом деле отсутствуют, и мы не будем знать, что такое RowID. поскольку это идентификационная строка.
Прошу прощения за проблемы с форматированием. Я думал, что это отформатирует блоки кода, но мне это не помогло.
DROP TABLE IF EXISTS ##People1;
CREATE TABLE ##People1
(
RowID INT PRIMARY KEY IDENTITY(1, 1) NOT NULL,
PersonID INT NULL,
PersonName VARCHAR(100) NULL,
ReportsTo INT NULL, -- Changed to INT to match PersonID type
SeeMyData VARCHAR(MAX) NULL
);
GO
INSERT INTO ##People1
(PersonID, PersonName, ReportsTo)
VALUES
(7036, 'Liesl',NULL),
(4049, 'Friedrich',7036),
(197,'Louisa',4049),
(2303,'Kurt',197),
(3409,'Brigitta',2303),
(5686,'Marta',4049),
(533,'Gretl',5686),
(5204,'Mike',533),
(4063,'Sara',3409),
(1928,'Tom',197),
(7013,'Jerry',1928),
(7033,'Sue',533);
GO
WITH HierarchyCTE AS
(
SELECT
RowID,
PersonID,
PersonName,
ReportsTo,
CAST(NULL AS VARCHAR(MAX)) AS SeeMyData,
CAST(CAST(PersonID AS VARCHAR(MAX)) AS VARCHAR(MAX)) AS Path
FROM
##People1
WHERE
ReportsTo IS NULL
UNION ALL
SELECT
p.RowID,
p.PersonID,
p.PersonName,
p.ReportsTo,
CAST(NULL AS VARCHAR(MAX)) AS SeeMyData,
CAST(h.Path + '-' + CAST(p.PersonID AS VARCHAR(MAX)) AS VARCHAR(MAX)) AS Path
FROM
##People1 p
INNER JOIN
HierarchyCTE h ON p.ReportsTo = h.PersonID
)
UPDATE p
SET SeeMyData = CASE WHEN p.ReportsTo IS NULL THEN NULL ELSE SUBSTRING(h.Path, 1, LEN(h.Path) - LEN(CAST(p.PersonID AS VARCHAR(MAX))) - 1) END
FROM ##People1 p
INNER JOIN HierarchyCTE h ON p.RowID = h.RowID;
SELECT
*
FROM
##People1
ORDER BY
RowID;
GO
Вы можете запустить sql здесь: SqlOnline
ИЛИ
DROP TABLE IF EXISTS ##People1;
CREATE TABLE ##People1
(
RowID INT PRIMARY KEY IDENTITY(1, 1) NOT NULL,
PersonID INT NULL,
PersonName VARCHAR(100) NULL,
ReportsTo INT NULL,
SeeMyData VARCHAR(MAX) NULL
);
GO
INSERT INTO ##People1
(PersonID, PersonName, ReportsTo)
VALUES
(7036, 'Liesl',NULL),
(4049, 'Friedrich',7036),
(197,'Louisa',4049),
(2303,'Kurt',197),
(3409,'Brigitta',2303),
(5686,'Marta',4049),
(533,'Gretl',5686),
(5204,'Mike',533),
(4063,'Sara',3409),
(1928,'Tom',197),
(7013,'Jerry',1928),
(7033,'Sue',533);
GO
DROP TABLE IF EXISTS #Hierarchy;
CREATE TABLE #Hierarchy
(
RowID INT PRIMARY KEY,
PersonID INT,
PersonName VARCHAR(100),
ReportsTo INT,
SeeMyData VARCHAR(MAX),
Path VARCHAR(MAX)
);
INSERT INTO #Hierarchy (RowID, PersonID, PersonName, ReportsTo, SeeMyData, Path)
SELECT
RowID,
PersonID,
PersonName,
ReportsTo,
NULL AS SeeMyData,
CAST(PersonID AS VARCHAR(MAX)) AS Path
FROM
##People1
WHERE
ReportsTo IS NULL;
-- Iteratively insert people who report to those already in the temporary table
DECLARE @RowCount INT = 1;
WHILE @RowCount > 0
BEGIN
INSERT INTO #Hierarchy (RowID, PersonID, PersonName, ReportsTo, SeeMyData, Path)
SELECT
p.RowID,
p.PersonID,
p.PersonName,
p.ReportsTo,
NULL AS SeeMyData,
CAST(h.Path + '-' + CAST(p.PersonID AS VARCHAR(MAX)) AS VARCHAR(MAX)) AS Path
FROM
##People1 p
INNER JOIN
#Hierarchy h ON p.ReportsTo = h.PersonID
WHERE
NOT EXISTS (SELECT 1 FROM #Hierarchy WHERE RowID = p.RowID);
SET @RowCount = @@ROWCOUNT;
END;
-- Update SeeMyData column
UPDATE p
SET SeeMyData = CASE WHEN p.ReportsTo IS NULL THEN NULL ELSE SUBSTRING(h.Path, 1, LEN(h.Path) - LEN(CAST(p.PersonID AS VARCHAR(MAX))) - 1) END
FROM ##People1 p
INNER JOIN #Hierarchy h ON p.RowID = h.RowID;
--output
SELECT
*
FROM
##People1
ORDER BY
RowID;
GO
Это прекрасно работает! Однако мой реальный набор данных составляет более 1000 человек. Когда я добавляю OPTION (MAXRECURSION 0), сценарий работает более 10 минут, после чего я прекращаю выполнение. Даже когда я сократил его до 50 записей и установил для MAXRECURSION значение 30000, я так и не получил ответа. Удалив оператор MAXRECURSION и при 30 записях, я получаю правильные результаты. Есть ли способ сделать это с помощью временных таблиц?
@user149104 user149104 Я добавил еще одно решение, которое использует временную таблицу и требует цикла while для итерации для генерации желаемого результата. Вы можете попробовать решение и, пожалуйста, примите ответ, если оно работает нормально. Спасибо
Новый код, отличный от CTE, выполняется, но для тестовых данных все результирующие пути имеют значение NULL.
Какие данные испытаний?
Неважно, я скопировал его на новый запрос, и это сработало. Спасибо!
У меня есть еще один вопрос, с которым мне нужна помощь. Я добавил к данным еще один столбец под названием DepartmentID. Я пытался получить код, который вы любезно предоставили, чтобы вместо добавления идентификатора ReportsTo к пути добавить DepartmentID. Я думал, что могу просто заменить каждое упоминание идентификатора ReportsTo идентификатором отдела, но, похоже, это не работает. В конечном итоге я хочу добавить оператор случая, в котором указывается, что если код отдела = 1, используйте ReportsToID, а если код отдела = 2, используйте код отдела в пути. Это возможно?
Хранение таких вычисленных значений часто приводит к несогласованности. Вы рассматривали вид вместо этого? Или хотя бы используйте триггеры для управления столбцом.