Ms sql рекурсивный запрос: Изучение SQL: рекурсивные запросы

sql server — рекурсивный запрос mssql


Вопрос задан


Изменён
5 лет 3 месяца назад


Просмотрен
619 раз

Помогите с рекурсивным запросом к таблице, чтобы значение в столбце flag родителей был изменен 0 на 1 в случае если у всех его детей стоит 1

id  name    ID_PP   parentID    flag
1   Все NULL    NULL    0
2   Компания    NULL    1   0
3   Производство 1  NULL    2   0
4   Производство 2  NULL    2   0
5   Производство 3  NULL    2   0
6   Производство 4  NULL    2   0
7   Производство 5  NULL    2   0
8   Производство 6  NULL    2   0
9   Цех 1   NULL    3   0
10  Цех 2   NULL    4   0
11  Цех 3   NULL    4   0
12  Цех 4   NULL    4   0
13  Цех 5   NULL    5   0
14  Цех 6   NULL    5   0
15  Цех 7   NULL    6   0
16  Цех 8   NULL    6   0
17  Цех  9  NULL    6   0
18  Цех  10 NULL    7   0
19  Цех 11  NULL    7   0
20  Цех 12  NULL    8   1
21  Цех 13  8   1
23  Цех 14  NULL    8   1
24  Отделение 0101  1898    9   1
25  Отделение 0102  2205    9   0
26  Отделение 0103  2064    9   1
27  Отделение 0104  2121    9   0
28  Отделение 0105  2185    9   0
29  УСК             1983            9   0
30  Отделение 0106 (И-7)    5347    9   1
declare @in_bdt datetime = cast('13. 02.2018 00:00:00' as datetime)   
declare @in_edt datetime = cast('13.02.2018 23:59:00' as datetime) 
 declare @razn varchar(10) = DATEDIFF (HOUR,@in_bdt,@in_edt)+1
;with rec (  [id]
      ,[ID_PP]
      ,[name]
      ,[parentID]
      ,flag)
  as ( 
  select [id]
      ,t2.[ID_PP]
      ,[name]
      ,[parentID]
      ,flag
   from [dbo].[tab1] 
    left  join(
           SELECT IIF((COUNT([ID_PP])-@razn)=0,1,0) flag
           ,[ID_PP]
                 FROM [dbo].[tab2]
                 where dt between @in_bdt and @in_edt
                 group by  [ID_PP]
           ) as t2 on [tab1].ID_PP = t2.ID_PP
   union all
   select [tab1].[id]
      ,[tab1].[ID_PP]
      ,[tab1].[name]
      ,[tab1].[parentID]  
      ,flag
            from [tab1] 
      join rec on [tab1].id =rec.parentID
   )
select distinct rec.[id]
      ,[ID_PP]
      ,[name]
      ,[parentID]
      ,flag
       from rec
       order by rec.[id]
1   NULL    1
2   1   1
3   2   1
4   2   1
5   2   1
6   2   1
7   2   1
8   2   1
83  2   1
9   3   1
10  4   NULL
11  4   1
12  4   1
13  5   1
14  5   1
15  6   1
16  6   1
17  6   1
18  7   1
19  7   1
20  8   1
21  8   1
23  8   1
24  9   1
25  9   NULL
  • sql-server
  • запрос
  • рекурсия






8

with RTable as(
     select [id],t2. [ID_PP],[name],[parentID], coalesce(flag,0) flag
       from [dbo].[tab1] 
       left  join(
           SELECT IIF((COUNT([ID_PP])-@razn)=0,1,0) flag,[ID_PP]
             FROM [dbo].[tab2]
             where dt between @in_bdt and @in_edt
             group by  [ID_PP]
       ) as t2 on [tab1].ID_PP = t2.ID_PP
),
R1 as(
    select id, ParentId, flag
      from RTable R
     where not exists(select 1 from RTable N where N.parentId=R.Id)
    union all
    select T.id, T.ParentId,
           case when T.flag=1 then 1 else R.flag end
      from R1 R, RTable T
     where T.id=R.ParentId
)
select id, parentId, min(flag) flag
  from R1
group by id, parentId

