Учитывая рекурсивный запрос, который начинается с дочернего элемента, как я могу удалить родственные и родительские строки?

Мне удалось создать рекурсивный запрос, который возвращает строки для выбранного идентификатора и всех его дочерних элементов. Это отлично работает для конечного родителя, но мне нужно, чтобы он также работал правильно, когда переданный идентификатор является идентификатором одного из дочерних элементов, показывая только дочерний элемент и его дочерние элементы, если таковые имеются. В настоящее время он по-прежнему возвращает другие дочерние строки конечного родителя, а переданная дочерняя строка отображается дважды...

Как и в предыдущем случае, я должен сделать это, используя формат подзапроса, потому что могут использоваться другие механизмы базы данных на основе TSQL, кроме SQL Server, которые не поддерживают CTE или предложение WITH.

Желаемый результат:

При использовании идентификатора 2 возвращаются правильные данные: 2, 3, 4, 6, 7. При использовании идентификатора 6 должны возвращаться только 6, 7. В настоящее время запрос возвращает 6,3,4, 6,7.

Данные:

ProjectId   ProjectName                             ParentId
1           Test Project                            -1
2           Test Project 2                          0
3           Test Project 2 Sub Project 1            2
4           Test Project 2 Sub Project 2            2
5           Test Project 3                          -1
6           Test Project 2 Sub Sub Project 1        3
7           Test Project 2 Sub Sub Sub Project 1    6

Запрос:

DECLARE @PROJECTID BIGINT = 2;

SELECT *
FROM            
(
    SELECT *
    FROM ProjectCostingProjects pcp
    WHERE pcp.[ProjectId] = @PROJECTID 
    UNION ALL
    SELECT  pcp2.*
    FROM    ProjectCostingProjects pcp2
    JOIN    ProjectCostingProjects pcp
    ON     pcp2.ParentID = pcp.ProjectId
);

Любые советы или предложения с благодарностью принимаются.


person oldcoder    schedule 29.08.2017    source источник
comment
Не должен ли возвращаться ID 2, 2, 3, 6, 7 и 4?   -  person devzero    schedule 30.08.2017
comment
@devzero: Спасибо за это, вы абсолютно правы. Я не включил все столбцы в вопрос, чтобы сделать его более удобоваримым. В моих тестовых данных идентификатор 4 помечен как неактивный, а в моем полном запросе есть дополнительный код для исключения неактивных записей. Я отредактировал ответ, чтобы отразить ваше исправление.   -  person oldcoder    schedule 30.08.2017


Ответы (2)


Вот ответ, который вместо этого использует цикл while.

Решение 1 с использованием цикла while и временной таблицы

/* Populating the temp table with the data */
DECLARE @recurse TABLE
(projectId INT,parent int);

INSERT INTO @recurse (projectId,parent) VALUES (1,-1);
INSERT INTO @recurse (projectId,parent) VALUES (2,-0);
INSERT INTO @recurse (projectId,parent) VALUES (3,2);
INSERT INTO @recurse (projectId,parent) VALUES (4,2);
INSERT INTO @recurse (projectId,parent) VALUES (5,-1);
INSERT INTO @recurse (projectId,parent) VALUES (6,3);
INSERT INTO @recurse (projectId,parent) VALUES (7,6);

DECLARE @recurse2 TABLE
(projectId INT,parent INT);

--Start by inserting the root element
INSERT INTO @recurse2 ( projectId, parent)
SELECT * FROM @recurse WHERE projectId = 2

--insert elements until all children have all children
WHILE EXISTS (SELECT * FROM @recurse WHERE parent IN (SELECT projectId FROM @recurse2) AND projectId NOT IN (SELECT projectId FROM @recurse2) )
BEGIN
    INSERT INTO @recurse2
    (
        projectId,
        parent
    )
    SELECT * FROM @recurse WHERE parent IN (SELECT projectId FROM @recurse2) AND projectId NOT IN (SELECT projectId FROM @recurse2)
END 

SELECT * FROM @recurse2

Решение 2. Для повышения производительности вы можете создать промежуточную таблицу с результатом цикла while. Эта промежуточная таблица может обновляться через определенные промежутки времени или как часть создания элемента в основной таблице. Это может быть либо частью вашей бизнес-логики, либо триггером БД.

Вот код, который я бы написал, если бы вы хотели это как запланированное задание:

-- Populating the temp table with the data 
DECLARE @recurse TABLE
(projectId INT,parent int);

INSERT INTO @recurse (projectId,parent) VALUES (1,-1);
INSERT INTO @recurse (projectId,parent) VALUES (2,-0);
INSERT INTO @recurse (projectId,parent) VALUES (3,2);
INSERT INTO @recurse (projectId,parent) VALUES (4,2);
INSERT INTO @recurse (projectId,parent) VALUES (5,-1);
INSERT INTO @recurse (projectId,parent) VALUES (6,3);
INSERT INTO @recurse (projectId,parent) VALUES (7,6);

