【问题标题】:Best Practices when Choosing SQL Keys Types选择 SQL 键类型时的最佳实践
【发布时间】:2020-02-16 02:51:55
【问题描述】:

我对 SQL 数据库非常陌生。由于我们的 DA 突然辞职,我需要为我的实习创建一个数据库。

数据可用,但尚未输入数据库。我正在尝试按照在线教程进行操作,但被困在为不同的密钥类型选择什么上。

希望得到更有经验的人的反馈,得到您的指导。

表格列:

entry id (unique)
entry timestamp
username (unique but can appear more than once if the same user input a new meal record)
email address
user first and last name
meals taken date
meal type
meal calories
meal duration
meal cost
meal location
user notes

对于主键 = 条目 id

对于候选键,我将选择用户名和条目 ID。我应该选择其他列作为候选键吗?电子邮件会更有意义吗?但是如果他们输入另一个用餐记录,则可以重复用户名。这有关系吗?

For compound Key = 
email address + user first and last name?
record date + user name? 

我还需要分类其他键吗?

在线教程这些是我需要识别的最基本的键。但我不确定我是否做出了正确的选择。感谢您提供任何反馈。

【问题讨论】:

  • 您熟悉数据库设计中的规范化思想吗?看起来其中一些属性可能应该放在其他表中。如果用户名不是唯一的,则它不是键(它可能是复合键的 part 之一)。您不应该只根据列名假设键。密钥必须基于对业务需求的理解,而不仅仅是数据。
  • 是时候关注已出版的关于信息建模、关系模型和数据库设计与查询的学术教科书了。 (记录和使用设计的语言和工具手册不是这样的教科书。)(维基文章或网络帖子也不是。)数十种已出版的学术信息建模和数据库设计教科书以 pdf 格式在线免费提供。 stanford.edu 有免费的在线课程。 (但在 SO 之外寻求资源是题外话。) PS 这个问题实际上要求我们重写一本教科书(带有定制教程)。关注一个并询问 1 个关于你第一次被困在哪里的具体问题。
  • “独特但可以出现多次”是一种缩略语。 “对于主键 = 条目 ID 对于候选键,我将选择用户名和条目 ID。”一个关系 PK 是一个 CK,一个 CK 不能包含另一个 CK。虽然一个 SQL PK 实际上对应一个关系超键。 “复合”键只是具有多于一列的键。你真的需要读一本教科书。

标签: sql database database-design


【解决方案1】:

您的数据似乎包含多个实体。根据您的简单描述,我可以确定:

  • users
  • meals
  • locations

然后似乎有一个叫做 entries 的东西,它是一个用户,在某个位置吃(买?)一顿饭。这是实体之间的 3 路连接表。

这是对您要表示的内容的猜测。但这听起来像是多个表。

【讨论】:

    【解决方案2】:

    我不知道你用的是什么数据库,我会用 Postgres,因为它是免费的,遵循 SQL 标准,有很好的文档,而且非常强大。

    正如 Gordon 所说,您似乎拥有三样东西:用户、膳食和位置。三样东西意味着每样东西一张桌子。这避免了存储冗余数据。整个话题是database normalization

    create table users (
        id bigserial primary key,
        username text not null unique,
        email text not null unique,
        first_name text not null,
        last_name text not null
    );
    
    create table meals (
        id bigserial primary key,
        type text not null unique,
        -- If calories vary by user, move this into user_meals.
        calories integer not null
    );
    
    create table locations (
        id bigserial primary key,
        -- The specific information you have about a location will vary,
        -- but this is a good start. I've allowed nulls because often
        -- people don't have full information about their location.
        name text,
        address text,
        city text,
        province text,
        country text,
        postal_code text
    );
    

    您询问了复合键。不要打扰。潜在的问题太多了。在每个表上使用一个简单、唯一、自动递增的大整数。

    主键必须是唯一且不变的。用户名、姓名、电子邮件地址……这些都可以改变。即使您认为他们不会,为什么要将风险纳入您的架构?

    外键将在整个数据库和索引中重复多次。您希望它们小而简单且大小固定,以免占用不必要的存储空间。整数小而简单且大小固定。文字不是。

    复合主键可能会泄露信息。主键指的是一行,这些通常出现在 URL 等中。如果我们使用用户的电子邮件地址或姓名可能会泄露personally identifiable information

    我选择了bigserial 作为主键。 serial 类型是自动递增的,因此数据库将负责为每个新行分配一个主键。 bigserial 使用 64 位整数。一个普通整数只能容纳大约 2 到 40 亿个条目。这听起来很多,但事实并非如此,而且您不想用完主键。 bigserial 可以处理 9 quintillion。每行额外的 32 位是值得的。

    还有一些笔记。

    • 除非我们有充分的理由,否则一切都是not null。这将捕获数据输入错误。它使数据库更易于使用,因为您知道数据会在那里。
    • 同样,任何应该是唯一的都被声明为unique,所以它是有保证的。
    • 对列大小没有任意限制。您可能会看到其他示例,例如 email varchar(64) 等。这实际上并没有为您节省任何空间,它只是限制了数据库中允许的内容。用户名或电子邮件地址的长度没有基本限制,即business rule。业务规则无时无刻不在变化;它们不应该被硬编码到模式中。执行业务规则是输入数据或触发器的工作。

    现在我们有了这三张表,我们可以用它们来记录人们的用餐情况。为此,我们需要第四张表来记录用户在某个位置用餐:一个连接表。连接表允许我们将有关用户、膳食和位置的信息保存在一个规范的位置。用户、位置和餐点由称为外键的 ID 引用。

    create table user_meals (
        id bigserial primary key,
    
        user_id bigint not null references users(id),
        meal_id bigint not null references meals(id),
        location_id bigint not null references locations(id),
    
        -- Using a time range is more flexible than start + duration.
        taken_at tstzrange not null,
    
        -- This can store up to 99999.99 which seems reasonable.
        cost numeric(7, 2) not null,
    
        notes text,
    
        -- When was this entry created?
        created_at timestamp not null default current_timestamp,
        -- When was it last updated?
        updated_at timestamp not null default current_timestamp
    );
    
    • 因为我们使用bigserial 作为所有内容的主键,所以引用其他表很简单:它们都是bigint
    • 和以前一样,除非我们有充分的理由,否则一切都是not null。例如,并非每餐都会有notes
    • 相当标准的created_atupdated_at 字段用于记录条目的创建或更新时间。
    • 避免使用不精确的数字类型,例如 floatdouble,尤其是对于金钱而言。相反,arbitrary precision type numeric 用于精确存储资金。
    • numeric 要求我们给它一个精度和规模;我们必须选择一个限制,所以我选择了一些不合理的高我货币的东西。您的货币可能会有所不同。
    • 我们利用Postgres's range types 存储从用餐开始到用餐结束的时间范围,而不是存储开始时间和用餐时间。这使我们可以使用range functions and operators 来更简单地查询用餐时间。例如,where taken_at @> '2020-02-15 20:00' 查找在 2 月 15 日晚上 8 点吃的所有餐点。

    还有更多工作要做,例如adding indexes for performance,希望这可以帮助您入门。如果有一个外卖,那就是:不要试图把所有东西都塞进一张桌子。

    Try it out.

    【讨论】:

      猜你喜欢
      • 2012-01-01
      • 2023-04-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-11-01
      • 1970-01-01
      • 2015-10-24
      相关资源
      最近更新 更多