Спускаемся от листьев к корню дерева (лист это такая запись у которой нет наследников). При этом берем флаг из текущей записи, если он 1 или, если он 0, то берем флаг пришедший по рекурсии от наследника. Если по всем возможным путям (от всех листьев) до данного узла дошли только 1, то и итогом данного узла будет 1. На выходе из рекурсии у нас как раз для каждого id есть записи всех его прямых наследников с теми флагами, которые были получены спуском от листьев. Остается только сгруппировать до id и получить минимум (если был хотя бы один 0 — то итог 0)

Пример на sqlfiddle.com (только рекурсии, без начальной подготовки таблицы из вашего примера)






7







Зарегистрируйтесь или войдите

Регистрация через Google

Регистрация через Facebook

Регистрация через почту

Отправить без регистрации

Почта

Необходима, но никому не показывается

Отправить без регистрации


Почта

Необходима, но никому не показывается




Нажимая на кнопку «Отправить ответ», вы соглашаетесь с нашими пользовательским соглашением, политикой конфиденциальности и политикой о куки


sql — Как написать рекурсивный запрос?

Кратко опишу алгоритм.

Если я правильно понял, задача состоит из трёх частей:

  1. От переданного ИДа идём вверх по дереву, пока не найдём департамент.

  2. от департамента находим всех его детей, выбираем из них только с типом employee

  3. выбираем тех, кто есть в таблице cache_extensions

пункты 1. и 2. первая и вторая рекурсивная часть рекурсивного СТЕ:

—таблички

USE tempdb;
IF OBJECT_ID('dept_employees')IS NOT NULL DROP TABLE dept_employees
IF OBJECT_ID('employees')IS NOT NULL DROP TABLE employees
IF OBJECT_ID('ogpo_dept')IS NOT NULL DROP TABLE ogpo_dept
IF OBJECT_ID('cash_extensions')IS NOT NULL DROP TABLE cash_extensions
CREATE TABLE dept_employees(
  ID INT NOT NULL,
  parent_id INT NULL,
  obj_id INT NOT NULL,
  obj_type VARCHAR(4) NOT NULL
)
CREATE TABLE employees(
  ID INT NOT NULL,
  FIO VARCHAR(255) NOT NULL,
  dept_id INT NOT NULL
)
/*CREATE TABLE ogpo_dept(
  ID INT NOT NULL,
  Name VARCHAR(255) NOT NULL
)*/
CREATE TABLE cash_extensions(
  ID INT NOT NULL,
  FIO VARCHAR(255) NOT NULL
)

—данные

INSERT dept_employees VALUES
--depts
(10, NULL, 1, 'dept'),
(20, 10,    2, 'dept'),
(30, NULL, 3, 'dept'),
(40, 30,    4, 'dept'),
--emps
(50, 20, 1, 'emp'),
(60, 50, 2, 'emp'),
(70, 50, 3, 'emp'),
(80, 40, 4, 'emp'),
(90, 80, 5, 'emp'),
(100,30, 6, 'emp')
INSERT employees VALUES
(1, 'd1->d2->p1', 2),
(2, 'd1->d2->p1->p2', 2),
(3, 'd1->d2->p1->p3', 2),
(4, 'd3->d4->p4', 4),
(5, 'd3->d4->p4->p5', 4),
(6, 'd3->p6', 3)
INSERT cash_extensions VALUES
(1, 'd1->d2->p1'),
(2, 'd1->d2->p1->p2'),
(3, 'd1->d2->p1->p3'),
(4, 'd3->d4->p4'),
(5, 'd3->d4->p4->p5'),
(6, 'd3->p6')

—процедура

IF OBJECT_ID('FindAllCasheExtemsionsFromDeptByEmployee') IS NOT NULL DROP PROC FindAllCasheExtemsionsFromDeptByEmployee
GO
CREATE PROC FindAllCasheExtemsionsFromDeptByEmployee 
  @EmployeeId INT
