「タスク」を保存するための単一のテーブルがあります。タスクは親および/または子にすることができます。同じテーブルのPKを参照するFKとして 'ParentID'を使用します。 NULLABLEなので、[〜#〜] null [〜#〜]の場合、親タスクはありません。
例は下のスクリーンショットです...
私のチームでは、ParentIDを格納する別のテーブルを作成し、テーブルにNULLSが含まれないようにして正規化設計を改善する方が(正規化/ベストプラクティスにとって)はるかに優れていると主張されています。
これはより良いオプションでしょうか?それとも、クエリが難しくなり、パフォーマンスの問題が発生しますか?
後で問題を見つけるのではなく、設計を最初から正しくしたいだけです。
既存のテーブルのSQL-DDLコード:
CREATE TABLE [Tasks].[TaskDetail]
(
[TaskDetailID] [int] IDENTITY(1,1) NOT NULL,
[TaskName] [varchar](50) NOT NULL,
[TaskDescription] [varchar](250) NULL,
[IsActive] [bit] NOT NULL CONSTRAINT [DF_TaskDetail_IsActive] DEFAULT ((1)),
[ParentID] [int] NULL,
CONSTRAINT [PK_TaskDetail_TaskDetailID] PRIMARY KEY CLUSTERED ([TaskDetailID] ASC),
CONSTRAINT [FK_TaskDetail_ParentID] FOREIGN KEY([ParentID]) REFERENCES [Tasks].[TaskDetail]([TaskDetailID])
);
Null値を禁止したり、隣接リストを別のテーブルに格納する必要がある正規化のルールはありません。どちらのアプローチも一般的であり、選択したパフォーマンスに大きな影響はありません。
どちらのデザインを選択する場合でも、すべての外部キー列はインデックスでサポートされる必要があることに注意してください。したがって、階層を効率的にたどることができるように、ParentIDのインデックスが必要になります。
SQL Server 2017およびAzure SQL DBから、 新しいグラフデータベース機能 と新しいMATCH句を使用して、このタイプの関係をモデル化できます。ヌルを見てはいけません!サンプルスクリプト:
USE tempdb
GO
IF NOT EXISTS ( SELECT * FROM sys.schemas WHERE name = 'Tasks' )
EXEC('CREATE SCHEMA Tasks')
GO
IF NOT EXISTS ( SELECT * FROM sys.schemas WHERE name = 'graph' )
EXEC('CREATE SCHEMA graph')
GO
DROP TABLE IF EXISTS [Tasks].[TaskDetail]
DROP TABLE IF EXISTS graph.taskDetail
DROP TABLE IF EXISTS graph.isParentOf
DROP TABLE IF EXISTS graph.isChildOf
GO
CREATE TABLE [Tasks].[TaskDetail]
(
[TaskDetailID] [int] IDENTITY(1,1) NOT NULL,
[TaskName] [varchar](50) NOT NULL,
[TaskDescription] [varchar](250) NULL,
[IsActive] [bit] NOT NULL CONSTRAINT [DF_TaskDetail_IsActive] DEFAULT ((1)),
[ParentID] [int] NULL,
CONSTRAINT [PK_TaskDetail_TaskDetailID] PRIMARY KEY CLUSTERED ([TaskDetailID] ASC),
CONSTRAINT [FK_TaskDetail_ParentID] FOREIGN KEY([ParentID]) REFERENCES [Tasks].[TaskDetail]([TaskDetailID])
);
GO
SET IDENTITY_INSERT [Tasks].[TaskDetail] ON
GO
INSERT INTO [Tasks].[TaskDetail] ( TaskDetailID, TaskName, TaskDescription, IsActive, ParentID )
VALUES
( 2, 'Cash Receipt 1', 'Fund Account', 1, NULL ),
( 3, 'Cash Receipt 2', 'Check the ...', 1, 2 ),
( 4, 'Non Trade', 'Income & Expense', 1, NULL ),
( 5, 'Income Verified', 'Income Verified', 1, 4 ),
( 6, 'Expense Verified', 'Expense Verified', 1, 4 ),
( 7, 'Pricing', 'Pricing Verified', 1, NULL ),
( 8, 'Manual Pricing', 'Manual Pricing', 1, 7 ),
( 9, 'Missing Pricing', 'Missing Pricing', 1, 7 )
GO
SET IDENTITY_INSERT [Tasks].[TaskDetail] OFF
GO
-- Create graph tables
CREATE TABLE graph.taskDetail (
taskDetailId INT PRIMARY KEY,
taskName VARCHAR(50) NOT NULL,
taskDescription VARCHAR(250) NULL,
isActive BIT NOT NULL
) AS NODE;
CREATE TABLE graph.isParentOf AS Edge;
CREATE TABLE graph.isChildOf AS Edge;
GO
-- !!TODO add indexes
-- Add the node data
INSERT INTO graph.taskDetail ( taskDetailId, taskName, taskDescription, isActive )
SELECT taskDetailId, taskName, taskDescription, isActive
FROM Tasks.TaskDetail
-- Add the Edge data
INSERT INTO graph.isParentOf ( $from_id, $to_id )
SELECT p.$node_id, c.$node_id
FROM Tasks.TaskDetail td
INNER JOIN graph.taskDetail c ON td.TaskDetailId = c.taskDetailId
INNER JOIN graph.taskDetail p ON td.ParentID = p.taskDetailId
-- Add inverse relationship
INSERT INTO graph.isChildOf ( $from_id, $to_id )
SELECT $to_id, $from_id
FROM graph.isParentOf
GO
-- Now run the graph queries
SELECT
FORMATMESSAGE( 'Task [%s](%i) is the parent of [%s](%i)', p.taskName, p.taskDetailId, c.taskName, c.taskDetailId )
FROM graph.taskDetail p, graph.isParentOf isParentOf, graph.taskDetail c
WHERE MATCH ( p-(isParentOf)->c )
ORDER BY 1;
-- Tasks with same parent
-- Tasks 5 and 6 have the same parent 4
-- Tasks 8 and 9 have the same parent 7
SELECT
FORMATMESSAGE( 'Tasks %i and %i have the same parent %i', t1.taskDetailId, t3.taskDetailId, t2.taskDetailId )
FROM graph.taskDetail t1, graph.isChildOf c1, graph.taskDetail t2, graph.isChildOf c2, graph.taskDetail t3
WHERE MATCH ( t1-(c1)->t2<-(c2)-t3 )
AND t1.$node_id < t3.$node_id
ORDER BY 1;
-- Find tasks with no parents?
SELECT
FORMATMESSAGE( 'Task [%s](%i) has no parents.', p.taskName, p.taskDetailId )
FROM graph.taskDetail p
WHERE NOT EXISTS
(
SELECT *
FROM graph.isParentOf isParentOf
WHERE p.$node_id = isParentOf.$from_id
)
私の結果: