Abuamer Ответов: 2

Как сделать рекурсивный cte SQL с 3 уровнями иерархии ?


у меня есть таблица в моей версии SQL Server, Экспресс-выпуск 2014 под названием ChartOfAccountsTree. то, что я хочу назвать своими учетными записями внутри этой таблицы с иерархическим представлением . во-первых, у меня есть MainTree ... тогда внутри него есть активы . тогда внутри активов есть земли.
то, что я хочу сделать, это назвать эти счета такими (Земли/активы/MainTree).
это код создания таблицы


/****** Object:  Table [dbo].[Tbl_ChartOfAccountsTree]    Script Date: 27/12/2019 10:18:01 م ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Tbl_ChartOfAccountsTree](
	[Chart_ID] [int] NOT NULL,
	[Chart_Name] [nvarchar](500) NULL,
	[Account_Level] [int] NULL,
	[ParentAccount] [int] NULL,
	[Accoutn_Type] [nvarchar](150) NULL,
	[Direction] [nvarchar](150) NULL,
	[OB] [decimal](18, 2) NULL,
	[Date] [date] NULL,
 CONSTRAINT [PK_Tbl_ChartOfAccountsTree] PRIMARY KEY CLUSTERED 
(
	[Chart_ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO
INSERT [dbo].[Tbl_ChartOfAccountsTree] ([Chart_ID], [Chart_Name], [Account_Level], [ParentAccount], [Accoutn_Type], [Direction], [OB], [Date]) VALUES (0, N'MainTree', 0, NULL, N'Parent', NULL, CAST(0.00 AS Decimal(18, 2)), CAST(N'2019-07-01' AS Date))
GO
INSERT [dbo].[Tbl_ChartOfAccountsTree] ([Chart_ID], [Chart_Name], [Account_Level], [ParentAccount], [Accoutn_Type], [Direction], [OB], [Date]) VALUES (1000, N'Assets', 1, 0, N'Parent', N'Balance', CAST(0.00 AS Decimal(18, 2)), CAST(N'2019-07-01' AS Date))
GO
INSERT [dbo].[Tbl_ChartOfAccountsTree] ([Chart_ID], [Chart_Name], [Account_Level], [ParentAccount], [Accoutn_Type], [Direction], [OB], [Date]) VALUES (1100, N'FixedAssets', 2, 1000, N'Parent', N'Balance', CAST(0.00 AS Decimal(18, 2)), CAST(N'2019-07-01' AS Date))
GO
INSERT [dbo].[Tbl_ChartOfAccountsTree] ([Chart_ID], [Chart_Name], [Account_Level], [ParentAccount], [Accoutn_Type], [Direction], [OB], [Date]) VALUES (1101, N'Buildings', 3, 1100, N'Child', N'Balance', CAST(0.00 AS Decimal(18, 2)), CAST(N'2019-11-01' AS Date))
GO
INSERT [dbo].[Tbl_ChartOfAccountsTree] ([Chart_ID], [Chart_Name], [Account_Level], [ParentAccount], [Accoutn_Type], [Direction], [OB], [Date]) VALUES (1102, N'Lands', 3, 1100, N'Child', N'Balance', CAST(0.00 AS Decimal(18, 2)), CAST(N'2019-11-29' AS Date))
GO
ALTER TABLE [dbo].[Tbl_ChartOfAccountsTree]  WITH CHECK ADD  CONSTRAINT [FK_Tbl_ChartOfAccountsTree_Tbl_ChartOfAccountsTree] FOREIGN KEY([ParentAccount])
REFERENCES [dbo].[Tbl_ChartOfAccountsTree] ([Chart_ID])
GO
ALTER TABLE [dbo].[Tbl_ChartOfAccountsTree] CHECK CONSTRAINT [FK_Tbl_ChartOfAccountsTree_Tbl_ChartOfAccountsTree]
GO


проблема в том, что когда я вызываю столбец Chart_Name, он дает мне
(Земли/Активы)

Что я уже пробовал:

;
WITH HierarchyList_CTE
AS
(
SELECT    Chart_ID, Chart_Name, ParentAccount, 1 AS StaffLevel
FROM      Tbl_ChartOfAccounts
WHERE     ParentAccount IS NULL
UNION ALL
SELECT         ST.Chart_ID, ST.Chart_Name
              , ST.ParentAccount, StaffLevel + 1
FROM          Tbl_ChartOfAccounts ST
INNER JOIN    HierarchyList_CTE CTE
              ON ST.ParentAccount = CTE.Chart_ID
)
SELECT         stf.Chart_ID,
              CTE.Chart_Name + ' / '+ STF.Chart_Name
               AS FullName
              ,CTE.StaffLevel ,CTE.ParentAccount
FROM          HierarchyList_CTE CTE
INNER JOIN    Tbl_ChartOfAccounts STF
              ON STF.ParentAccount = CTE.Chart_ID


может ли кто нибудь помочь мне плз

2 Ответов

Рейтинг:
4

Ahmed Elbaz

Основываясь на определении таблицы и предоставленных вами данных, я рекомендую создать функцию для возврата полного имени (полного имени) каждой учетной записи следующим образом:

CREATE FUNCTION [dbo].[GetAccountFullName](@AccountId INT) RETURNS NVARCHAR(MAX)
AS 
BEGIN

DECLARE @AccountFullName  NVARCHAR(MAX);

WITH HierarchyList_CTE
AS
(
SELECT    Chart_ID, Chart_Name, ParentAccount
FROM      Tbl_ChartOfAccountsTree
WHERE     Chart_ID = @AccountId
UNION ALL
SELECT         ST.Chart_ID, ST.Chart_Name, ST.ParentAccount
FROM          Tbl_ChartOfAccountsTree ST
INNER JOIN    HierarchyList_CTE CTE
              ON ST.Chart_ID = CTE.ParentAccount
)

SELECT @AccountFullName = COALESCE(@AccountFullName+'/','') + ISNULL(Chart_Name, '/')
FROM HierarchyList_CTE
 
RETURN @AccountFullName

END

Приведенная выше функция принимает идентификатор учетной записи в данном случае Chart_ID и возвращает ее полное имя.
Вы можете использовать его для возврата полного имени для одной учетной записи следующим образом:
SELECT [dbo].[GetAccountFullName] (1102)

и вот результат
Lands/FixedAssets/Assets/MainTree

Или верните полное имя для всех записей следующим образом:
SELECT    Chart_ID, Chart_Name, ParentAccount, [dbo].[GetAccountFullName] (Chart_ID) AS FullName
FROM      Tbl_ChartOfAccountsTree

и вот результат
Chart_ID	Chart_Name	ParentAccount	FullName
0	    MainTree	NULL	MainTree
1000	Assets	0	Assets/MainTree
1100	FixedAssets	1000	FixedAssets/Assets/MainTree
1101	Buildings	1100	Buildings/FixedAssets/Assets/MainTree
1102	Lands	1100	Lands/FixedAssets/Assets/MainTree

Кроме того, рекомендуется добавить новый столбец в таблицу accounts (Tbl_ChartOfAccountsTree) для хранения значения полного имени вместо того, чтобы генерировать его каждый раз, когда оно вам нужно, поскольку предполагается, что оно редко изменяется.
Для этого используйте следующий код для добавления столбца:
ALTER TABLE dbo.Tbl_ChartOfAccountsTree ADD FullName nvarchar(MAX) NULL

А затем используйте приведенный ниже код для обновления всех учетных записей в таблице:
UPDATE [dbo].[Tbl_ChartOfAccountsTree]
   SET [FullName] =  [dbo].[GetAccountFullName] (Chart_ID)

Возможно, вам придется заново создавать полные имена всякий раз, когда в таблице происходят какие-либо изменения. Один из способов сделать это-добавить триггер в таблицу для запуска вышеупомянутого оператора update, когда происходит какое-либо изменение следующим образом:
CREATE TRIGGER TriggerUpdateFullNames
   ON  [dbo].[Tbl_ChartOfAccountsTree]
   AFTER INSERT,DELETE,UPDATE
AS 
BEGIN

	SET NOCOUNT ON;

	UPDATE [dbo].[Tbl_ChartOfAccountsTree]
    SET [FullName] =  [dbo].[GetAccountFullName] (Chart_ID)

END
GO

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

Надеюсь, это поможет :)


Рейтинг:
2

Wendelius

Если я правильно понимаю вопрос, вы можете построить столбец "путь" в своем CTE

Рассмотрим следующий пример

Создайте тестовую таблицу и заполните ее некоторыми данными

create table locations (
   id int,
   parentid int,
   place varchar(100)
)

insert into locations (id, parentid, place) values 
(1, null, 'Europe'),
(2, 1, 'France'),
(3, 2, 'Paris'),
(4, 2, 'Marseille'),
(5, 2, 'Lyon'),
(6, 1, 'Italy'),
(7, 6, 'Rome'),
(8, 6, 'Milan'),
(9, 6, 'Venice'),
(10, 1, 'United Kingdom'),
(11, 10, 'London'),
(12, 10, 'Cambridge'),
(13, 10, 'Bath')

Запрос данных
with Places (id, parentid, place, level, path) AS (
   select l.id, 
          l.parentid, 
	      l.place, 
		  1 as level,
	      cast('Continent' as varchar(1000))
   from locations l
   where l.parentid is null
   union all
   select l.id, 
          l.parentid, 
	      l.place, 
		  p.level + 1,
		  cast(case p.level 
		       when 1 then p.path + '/Country'		     
			   when 2 then p.path + '/City'		     
		    end as varchar(1000))
   from locations l
        inner join places p on p.id = l.parentid
)
select * 
from places
order by id

результат был бы таков
id   parentid   place            level   path
--   --------   -----            -----   --------
1    NULL       Europe           1       Continent
2    1          France           2       Continent/Country
3    2          Paris            3       Continent/Country/City
4    2          Marseille        3       Continent/Country/City
5    2          Lyon             3       Continent/Country/City
6    1          Italy            2       Continent/Country
7    6          Rome             3       Continent/Country/City
8    6          Milan            3       Continent/Country/City
9    6          Venice           3       Continent/Country/City
10   1          United Kingdom   2       Continent/Country
11   10         London           3       Continent/Country/City
12   10         Cambridge        3       Continent/Country/City
13   10         Bath             3       Continent/Country/City


[ДОБАВЛЕН АЛЬТЕРНАТИВНЫЙ ПРИМЕР]

Что касается примера с футболистом. Идея та же, что и в предыдущем запросе. Во время каждого рекурсивного цикла объедините значения в виде пути к результирующему набору.

Рассмотреть следующее

Данные
create table person (
   id int,
   fatherid int,
   name varchar(100)
)


insert into person (id, fatherid, name) values 
(1, null, 'Aveiro'),
(2, 1, 'Dos Santos'),
(3, 2, 'Ronaldo'),
(4, 3, 'Cristiano')


Запрос

with PersonHier (id, fatherid, name, level, fullname) AS (
   select p.id, 
          p.fatherid, 
	      p.name, 
		  1 as level,
	      cast(p.name as varchar(max))
   from person p
   where p.fatherid is null
   union all
   select p.id, 
          p.fatherid, 
	      p.name, 
		  h.level + 1,
		  concat(p.name, ' / ', h.FullName)
   from person p
        inner join PersonHier h on h.id = p.fatherid
)
select * 
from PersonHier
order by id


результат
id   fatherid   name       level   fullname
--   --------   -----      -----   ------------
1    NULL       Aveiro     1       Aveiro
2    1          Dos Santos 2       Dos Santos / Aveiro
3    2          Ronaldo    3       Ronaldo / Dos Santos / Aveiro
4    3          Cristiano  4       Cristiano / Ronaldo / Dos Santos / Aveiro


Abuamer

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

Wendelius

Не могли бы вы объяснить проблему более подробно, каков ожидаемый результат?

Abuamer

Хорошо, я приведу вам пример того, что я хочу, более подробно. Криштиану Роналду-Хороший Игрок, И Его Полное Имя (Кристаино Роналду Душ Сантуш Авейру).
Авейру-это великий дедушка с уровнем 0.
Дос Сантос - сын Авейру с уровнем 1.
Роналду-сын Дос Сантоса с уровнем 2.
И, наконец, Криштиану-сын Роналду с уровнем 3.

то что я хочу сделать это когда я выбираю Криштиану мой результат должен быть таким
(Криштиану / Роналду / Душ Сантуш / Авейру)
выход должен дать мне Криштиану / его отец /его дедушка/его прадедушка)

а это запрос на создание таблицы с иерархическим представлением

УСТАНОВИТЕ ANSI_NULLS НА
ГО
УСТАНОВИТЕ QUOTED_IDENTIFIER НА
ГО
Создайте таблицу [dbo].[Tbl_ChartOfAccountsTree](
[Chart_ID] [int] NOT NULL,
[Chart_Name] [nvarchar](500) NULL,
[Account_Level] [int] NULL,
[ParentAccount] [int] NULL,
[Accoutn_Type] [nvarchar](150) NULL,
[Направление] [nvarchar](150) NULL,
[OB] [decimal](18, 2) NULL,
[Дата] [дата] NULL,
Ограничение [PK_Tbl_ChartOfAccountsTree] первичный ключ КЛАСТЕРИЗОВАН
(
[Chart_ID] ASC
)С (КАК = ВЫКЛ, STATISTICS_NORECOMPUTE = OFF, ТО ЗНАЧЕНИЕ IGNORE_DUP_KEY = OFF, ТО ПАРАМЕТРЫ ALLOW_ROW_LOCKS = ON, ТО ALLOW_PAGE_LOCKS ИНСТРУКЦИИ =) НА [ОСНОВНОЙ]
) НА [ПЕРВИЧНОМ]

ГО
Вставить [dbo].[Tbl_ChartOfAccountsTree] ([Chart_ID], [Chart_Name], [Account_Level], [ParentAccount], [Accoutn_Type], [направление], [объединение], [дата]) значения (0, Н'Aveiro', 0, нуль, Н'Parent', нуль, литой(0.00 как decimal(18, 2)), ролях(Н-2019-07-01 как дата))
ГО
Вставить [dbo].[Tbl_ChartOfAccountsTree] ([Chart_ID], [Chart_Name], [Account_Level], [ParentAccount], [Accoutn_Type], [направление], [объединение], [дата]) значения (1000, Н'Dos Сантос', 1, 0, Н'Parent', Н'Balance', литой(0.00 как decimal(18, 2)), ролях(Н-2019-07-01 как дата))
ГО
Вставить [dbo].[Tbl_ChartOfAccountsTree] ([Chart_ID], [Chart_Name], [Account_Level], [ParentAccount], [Accoutn_Type], [направление], [объединение], [дата]) значения (1100, Н'Ronaldo', 2, 1000, Н'Parent', Н'Balance', литой(0.00 как decimal(18, 2)), ролях(Н-2019-07-01 как дата))
ГО
Вставить [dbo].[Tbl_ChartOfAccountsTree] ([Chart_ID], [Chart_Name], [Account_Level], [ParentAccount], [Accoutn_Type], [направление], [объединение], [дата]) значения (1101, Н'Cristiano', 3, 1100, Н'Child', Н'Balance', литой(0.00 как decimal(18, 2)), ролях(Н-2019-11-01 как дата))
ГО
ALTER TABLE [dbo].[Tbl_ChartOfAccountsTree] с проверкой добавить ограничение [FK_Tbl_ChartOfAccountsTree_Tbl_Chartofaccountstree] внешний ключ([ParentAccount])
Список литературы [dbo].[Tbl_ChartOfAccountsTree] ([Chart_ID])
ГО
ALTER TABLE [dbo].[Tbl_ChartOfAccountsTree] контрольное ограничение [FK_Tbl_ChartOfAccountsTree_Tbl_Chartofaccountstree]
ГО
так что пожалуйста есть ли какая нибудь возможная помощь

Abuamer

Хорошо, я приведу вам пример того, что я хочу, более подробно. Криштиану Роналду-Хороший Игрок, И Его Полное Имя (Кристаино Роналду Душ Сантуш Авейру).
Авейру-это великий дедушка с уровнем 0.
Дос Сантос - сын Авейру с уровнем 1.
Роналду-сын Дос Сантоса с уровнем 2.
И, наконец, Криштиану-сын Роналду с уровнем 3.

то что я хочу сделать это когда я выбираю Криштиану мой результат должен быть таким
(Криштиану / Роналду / Душ Сантуш / Авейру)
выход должен дать мне Криштиану / его отец /его дедушка/его прадедушка)

а это запрос на создание таблицы с иерархическим представлением

УСТАНОВИТЕ ANSI_NULLS НА
ГО
УСТАНОВИТЕ QUOTED_IDENTIFIER НА
ГО
Создайте таблицу [dbo].[Tbl_ChartOfAccountsTree](
[Chart_ID] [int] NOT NULL,
[Chart_Name] [nvarchar](500) NULL,
[Account_Level] [int] NULL,
[ParentAccount] [int] NULL,
[Accoutn_Type] [nvarchar](150) NULL,
[Направление] [nvarchar](150) NULL,
[OB] [decimal](18, 2) NULL,
[Дата] [дата] NULL,
Ограничение [PK_Tbl_ChartOfAccountsTree] первичный ключ КЛАСТЕРИЗОВАН
(
[Chart_ID] ASC
)С (КАК = ВЫКЛ, STATISTICS_NORECOMPUTE = OFF, ТО ЗНАЧЕНИЕ IGNORE_DUP_KEY = OFF, ТО ПАРАМЕТРЫ ALLOW_ROW_LOCKS = ON, ТО ALLOW_PAGE_LOCKS ИНСТРУКЦИИ =) НА [ОСНОВНОЙ]
) НА [ПЕРВИЧНОМ]

ГО
Вставить [dbo].[Tbl_ChartOfAccountsTree] ([Chart_ID], [Chart_Name], [Account_Level], [ParentAccount], [Accoutn_Type], [направление], [объединение], [дата]) значения (0, Н'Aveiro', 0, нуль, Н'Parent', нуль, литой(0.00 как decimal(18, 2)), ролях(Н-2019-07-01 как дата))
ГО
Вставить [dbo].[Tbl_ChartOfAccountsTree] ([Chart_ID], [Chart_Name], [Account_Level], [ParentAccount], [Accoutn_Type], [направление], [объединение], [дата]) значения (1000, Н'Dos Сантос', 1, 0, Н'Parent', Н'Balance', литой(0.00 как decimal(18, 2)), ролях(Н-2019-07-01 как дата))
ГО
Вставить [dbo].[Tbl_ChartOfAccountsTree] ([Chart_ID], [Chart_Name], [Account_Level], [ParentAccount], [Accoutn_Type], [направление], [объединение], [дата]) значения (1100, Н'Ronaldo', 2, 1000, Н'Parent', Н'Balance', литой(0.00 как decimal(18, 2)), ролях(Н-2019-07-01 как дата))
ГО
Вставить [dbo].[Tbl_ChartOfAccountsTree] ([Chart_ID], [Chart_Name], [Account_Level], [ParentAccount], [Accoutn_Type], [направление], [объединение], [дата]) значения (1101, Н'Cristiano', 3, 1100, Н'Child', Н'Balance', литой(0.00 как decimal(18, 2)), ролях(Н-2019-11-01 как дата))
ГО
ALTER TABLE [dbo].[Tbl_ChartOfAccountsTree] с проверкой добавить ограничение [FK_Tbl_ChartOfAccountsTree_Tbl_Chartofaccountstree] внешний ключ([ParentAccount])
Список литературы [dbo].[Tbl_ChartOfAccountsTree] ([Chart_ID])
ГО
ALTER TABLE [dbo].[Tbl_ChartOfAccountsTree] контрольное ограничение [FK_Tbl_ChartOfAccountsTree_Tbl_Chartofaccountstree]
ГО
так что пожалуйста есть ли какая нибудь возможная помощь

Wendelius

Я попытался привести еще один пример, основанный на именах, касающихся Криштиану Роналду. Смотрите обновленный ответ, надеюсь, что это поможет.