AS
WITH CTE AS(
  SELECT 
    CASE obj_type 
      WHEN 'emp' THEN 'find_depart' 
      WHEN 'dept' THEN 'find_childs'
    END what, id AS child_Id, parent_id AS parend_id, obj_id, E. obj_type
  FROM dept_employees E
  WHERE id = @EmployeeId
  UNION ALL
  --идём вверх по дереву до первого департамента
  SELECT 
    CASE E.obj_type 
      WHEN 'emp' THEN 'find_depart' 
      WHEN 'dept' THEN 'find_childs'
    END what, id AS child_Id, parent_id AS parend_id, E.obj_id, E.obj_type
  FROM CTE
    JOIN dept_employees E ON CTE.parend_id = E.ID
  WHERE what = 'find_depart'
  UNION ALL
  --теперь идём вниз по дереву, находим всех потомков
  SELECT 'find_childs' what, id AS child_Id, parent_id AS parend_id, E.obj_id, E.obj_type
  FROM CTE
    JOIN dept_employees E ON CTE.child_id = E.parent_id
  WHERE what = 'find_childs'
)
SELECT E.*
FROM CTE
  JOIN employees E ON CTE.obj_id = E.ID
  JOIN cash_extensions C ON E.FIO = C.FIO
WHERE CTE.obj_type = 'emp' AND CTE.what = 'find_childs'
OPTION (MAXRECURSION 0) 
GO

—тесты и результаты

EXEC FindAllCasheExtemsionsFromDeptByEmployee 10 -- d1
/*
ID     FIO    dept_id
1      d1->d2->p1   2
2      d1->d2->p1->p2      2
3      d1->d2->p1->p3      2
*/
EXEC FindAllCasheExtemsionsFromDeptByEmployee 50 -- p1
/*
ID     FIO    dept_id
1      d1->d2->p1   2
2      d1->d2->p1->p2      2
3      d1->d2->p1->p3      2
*/
EXEC FindAllCasheExtemsionsFromDeptByEmployee 70 -- p3
/*
ID     FIO    dept_id
1      d1->d2->p1   2
2      d1->d2->p1->p2      2
3      d1->d2->p1->p3      2
*/
EXEC FindAllCasheExtemsionsFromDeptByEmployee 40 -- d4
/*
ID     FIO    dept_id
4      d3->d4->p4   4
5      d3->d4->p4->p5      4
*/
EXEC FindAllCasheExtemsionsFromDeptByEmployee 90 -- p5
/*
ID     FIO    dept_id
4      d3->d4->p4   4
5      d3->d4->p4->p5      4
*/
EXEC FindAllCasheExtemsionsFromDeptByEmployee 100 -- p6
/*
ID     FIO    dept_id
4      d3->d4->p4   4
5      d3->d4->p4->p5      4
6      d3->p6 3
*/

пс: задача очень плохо описана.

общее табличное выражение — SQL Server CTE и пример рекурсии

Я не тестировал ваш код, просто попытался помочь вам понять, как он работает, в комментарии;

 С
  cteReports (EmpID, Имя, Фамилия, MgrID, EmpLevel)
  КАК
  (
-->>>>>>>>>>Блок 1>>>>>>>>>>>>>>>>>>>>
-- В rCTE этот блок называется [Anchor].
-- Запрос находит все корневые узлы, как описано в WHERE ManagerID IS NULL.
    ВЫБЕРИТЕ EmployeeID, Имя, Фамилия, ManagerID, 1
    ОТ сотрудников
    ГДЕ ManagerID имеет значение NULL
-->>>>>>>>>>Блок 1>>>>>>>>>>>>>>>>>>>>
    СОЮЗ ВСЕХ
-->>>>>>>>>>Блок 2>>>>>>>>>>>>>>>>>>>
-- Это рекурсивное выражение rCTE
-- При первом "выполнении" он будет запрашивать данные в [Employees],
-- относительно [Anchor] выше.
-- Это создаст набор результатов, мы назовем его R{1}, и он будет ПРИСОЕДИНЕН к [Сотрудникам].
-- как определено иерархией
-- Последующие "выполнения" этого блока будут ссылаться на R{n-1}
    ВЫБЕРИТЕ e.EmployeeID, e.FirstName, e.LastName, e.ManagerID,
      r. EmpLevel + 1
    ОТ сотрудников e
      ВНУТРЕННЕЕ СОЕДИНЕНИЕ cteReports r
        ON e.ManagerID = r.EmpID
-->>>>>>>>>>Блок 2>>>>>>>>>>>>>>>>>>>
  )