DECLARE @recurse2 TABLE
(projectId INT,parent INT, lvl int);

--Start by inserting all elements root at lvl 0
INSERT INTO @recurse2 ( projectId, parent, lvl ) SELECT projectId, parent, 0 FROM @recurse 
SELECT * FROM @recurse2

--insert elements until we have all parents for all elements
WHILE EXISTS (SELECT * FROM @recurse2 a WHERE lvl IN (SELECT TOP 1 lvl FROM @recurse2 b WHERE a.projectId = b.projectId ORDER BY lvl DESC) AND a.parent > 0 )
BEGIN
    --Insert the next parent for all elements that does not have a their top level parent allready
    INSERT INTO @recurse2 ( projectId, parent , lvl )
    SELECT  projectId, 
            (SELECT b.parent FROM @recurse b WHERE b.projectId = a.parent), 
            lvl + 1 
    FROM @recurse2 a WHERE lvl IN (SELECT TOP 1 lvl FROM @recurse2 b WHERE a.projectId = b.projectId ORDER BY lvl DESC) AND a.parent > 0 
END 

--Find all children to an element
SELECT * FROM @recurse2 WHERE parent = 2

Большим преимуществом решения № 2 является то, что производительность должна быть ДЕЙСТВИТЕЛЬНО хорошей для чтения, возможно, даже лучше, чем у CTE. Также он работает одинаково хорошо как для чтения снизу вверх, так и сверху вниз.

person devzero    schedule 29.08.2017
comment
Это кажется крайне неэффективным. Мой исходный запрос с двумя другими запросами сравнения, встроенными в тот же сценарий, имеет общее время выполнения 0,47 миллисекунды. Я запустил ваш сам по себе (без лишних запросов) более минуты назад, и он до сих пор не вернулся, хотя записей всего 7! Если он не вернется в ближайшее время, мне придется убить процесс, чтобы избежать его... Единственное изменение, которое я сделал, касалось типов столбцов, которые являются БОЛЬШИМИ в моих тестовых данных. Есть идеи, почему? - person oldcoder; 30.08.2017
comment
ХОРОШО. Производительность на SQL Server, низкая производительность на других механизмах баз данных. Я передам запрос разработчикам движка для расследования, которое, надеюсь, со временем приведет к улучшению, но пока мне нужно альтернативное решение... - person oldcoder; 30.08.2017
comment
Это была проблема, по крайней мере, с одним другим механизмом базы данных, и она была решена разработчиками. Поэтому это отличный ответ, и я принял его с благодарностью. - person oldcoder; 01.10.2017

person    schedule
comment
Спасибо за вклад и усилия, которые вы приложили, чтобы предоставить его. К сожалению, в вопросе я очень четко указал, что не могу использовать CTE или WITH, потому что запрос должен быть переносимым на другие механизмы баз данных, которые поддерживают только подмножество TSQL. У информации даже есть свой абзац! В результате ваше сообщение не является ответом на поставленный вопрос... - person oldcoder; 30.08.2017
comment
@oldcoder Пожалуйста, отметьте свой вопрос фактической базой данных, которую вы используете. Если вы используете SQL Server Compact Edition, отметьте свой вопрос sql-server-ce вместо sql-server. - person SqlZim; 30.08.2017
comment
Я использую SQL Server. Однако, поскольку в вопросе четко указано, что один и тот же запрос может потребоваться использовать в других механизмах баз данных, которые используют подмножество TSQL, включая, помимо прочего, VistaDB, SQLite и т. д. Поэтому вопрос помечен абсолютно правильно, и требования четко включены в вопросе, готовый для любого потенциального респондента, чтобы прочитать и понять. Это действительно не могло быть более ясным, не так ли? В требованиях в вопросе конкретно указано НЕ CTE и НЕ WITH... - person oldcoder; 30.08.2017
comment
@oldcoder SQLite не использует подмножество TSQL, он использует собственное подмножество проприетарных функций ANSI SQL + и поддерживает Рекурсивные общие табличные выражения с использованием with. Однако VistaDB не поддерживает рекурсивные запросы, поэтому вам понадобится другое решение, такое как то, что опубликовал devzero. - person SqlZim; 30.08.2017
comment
Спасибо за разъяснение - я не знал, что SQLite поддерживает эти языковые опции, и мне всегда приятно узнавать что-то новое. Я полностью осознавал, что VistaDB не имеет такой поддержки, но моя цель состояла в том, чтобы привести примеры других механизмов, которые потенциально можно было бы использовать и для которых окончательный запрос должен быть подходящим. Таким образом, VistaDB кажется отличным примером такого движка, но SQLite не так хорош. Так что VistaDB явно идеальна для обсуждения. Опять же, я благодарен за ваши усилия, но они не решают проблему. - person oldcoder; 30.08.2017