【发布时间】:2011-07-24 21:10:39
【问题描述】:
给定一个处理员工、客户和供应商的数据库系统,所有这些系统都有多个可能的电话号码,您将如何以一种很好的标准化方式存储这些号码?我稍微考虑了一下,合乎逻辑的方式并没有跳出来。
【问题讨论】:
标签: database-design database-normalization
给定一个处理员工、客户和供应商的数据库系统,所有这些系统都有多个可能的电话号码,您将如何以一种很好的标准化方式存储这些号码?我稍微考虑了一下,合乎逻辑的方式并没有跳出来。
【问题讨论】:
标签: database-design database-normalization
在大多数情况下。 . .
员工电话号码、供应商电话号码和客户电话号码的单独表格存在严重问题。
您需要实现超类型/子类型架构。 (PostgreSQL 代码,未经严格测试。)
create table parties (
party_id integer not null unique,
party_type char(1) check (party_type in ('I', 'O')),
party_name varchar(10) not null unique,
primary key (party_id, party_type)
);
insert into parties values (1,'I', 'Mike');
insert into parties values (2,'I', 'Sherry');
insert into parties values (3,'O', 'Vandelay');
-- For "persons", a subtype of "parties"
create table person_st (
party_id integer not null unique,
party_type char(1) not null default 'I' check (party_type = 'I'),
height_inches integer not null check (height_inches between 24 and 108),
primary key (party_id),
foreign key (party_id, party_type) references parties (party_id, party_type) on delete cascade
);
insert into person_st values (1, 'I', 72);
insert into person_st values (2, 'I', 60);
-- For "organizations", a subtype of "parties"
create table organization_st (
party_id integer not null unique,
party_type CHAR(1) not null default 'O' check (party_type = 'O'),
ein CHAR(10), -- In US, federal Employer Identification Number
primary key (party_id),
foreign key (party_id, party_type) references parties (party_id, party_type) on delete cascade
);
insert into organization_st values (3, 'O', '00-0000000');
create table phones (
party_id integer references parties (party_id) on delete cascade,
-- Whatever you prefer to distinguish one kind of phone usage from another.
-- I'll just use a simple 'phone_type' here, for work, home, emergency,
-- business, and mobile.
phone_type char(1) not null default 'w' check
(phone_type in ('w', 'h', 'e', 'b', 'm')),
-- Phone numbers in the USA are 10 chars. YMMV.
phone_number char(10) not null check (phone_number ~ '[0-9]{10}'),
primary key (party_id, phone_type)
);
insert into phones values (1, 'h', '0000000000');
insert into phones values (1, 'm', '0000000001');
insert into phones values (3, 'h', '0000000002');
-- Do what you need to do on your platform--triggers, rules, whatever--to make
-- these views updatable. Client code uses the views, not the base tables.
-- In current versions of PostgreSQL, I think you'd create some "instead
-- of" rules.
--
create view people as
select t1.party_id, t1.party_name, t2.height_inches
from parties t1
inner join person_st t2 on (t1.party_id = t2.party_id);
create view organizations as
select t1.party_id, t1.party_name, t2.ein
from parties t1
inner join organization_st t2 on (t1.party_id = t2.party_id);
create view phone_book as
select t1.party_id, t1.party_name, t2.phone_type, t2.phone_number
from parties t1
inner join phones t2 on (t1.party_id = t2.party_id);
为了进一步扩展这一点,实现“员工”的表需要引用人员子类型,而不是派对超类型。组织不能有员工。
create table staff (
party_id integer primary key references person_st (party_id) on delete cascade,
employee_number char(10) not null unique,
first_hire_date date not null default CURRENT_DATE
);
如果供应商只能是组织,而不是个人,那么实现供应商的表将以类似的方式引用组织子类型。
对于大多数公司,客户可以是个人或组织,因此实现客户的表应引用超类型。
create table customers (
party_id integer primary key references parties (party_id) on delete cascade
-- Other attributes of customers
);
【讨论】:
person_st (party_id int NOT NULL, party_type char(1) 始终生成为 ('O') 存储)
最直接的方法可能是最好的。即使员工、客户或供应商都有电话、手机和传真号码的位置,最好将这些字段放在每个表上。
但是,您拥有的此类字段越多,您就越应该考虑某种“继承”或集中化。如果有其他联系信息以及多个电话号码,您可以在一个集中的表格 Contacts 上拥有这些常用值。特定于作为客户、供应商等的字段将位于单独的表中。例如,Customer 表将有一个 ContactID 外键返回给 Contacts。
【讨论】:
我认为该决定需要基于对这些联系信息的重要性、更改频率以及拥有电话号码的不同类型的人之间可能存在多少重叠的实际评估。
如果联系信息是不稳定的和/或对应用程序非常重要,那么更多的规范化可能会更好。这意味着有一个 PHONE_NUMBER 表,您的各种 CUSTOMER、SUPPLIER、EMPLOYEE 表(等)可以指向 - 或者更有可能通过联系人类型、联系人个人(客户/供应商/员工)和联络点(电话)。通过这种方式,您可以将员工的家庭电话号码作为其客户记录的主要业务号码,如果更改,则每次使用该联络点都会更改一次。
另一方面,如果您存储电话号码只是为了解决问题而您不使用它们并且可能不会维护它们,那么您需要花费大量时间和精力来建模并将这种复杂性构建到您的数据库不值得,您可以在 CUSTOMER、SUPPLIER、EMPLOYEE 或您拥有的任何东西上做好的、老式的 Phone1、Phone2、Phone3... 列。这是一个糟糕的数据库设计,但它是一个很好的系统开发实践,因为它应用 80/20 规则来确定项目优先级。
所以总结一下:如果数据很重要,那就做对,如果数据真的不重要,就直接加入 - 或者更好的是,完全不考虑。
【讨论】:
Mike Sherrill 'Cat Recall' 的 answer 在 MariaDB 上工作,只有一个变化:“~”需要变成“LIKE”。
这是他在 MariaDB 上测试的示例。我还对使用单词而不是单个字符描述的类型进行了更改 asked about here。
create table parties (
party_id integer not null unique,
party_type varchar(20) not null check (party_type in ('individual', 'organization')),
party_name varchar(50) not null unique,
primary key (party_id, party_type)
);
insert into parties values (1,'individual', 'Mike');
insert into parties values (2,'individual', 'Sherry');
insert into parties values (3,'organization', 'Vandelay');
-- For "persons", a subtype of "parties"
create table person_st (
party_id integer not null unique,
party_type varchar(20) not null default 'individual' check (party_type = 'individual'),
height_inches integer not null check (height_inches between 24 and 108),
primary key (party_id),
foreign key (party_id, party_type) references parties (party_id, party_type) on delete cascade
);
insert into person_st values (1, 'individual', 72);
insert into person_st values (2, 'individual', 60);
-- For "organizations", a subtype of "parties"
create table organization_st (
party_id integer not null unique,
party_type varchar(20) not null default 'organization' check (party_type = 'organization'),
ein CHAR(10), -- In US, federal Employer Identification Number
primary key (party_id),
foreign key (party_id, party_type) references parties (party_id, party_type) on delete cascade
);
insert into organization_st values (3, 'organization', '00-0000000');
create table phones (
party_id integer references parties (party_id) on delete cascade,
-- Whatever you prefer to distinguish one kind of phone usage from another.
-- I'll just use a simple 'phone_type' here, for work, home, emergency,
-- business, and mobile.
phone_type varchar(10) not null default 'work' check
(phone_type in ('work', 'home', 'emergency', 'business', 'mobile')),
-- Phone numbers in the USA are 10 chars. YMMV.
phone_number char(10) not null check (phone_number like '[0-9]{10}'),
primary key (party_id, phone_type)
);
insert into phones values (1, 'home', '0000000000');
insert into phones values (1, 'mobile', '0000000001');
insert into phones values (3, 'home', '0000000002');
-- Do what you need to do on your platform--triggers, rules, whatever--to make
-- these views updatable. Client code uses the views, not the base tables.
-- Inserting and Updating with Views - MariaDB Knowledge Base https://mariadb.com/kb/en/library/inserting-and-updating-with-views/
--
create view people as
select t1.party_id, t1.party_name, t2.height_inches
from parties t1
inner join person_st t2 on (t1.party_id = t2.party_id);
create view organizations as
select t1.party_id, t1.party_name, t2.ein
from parties t1
inner join organization_st t2 on (t1.party_id = t2.party_id);
create view phone_book as
select t1.party_id, t1.party_name, t2.phone_type, t2.phone_number
from parties t1
inner join phones t2 on (t1.party_id = t2.party_id);
【讨论】: