将小数组存储为SQL中的多个列

时间:2015-03-26 05:14:19

标签: mysql sql database-design

我和我的一个朋友讨论如何在SQL中存储一个小数组(< 10)的引用。假设有一个播放器类可以在其库存中保存一个 。将其描述为SQL表非常简单:

CREATE TABLE items(
    id        INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    type      VARCHAR(32),
    weight    FLOAT
);
CREATE TABLE players(
    id        INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    name      VARCHAR(128) not null,
    item      INT,
    FOREIGN KEY(item) REFERENCES items(id)
);

现在的问题是:如果播放器可以容纳多个项目,但只有少量固定项目,那么更好将它们存储在附加表格中然后{{1}在他们之上,像这样:

附加表

JOIN

或者只是添加其他列更好? 如果物品数量是动态且无限制的,那么这当然不是一个选择:

多列

CREATE TABLE players(
    id        INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    name      VARCHAR(128) not null
);
CREATE TABLE inventory(
    id        INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    item      INT NOT NULL,
    player    INT NOT NULL,
    FOREIGN KEY(item) REFERENCES items(id),
    FOREIGN KEY(player) REFERENCES players(id)
);

这样做的好处就是不必加入一个非常快速增长的桌子,但如果没有任何玩家一直携带所有四个项目,可能会非常快速地破坏你的桌子。

  • 哪种方式应该首选?
  • 使用多列违反第一范式的第二种方法是什么?
  • 两种方式有哪些优点和缺点?
  • 什么能带来最佳表现?

3 个答案:

答案 0 :(得分:3)

这可能会成为一个很好的面试问题。

  1. 偏好是意见。这取决于。但是,如果我因为很多原因每个玩家有超过2个项目,我会避免使用“多列”技术(技术#2)。首先,如果你为每个玩家设计和编码n = 10个项目,项目经理明天会想要多少项目? n + 1当然。

  2. 我相信“多列”技术是1NF因为数据是原子的(虽然它需要空值)

  3. “许多作家误解了重复小组的概念,并用它来声称某张桌子违反了1NF。”

    https://www.simple-talk.com/sql/learn-sql-server/facts-and-fallacies-about-first-normal-form/

    仅仅因为它的1NF意味着它是一个很好的解决方案。规范化本身并不像应用程序可用性,可维护性和性能那样重要。去标准化是性能的常见做法。

    1. 见下文

    2. 你在解决什么问题?您提供了一种技术,但在您解决问题之前,您无法衡量绩效。如果可能更适合写入而不是读取。

    3. 为应用程序需要回答的问题编写一些示例SQL。对于你的技术#2,我能想到的几乎所有问题都需要使用子选择(或case语句)。这些很难维护,我认为(因此不是'首选')让我们为你的两种技术#1和#2编号。以下是(太多)每个示例SQL解决方案:

      每个玩家中有多少项?

      #1。 Select count(inventory.item) from inventory inner join player = 1

      #2。实际上取决于您的数据库,例如MySQL您可以使用IFNULL(item1,0)并对它们或CASE语句求和。不会尝试编写此代码。

      哪些玩家的商品ID = 9?

      1. select id from players from players inner join inventory on players.id = inventory.player where inventory.item = 9

      2. select id from players where item1=9 or item2=9 or item3=9 ....

      3. 哪些玩家有项目ID X和Y?

        1. select id from players from players inner join inventory on players.id = inventory.player where inventory.item = X or inventory.item = Y;

        2. select id from players where id in (select id from players where item1 = X or item2 = X....) or id in (select id from players where item1 = Y or item2 = Y ...) or ...

        3. 由于物品具有重量,所以玩家具有重量> 1的物品。 10?

          1. select distinct players.* from players inner join inventory on players.id = inventory.player inner join items on inventory.item = items.id where items.weight > 10

          2. select distinct id from players where players.item1 in (select id from items where items.weight > 10) or players.item2 in (select id from items where items.weight > 10) or ...

          3. 注意我没有完成技术#2的SQL。你呢?

            还有很多其他痛苦的SQL例子。哪些球员的总重量最高?删除具有特定ID的所有项目。我不打算回答这些问题;对于我认为的每个案例,技术#2的sql更难维护(对我来说==不是优选的)。

            可能有一些技术可以使这些子选项更简单(参数化视图,应用程序代码中的SQL模板),但这取决于您的平台。

            使用索引进行优化也会产生问题,因为在我看来,您需要在player表中的每个项目列上都有一个索引。

            如果我是正确的,技术#2需要子选择,我听说连接效率更高(Join vs. sub-query

            使用技术#1,(附加表)只需使用触发器或应用程序代码来强制限制每个玩家10个项目的规则。这种规则比所有SELECT

            更容易改变

            我现在应该停下来,但这是你们两个可以争辩的其他事情。如果您的项目没有属性(或很少引用属性),请考虑技术#3:

            单列删减列表

            CREATE TABLE players( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, name VARCHAR(128) not null, items VARCHAR(2048) -- or whatever size you need, or TEXT );

            INSERT INTO PLAYERS (name, items) values ('player 1', 'itemX, itemY, itemZ');

            没有标准化,但谁在乎它是否快速!

答案 1 :(得分:1)

我希望这不是一个家庭作业问题。

在一个小问题的背景下,很难决定走哪条路。它将取决于系统中存在的其他实体以及它们的使用方式。

第一种方法对于较小的数据集更有效,并且更易于维护,但灵活性低于第二种,随着实体数量的增加,规范化程度越高,效率越高。

强烈建议您阅读一些文章或找一本解决数据库规范化的好书。

编辑:这应该是一个不错的开始: http://holowczak.com/database-normalization/

答案 2 :(得分:1)

制作另一张桌子。

是的,使多列违反1NF。你为什么要遵守这条规则? 考虑:

(1)绝对限制是10吗?听起来这是某种游戏(来自“玩家”这个词)所以也许就是这样。但在大多数应用程序中,这种限制往往是“我无法想象任何人有超过......”的变化。几年前我在一个系统上工作过保险,我们必须记录政策所涵盖的员工子女。原设计师决定创建多个字段,child1,child2,... child8。他显然对自己说:“没有人会有超过8个孩子。这将是充足的。”然后我们找到了一个有9个孩子的员工,系统爆炸了。

(2)假设您要测试玩家是否携带某个特定物品。使用两个表,您可以编写类似

的内容
select count(*) from player_item where player_id=@pid and item_id=@iid

如果count> 0,则玩家拥有该项目。有一个表,你必须写

select count(*) from player where player_id=@pid and
  (item1=@iid or item2=@iid or item3=@iid or item4=@iid or item6=@iid or item7=@iid or item8=@iid or item9=@iid or item10=@iid)

即使对于一个简单的“它是平等的”测试,这也是很多额外的代码。你注意到我跳过了item5吗?在反复输入这些重复测试时,这是一个容易犯的错误。相信我:当只有3次重复时,我做过一次。如果所需的值在插槽1或插槽3中,则程序正常工作,但当值在插槽2中时,程序失败。在我们的大多数测试中,我们只放入一个项目,因此它似乎有效。在我们投入生产之前,我们没有抓到那个。

(3)假设您确定10不是正确的限制,并且您想将其更改为12.使用两个表,唯一可以更改的位置是您创建新的代码,以强制执行限制为12而不是10.如果你做得对,那10是一个符号变量,而不是硬编码,所以你改变了一个赋值语句。使用一个表,您必须更改读取该表的每个查询。

(4)说到在表中搜索给定项:使用两个表,可以在item_id上创建索引。一个表,你需要一个关于item1的索引,另一个关于item2的索引,另一个关于item3的索引,等等。系统要维护10个索引而不是1个。

(5)加入将是一场特殊的噩梦。您可能希望显示玩家拥有的所有项目的列表以及项目记录中的某些值,例如名称。有两个表,那是

select item.name from player_item
join item on item.item_id=player_item.item_id where player_id=@pid

有一张桌子,就是

select item1.name, item2.name, item3.name, item4.name, item5.name, item6.name, item7.name, item8.name, item9.name, item10.name 
from player 
left join item item1 on item1.item_id=player.item1 
left join item item2 on item2.item_id=player.item2
left join item item3 on item3.item_id=player.item3
...

等10个连接。如果连接比具有id匹配的简单值更复杂,则必须重复所有列和所有条件10次。呼!如果您以后决定需要更改条件,则必须确保进行十次相同的更改。

(5)你如何处理添加和删除?订单重要吗?就像你使用一个表,并且有4个项目填写,如果#3被删除会发生什么?我们可以在插槽3中放置一个空值吗?或者我们是否需要将值从插槽4向下移动到插槽3然后在插槽4中为空?当我们添加新项目时,它们总是可以结束,还是我们必须将它们置于中间?当我们向用户显示项目列表时,它们是否必须以某种顺序出现?使用两个表,我们可以向查询添加一个简单的“按名称排序”或“按turn_acquired顺序”。使用一个表,您必须在内存中构建一个数组并对它们进行排序。好吧,这不是一件大事,但如果在程序中多次出现会很痛苦。