【问题标题】:Basic database design about intermediate optional models关于中间可选模型的基本数据库设计
【发布时间】:2011-03-13 17:14:39
【问题描述】:

我正在做一个项目,在设计一个看似非常简单的场景时遇到了些许困难:

user 属于city 属于country,但是,city 引用可能是nulluser 必须属于country。换句话说(在 basic RoR 模型语法中),

# class User < ActiveRecord::Base 
belongs_to :city
belongs_to :country
validates_existence_of :country

# class City < ActiveRecord::Base
has_many :users
belongs_to :country
validates_existence_of :country

# class Country < ActiveRecord::Base
has_many :users
has_many :cities    

我对这个超级简单的设计的问题是有太多的冗余。只要cityuser 引用,country 引用就可以从中推断出来(换句话说,因为它已经在city 表中被引用,所以看起来并不那么棒也可以在user 表中引用它)。

【问题讨论】:

  • 你确定一个城市一定在一个国家吗?反例可能是柏林(在糟糕的过去)和耶路撒冷(现在)。我希望还有其他人。
  • 好点,我不确定如何处理这些异常...

标签: sql ruby-on-rails database database-design


【解决方案1】:

我没有深思熟虑的答案,但我首先想到的是这个,

# class User < ActiveRecord::Base 
belongs_to :country, :through => :city
validates_existence_of :city

# class City < ActiveRecord::Base
has_many :users
belongs_to :country
validates_existence_of :country

# class Country < ActiveRecord::Base
has_many :users, :through => :city
has_many :cities

诀窍是在每个国家/地区添加一个虚拟或空白城市,以便验证成立。

【讨论】:

  • 感谢您的意见,OmniBus!
【解决方案2】:

当 A(城市)也唯一标识 B(国家)时会发生这种情况,但 A 是可选的,而 B 是强制性的。基本上,添加 Country 只是因为 City 是可选的,而仍然需要识别每个用户的国家/地区。

将国家和城市联系在一起的想法可能看起来很有吸引力,因为一个城市唯一地“识别”了一个国家,但是:是吗?阿姆斯特丹不仅仅是荷兰的一座城市。

此外,它还带有您在评论中已经提到的问题……您如何处理其他数据;现在列出国家/地区需要将它们从国家/城市合并中过滤掉。

您的原始设计可能会觉得冗余和数据方面可能是这样,但逻辑方面和需求方面却不是。我会坚持下去,因为它非常清晰并且完美地反映了要求。我会学会忍受明显的冗余。您可能想出的任何“解决方案”来避免“冗余”,最终都可能会弄得一团糟。或者会使将来定义查询变得更加困难。

【讨论】:

  • 对此+1,有时标准化可能会导致不必要的痛苦。我唯一要补充的是,您可以并且可能应该进行验证,以确保该城市位于所选国家/地区。
  • Marjan,非常感谢您的意见(我得到的越多,我对使用某个解决方案的感觉就越好)。你对学习这类东西有什么建议吗?我应该只是拿起一本关于数据库设计的书还是有什么好的网络资源?关于这个话题的问题是我搜索了这个问题的答案,但实际上只是空手而归。我担心我在数据库设计方面所做的所有决定都可能不够完美......
  • @Geoff,对,感谢您支持此回复。并且 +1 提醒我验证城市(我显然是在计划它,但我很容易忘记这些随着 Rails 的敏捷性发展的事情)。
  • @tjko:抱歉,我没有推荐书名之类的东西。总的来说:不要卡在数据库设计上,停留在它之上,想想你正在建模的东西。如果它们是不同的概念,请将它们分开。除非有(经证实的)需要,否则不要优化。除此之外:与有更多经验的人多谈(比如这里的 SO),了解他们的观点,即使他们有冲突,你也会对优缺点有所了解。
【解决方案3】:

由于某种原因,它具有“sql”标签,所以这是我在 SQL 中的操作方式(请注意,自始至终都有引用整数,没有 NULLable 列):

CREATE TABLE Countries 
(
 country_code CHAR(3) NOT NULL UNIQUE
);

CREATE TABLE Cities 
(
 city_name VARCHAR(20) NOT NULL, 
 country_code CHAR(3) NOT NULL 
    REFERENCES Countries (country_code), 
 UNIQUE (country_code, city_name)
);

CREATE TABLE Users
(
 username CHAR(8) NOT NULL UNIQUE, 
 country_code CHAR(3) NOT NULL, 
 UNIQUE (country_code, username)
);

CREATE TABLE UsersCountries
(
 username CHAR(8) NOT NULL UNIQUE, 
 country_code CHAR(3) NOT NULL, 
 FOREIGN KEY (country_code, username)
    REFERENCES Users (country_code, username), 
 city_name VARCHAR(20) NOT NULL, 
 FOREIGN KEY (country_code, city_name)
    REFERENCES Cities (country_code, city_name)
);

测试数据:

INSERT INTO Countries (country_code) VALUES 
('ITL'), 
('ESP');

INSERT INTO Cities (city_name, country_code) 
VALUES 
('Roma', 'ITL'), 
('Naples', 'ITL'), 
('Barcelona', 'ESP'), 
('Madrid', 'ESP');

INSERT INTO Users (username, country_code) VALUES 
('00000001', 'ESP'), 
('00000002', 'ESP'), 
('00000003', 'ITL'), 
('00000004', 'ITL');

INSERT INTO UsersCountries (username, city_name, country_code) 
VALUES 
('00000002', 'Madrid', 'ESP'), 
('00000004', 'Roma', 'ITL');

公平地说,大多数 SQL 编码人员不会反感使用 NULLable 列,而是希望所有用户的详细信息都出现在一个表中。假设您的 SQL 产品(正确地)没有将 NULL 视为一个值(例如 MS SQL Server 没有,但 MS Access 有),那么以下内容将起作用并且等效于上述结构(即,尽管存在,但仍然存在引用整数NULLable 列):

CREATE TABLE Users
(
 username CHAR(8) NOT NULL UNIQUE, 
 city_name VARCHAR(20), 
 country_code CHAR(3) NOT NULL
    REFERENCES Countries (country_code), 
 FOREIGN KEY (country_code, city_name)
    REFERENCES Cities (country_code, city_name)
);

INSERT INTO Users (username, city_name, country_code) VALUES 
('00000001', NULL, 'ESP'), 
('00000002', 'Madrid', 'ESP'), 
('00000003', NULL, 'ITL'), 
('00000004', 'Roma', 'ITL');

【讨论】:

  • onedaywhen,感谢您发布回复。我将其标记为 SQL,因为我认为可能精通 SQL 的人可以对数据库设计提供很好的见解,但也许我错误地认为,对不起(我是新来的)。无论如何,感谢您的回复,我能读懂一点,但我必须说它有点太低级了,对我要解决的设计问题没有太多意义。
  • 重点是,这些设计没有冗余!当然,country_code 出现在所有三个/四个表中,但每个表都需要维护数据完整性。您不能从一个城市“推断”一个国家(例如,它是德克萨斯州的巴黎还是法国的巴黎?),因此是复合键。
猜你喜欢
  • 2013-11-06
  • 2017-06-19
  • 1970-01-01
  • 1970-01-01
  • 2019-09-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多