ВЫБИРАТЬ
  Имя + ' ' + Фамилия КАК ФИО,
  ЭмпУровень,
  (ВЫБЕРИТЕ Имя + '' + Фамилия ОТ Сотрудников
    ГДЕ EmployeeID = cteReports.MgrID) AS Manager
ИЗ cteReports
ЗАКАЗАТЬ ПО EmpLevel, MgrID
 

Самый простой пример рекурсивного CTE , который я могу придумать, чтобы проиллюстрировать его работу;

 ;С номерами КАК
(
    ВЫБЕРИТЕ n = 1
    СОЮЗ ВСЕХ
    ВЫБЕРИТЕ п + 1
    ОТ Числа
    ГДЕ n+1 <= 10
)
ВЫБЕРИТЕ n
ОТ Числа
 

В 1) как увеличивается значение N. если значение присваивается N каждый раз, то значение N может увеличиваться, но значение N инициализируется только в первый раз .

A1: В этом случае N не является переменной. N — псевдоним. Это эквивалент SELECT 1 AS N . Это синтаксис личных предпочтений. Существует 2 основных метода псевдонимов столбцов в CTE в T-SQL . Я включил аналог простого CTE в Excel , чтобы попытаться более привычно проиллюстрировать происходящее.

 -- Снаружи
;С CTE (MyColName) КАК
(
    ВЫБЕРИТЕ 1
)
-- Внутри
;С ОТЭ КАК
(
    ВЫБЕРИТЕ 1 КАК MyColName
    -- Или
    ВЫБЕРИТЕ MyColName = 1
    -- И т. д...
)
 

Q 2) теперь здесь о CTE и рекурсии отношений сотрудников
в тот момент, когда я добавляю двух менеджеров и добавляю еще несколько сотрудников под вторым менеджером, тогда начинаются проблемы.
я хочу отобразить информацию о первом менеджере, а в следующих строках будут отображаться только данные о сотрудниках, которые являются подчиненными этого менеджера

A2:

Этот код отвечает на ваш вопрос?

 -----------------------------------------------------------
-- Синтезировать таблицу с нерекурсивным CTE
--------------------------------------------
;С сотрудником (ID, имя, MgrID) AS
(
    ВЫБЕРИТЕ 1, 'Кит', NULL UNION ALL
    ВЫБЕРИТЕ 2, 'Джош', 1 ОБЪЕДИНИТЕ ВСЕ
    ВЫБЕРИТЕ 3, 'Робин', 1 ОБЪЕДИНЕНИЕ ВСЕХ
    ВЫБЕРИТЕ 4, 'Раджа', 2 ОБЪЕДИНИТЕ ВСЕ
    ВЫБЕРИТЕ 5, 'Тридип', NULL UNION ALL
    ВЫБЕРИТЕ 6, 'Ариджит', 5 ОБЪЕДИНИТЕ ВСЕ
    ВЫБЕРИТЕ 7, 'Амит', 5 ОБЪЕДИНИТЕ ВСЕ
    ВЫБЕРИТЕ 8, 'Разработчик', 6
)
--------------------------------------------
-- Рекурсивный CTE - привязан к указанному выше CTE
--------------------------------------------
,Иерархия КАК
(
    --  Якорь
    ВЫБЕРИТЕ ИДЕНТИФИКАТОР
            ,Имя
            ,ID администратора
            ,nУровень = 1
            ,Family = ROW_NUMBER() OVER (ЗАКАЗАТЬ ПО ИМЕНИ)
    ОТ Сотрудника
    ГДЕ MgrID имеет значение NULL
    СОЮЗ ВСЕХ
    -- Рекурсивный запрос
    ВЫБЕРИТЕ Э. ID
            ,E.Имя
            ,Э.МгрИД
            ,H.nLevel+1
            ,Семья
    ОТ Сотрудника E
    ПРИСОЕДИНЯЙТЕСЬ к иерархии H ON E.MgrID = H.ID
)
ВЫБИРАТЬ *
ИЗ Иерархии
ЗАКАЗАТЬ ПО Семейству, nLevel
 
 ВЫБЕРИТЕ ID,пробел(nLevel+
                    (СЛУЧАЙ, КОГДА nLevel > 1, THEN nLevel ELSE 0 END)
                )+Имя
