Thiết kế bảng quan hệ cha mẹ / con cái - Cách thực hành tốt nhất?


7

Tôi có một bảng duy nhất để lưu trữ 'Nhiệm vụ'. Một nhiệm vụ có thể là cha mẹ và / hoặc một đứa trẻ. Tôi sử dụng ' ParentID ' làm FK tham chiếu PK trên cùng một bảng. Nó là NULLABLE, vì vậy nếu là NULL thì nó không có tác vụ chính.

Ví dụ là ảnh chụp màn hình bên dưới ...

nhập mô tả hình ảnh ở đây

Trong nhóm của tôi đã tranh luận rằng sẽ tốt hơn nhiều (đối với việc thực hiện bình thường / thực hành tốt nhất) để tạo một bảng riêng để lưu trữ ParentID và do đó tránh có NULLS trong bảng và dẫn đến thiết kế chuẩn hóa tốt hơn.

Đây sẽ là một lựa chọn tốt hơn? Hoặc sẽ khó khăn hơn với truy vấn và gây ra vấn đề hiệu suất?

Chúng tôi chỉ muốn có được thiết kế ngay từ đầu chứ không phải tìm kiếm các vấn đề sau này.

Mã SQL-DDL cho bảng hiện có:

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])
);

2
Tôi có phải là người duy nhất mong đợi câu hỏi này là về Nuôi dạy con cái không?
Martin Hennings

được lồng nhau quan hệ cha mẹ / con cái? 3 có thể được chẵn lẻ 4 trong ví dụ của bạn và như vậy? Nếu nó có thể được lồng nhau thì điểm nào trong việc tạo bảng con? Ngoài ra, nếu nó được lồng thì bạn có thể giữ một cột có tên là "Cấp độ" chứa giá trị được tính toán trước và nó có ích. Nếu nó không được lồng, bạn sẽ không phải đối mặt truy vấn thách thức. Tìm kiếm xem có phải là không đáng tin cậy FK hay không. Một logic cho bảng riêng biệt là nếu bảng sẽ chứa hàng triệu .billions các bản ghi thì tốt hơn là giữ chúng tách biệt.
KumarHarsh

Câu trả lời:


7

Kỹ thuật mà bạn đang mô tả để thể hiện hệ thống phân cấp nhiệm vụ được gọi là 'Danh sách điều chỉnh'. Mặc dù nó là trực quan nhất đối với con người, nhưng nó không cho vay để truy vấn rất hiệu quả trong SQL. Các kỹ thuật khác bao gồm liệt kê đường dẫn (còn gọi là đường dẫn cụ thể hóa) và các tập hợp lồng nhau. Để tìm hiểu về một số kỹ thuật khác, hãy đọc bài đăng này hoặc tìm kiếm trên web nhiều bài viết về các kỹ thuật này.

SQL Server cung cấp một biểu diễn phân cấp riêng cho phép liệt kê đường dẫn. Đây rất có thể là đặt cược tốt nhất của bạn ...


3

Không có quy tắc chuẩn hóa nào cấm các giá trị null hoặc sẽ yêu cầu lưu trữ danh sách kề trong một bảng riêng. Cả hai cách tiếp cận đều phổ biến và không có ý nghĩa về hiệu suất đáng kể đối với lựa chọn của bạn.

Cho dù bạn chọn thiết kế nào, hãy nhớ tất cả các cột khóa ngoại cần được hỗ trợ bởi một chỉ mục. Vì vậy, bạn sẽ cần một chỉ mục trên ParentID để hỗ trợ truyền tải hiệu quả xuống hệ thống phân cấp.


2

Từ SQL Server 2017 và Azure SQL DB, bạn có thể sử dụng các khả năng cơ sở dữ liệu đồ thị mới và mệnh đề MATCH mới để mô hình hóa loại mối quan hệ này. Nhìn không có giá trị! Một tập lệnh mẫu:

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
    )

Kết quả của tôi:

Kết quả của tôi

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.