1

首先,我在这里发帖的借口,通常我觉得我应该能够通过尝试和搜索足够长的时间来找到一个情况的答案......到目前为止,它已经大约 5 个小时的搜索和测试,我无法解释结果我越来越。我现在有点不知所措。如果你们中的任何人可以帮助我,那将不胜感激。

情况

以下是所有现有代码,但我正忙于优化行为。

我使用三张桌子;

  • 第一个表包含项目,
  • 第二个表包含所有项目的属性字段,其中包括名称和默认值
  • 第三个表包含属性字段的字段值,通过 id 链接到项目并通过字段 id 链接到字段

这个想法是让每个项目的所有字段都有一个值。如果值表中不存在字段和项值的行,则应使用默认值。

这一切都必须发生在一个查询中。

我之前的那个人通过确保每次添加一个字段时,都会将一个默认值字段插入到所有项目的值表中,从而“修复”了这个问题。当您的数据库表中可以有超过 10.000 个项目和超过 10 个字段时,这当然是错误的方法

我的测试用例

在这个系统上工作了 2 年多,我终于有时间从现有的权力中解决这个问题。对工作系统的正常测试总是给我所期望的不一致的回报。这是内部测试系统的当前状态:

  • 277 个项目行
  • 3 个字段行
  • 824 个值行

这是在我通过执行一次性清理查询来清理系统上不再存在的项目和字段的值之后(是的,这部分是错误的):

DELETE FROM values WHERE item_id NOT IN (SELECT id FROM items) OR field_id NOT IN (SELECT id FROM fields);

我还制作了一个具有最低要求的虚拟系统,因为原始表包含更多字段:

-- table a represents items
CREATE TABLE IF NOT EXISTS `a` (
  `id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `a` (`id`) VALUES (1),(2),(3);

-- table b represents fields
CREATE TABLE IF NOT EXISTS `b` (
  `id` int(11) NOT NULL,
  `default` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `b` (`id`, `default`) VALUES
(1, 4),
(2, 5),
(3, 6),
(4, 11),
(5, 12);

-- table c represents values
CREATE TABLE IF NOT EXISTS `c` (
  `id` int(11) NOT NULL,
  `a_id` int(11) NOT NULL,
  `b_id` int(11) NOT NULL,
  `value` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `c` (`id`, `a_id`, `b_id`, `value`) VALUES
(1, 1, 1, 7),
(2, 1, 2, 8),
(3, 2, 3, 9),
(4, 2, 1, 7),
(5, 3, 2, 8),
(6, 3, 3, 9),
(7, 1, 5, 13);

预期结果应为 831 行(277 个项目 * 3 个字段),其中值表中不可用的项目/字段组合应填充字段默认值,而不是值表中的值。

我在为检查我的发现而制作的虚拟系统上尝试的成功测试用例 SQL 返回了我的预期:

SELECT  a.id,
    b.id, 
    IF(c.value IS NOT NULL, c.value, b.default) as t_value
FROM a
join b
LEFT JOIN c on c.a_id = a.id AND c.b_id = b.id

这将返回 15 行(3 a(项目)x 5 b(字段)),包含所有预期数据

在更改内部测试系统的查询时,它应该可以工作。这是我发送的 SQL:

SELECT items.id AS item_id, fields.id AS field_id, IF(values.value IS NULL, fields.default_value, value.value) AS field_value
FROM        items
JOIN        fields
LEFT JOIN   values ON values.item_id = item.id AND values.field_id = fields.id

...但它返回 1104 行而不是预期的 831 行。这些表已经清除了不准确的数据,并且我没有考虑到的额外字段根本没有在 SQL 中使用,而且抽象测试已经证明了查询概念。只有实际情况不断失败。

如果有人能指出我的错误,那将不胜感激。此处的表名已重命名,但根据要求,我还可以发布相关内部测试系统表的部分转储。上面的例子应该是准确的。

4

1 回答 1

1

我没有看到任何提及您检查唯一性的内容。您的“额外行”可能是重复的。

SELECT a.id FROM a GROUP BY a.id HAVING COUNT(1) > 1 ;

SELECT b.id FROM b GROUP BY b.id HAVING COUNT(1) > 1 ;

SELECT c.a_id, c.b_id FROM c GROUP BY c.a_id, c.b_id HAVING COUNT(1) > 1 ;

SELECT s.a_id, s.b_id
  FROM
       (
        your query here
       ) s
 GROUP BY s.a_id, s.b_id 
HAVING COUNT(1) > 1 ;

我们希望和中的id列是唯一且非空的。ab

我们还期望(a_id,b_id)inc是唯一且非空的。如果您创建唯一索引或声明 UNIQUE KEY 约束,数据库可以强制执行此约束:

ALTER TABLE c ADD CONSTRAINT c_ux UNIQUE KEY (a_id,b_id);

-或者-

CREATE UNIQUE INDEX c_ux ON c (a_id, b_id); 

如果没有唯一性保证,您的查询可能会返回“重复”a.idb.id对。

添加到您的查询中的“ GROUP BY”子句可用于消除重复项,但这似乎更像是杂牌。(给定一个特定的项目和一个特定的字段,您将允许存储多少个不同的值?当您拉回这些值时,您真正想要返回哪些值?)


通常,我们希望id每个表中的列都可以定义为 PRIMARY KEY:

ALTER TABLE a ADD PRIMARY KEY (id);
ALTER TABLE b ADD PRIMARY KEY (id);
ALTER TABLE c ADD PRIMARY KEY (id);

我们还期望(使用 InnnoDB 表)定义外键:

ALTER TABLE c ADD CONSTRAINT FK_c_a (a_id) REFERENCES a (id) ;
ALTER TABLE c ADD CONSTRAINT FK_c_b (b_id) REFERENCES b (id) ;

我的偏好是CROSS在 a 和 b 之间的连接中包含关键字,尽管这对查询的作用没有任何影响。它只是作为文档说明没有 ON 子句是故意的,并且我打算使用笛卡尔积。


更新:

有时,像这样的 EAV 模型旨在保存以前的值以及当前值。在这种情况下,通常有一个“有效日期”和/或“取代日期”和/或一个简单的“活动”标志,可用于计算当前值。因此,重复可能是垃圾,来自糟糕的实现,或者“重复”可能是故意的。

在这种情况下,唯一键可能类似于(a_id, b_id, effective_date).

处理 EAV 模型中的历史(“时间”)值的查询可能是相当复杂的,而不是为了装腔作势;但这是可能的。

另一种可能性是某些字段旨在“多值”。即实体的重复属性。例如,一个项目可能有多个“关键字”或“标签”值。“霍比特人”可能具有“高帧率”、“幻想”、“极度痛苦”的“标签”值。但是,如果不了解数据库设计,我们就无法真正分辨出我们所处的位置。

但是,如果数据库被设计为仅保存一个字段的单个“当前”值(无法查看历史值),那么我同意重复的(a_id,b_id)行可能(可能)是垃圾。在开始删除任何内容之前,我可能会保存表中所有行的副本(到单独的“保存”表中)。

于 2013-01-18T13:59:48.640 回答