ИЗ Иерархии
ЗАКАЗАТЬ ПО Семейству, nLevel
 

sql - Рекурсивный запрос с отношением родитель-потомок

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

спросил

Изменено
1 год, 10 месяцев назад

Просмотрено
4к раз

Я пытаюсь сделать рекурсивный запрос в SQL Server, отображающий данные иерархически. Вот структура таблицы

 [id] [int] IDENTITY(1,1) NOT NULL,
    [имя] [varchar(100)] НЕ NULL,
    [Parent_Id] [число] NULL,
 

У каждого продукта есть родитель. Столбец Parent_Id содержит идентификатор родителя. parent_id имеет значение null для корневых продуктов.

Я хочу сделать sql-запрос, отображающий продукты в иерархическом порядке. Следующее изображение является примером того, как продукты могут быть организованы.

Продукты могут иметь дочерние элементы.

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

 id name parent_id
1 P1 НОЛЬ
2 P2 НОЛЬ
3 П2-1 2
4 П2-2 2
5 П2-3 2
6 П2-3-1 5
7 П2-3-2 5
8 Р3 НОЛЬ
9 П3-1 8
 

Вот запрос, который я написал для его достижения:

 с деревом как (выберите * из продуктов
               союз всех
               выберите * из дерева, где parent_id = tree.id
             )
выбрать * из дерева;
 

Но я получаю результат, подобный следующему:

 1 P1 NULL
2 P2 НОЛЬ
8 Р3 НОЛЬ
3 П2-1 2
4 П2-2 2
5 П2-3 2
9 П3-1 8
6 П2-3-1 5
7 П2-3-2 5
 

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

  • sql
  • sql-сервер
  • tsql
  • иерархические данные
  • рекурсивный запрос

1

Просто еще один вариант с использованием типа данных иерархия

Существуют некоторые дополнительные возможности и функции, связанные с иерархией

Пример

 -- Опционально См. 1-й ГДЕ
Объявить @Top int = null --<< Устанавливает верхнюю часть Hier Try 2
;с cteP как (
      Выберите идентификатор
            ,parent_id
            ,Имя
            , HierID = convert (иерархический идентификатор, concat ('/', ID, '/'))
      Из вашего стола
      Где IsNull(@Top,-1) = случай, когда @Top имеет значение null, тогда isnull(parent_id,-1), иначе конец идентификатора
      --Where parent_id is null -- Используйте это, если вы всегда хотите видеть полную иерархию
      Союз Все
      Выберите ID = r.ID
            ,parent_id = r.parent_id
            ,Имя = р. Имя
            ,HierID = convert(hierarchyid,concat(p.HierID.ToString(),r.ID,'/'))
      Из вашей таблицы r
      Присоединяйтесь к cteP p на r.parent_id = p.ID)
Выберите Lvl = HierID.GetLevel()
      ,ИДЕНТИФИКАТОР
      ,parent_id
      ,Имя
 Из КТЭП А
 Заказ по A.HierID
 

Результаты

 Lvl ID parent_id Имя
1 1 НУЛЕВОЕ P1
1 2 НУЛЕВОЕ P2
2 3 2 П2-1
2 4 2 П2-2
2 5 2 П2-3
3 6 5 П2-3-1
3 7 5 П2-3-2
1 8 НУЛЕВОЕ P3
2 9 8 П3-1
 

Просто для удовольствия. Если я установлю @Top на 2, результатом будет

 Lvl ID parent_id Имя
1 2 НУЛЕВОЕ P2
2 3 2 П2-1
2 4 2 П2-2
2 5 2 П2-3
3 6 5 П2-3-1
3 7 5 П2-3-2
 

1

Построить путь в рекурсивном запросе. Следующее делает это как строку с идентификаторами фиксированной длины:

 с деревом как (
выберите p.id, p.name, p.parentid,
формат (p.parentid, '0000') как путь
из продуктов р
где p.