首先,我在这里发帖的借口,通常我觉得我应该能够通过尝试和搜索足够长的时间来找到答案...到目前为止,它已经进行了大约5个小时的搜索和测试,而且我不能解释我得到的结果。我此刻有点智慧。如果你们中的任何人能够帮助我,那将非常感激。
情况
以下是所有现有代码,但我忙于优化行为。
我使用三张桌子;
我们的想法是为每个项目的所有字段添加一个值。如果值表中不存在字段和项值的行,则应使用默认值。
这一切都必须在一个查询中发生。
我之前的那个人通过确保每次添加字段时“修复”此问题,将默认值字段插入到所有项目的值表中。当你在数据库表中拥有超过10,000个项目和超过10个字段时,这当然是错误的方法
我的测试用例
在这个系统上工作了两年多,我终于有时间从权力来解决这个问题了。对工作系统的正常测试总是给出我期望的不一致的回报。这是内部测试系统的当前状态:
这是在我通过执行一次仅查询清理后清除系统上不再存在的项目和字段的值(是的,这部分是错误的):
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中使用,加上抽象测试已经证明了查询的概念。只有实际情况才会失败。
如果有人能够指出我的错误,那将非常感激。此处的表名已重命名,但根据请求,我还可以发布有问题的内部测试系统表的部分转储。上面的例子应该是准确的。
答案 0 :(得分: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
和a
中的b
列是唯一且非空的。
我们还希望(a_id,b_id)
中的c
是唯一且非空的。如果您创建唯一索引或声明UNIQUE KEY约束,则数据库可以强制执行此约束:
ALTER TABLE c ADD CONSTRAINT c_ux UNIQUE KEY (a_id,b_id);
-OR -
CREATE UNIQUE INDEX c_ux ON c (a_id, b_id);
缺少唯一性保证,您的查询可能会返回“重复”a.id
和b.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) ;
我的偏好是在a和b之间的连接中包含CROSS
关键字,尽管这对查询的作用没有任何影响。它只是作为文件证明没有ON条款是故意的,我打算用笛卡尔积。
有时,像这样的EAV模型旨在保存以前的值以及当前值。在这种情况下,通常会有一个“effective_date”和/或“被取代的日期”和/或一个简单的“活动”标志,可用于计算当前值。因此,重复可能是垃圾,来自不良的实施,或者“重复”可能是有意的。
在这种情况下,唯一键可能类似于(a_id, b_id, effective_date)
。
在EAV模型中处理历史(“时间”)值的查询可能是相当复杂的,并不适用于虚假的虚假;但这是可能的。
另一种可能性是某些字段旨在“多值”。也就是说,实体的重复属性。例如,某个商品可能包含多个“关键字”或“代码”值。 “霍比特人”可能具有“高帧率”,“幻想”,“难以忍受”的“标记”值。但是,如果不了解数据库设计,我们无法确定我们所处的位置。
但是,如果设计数据库只保留一个字段的“当前”值(无法查看历史值),那么我同意可能(可能)重复的(a_id,b_id)
行垃圾。在开始删除任何内容之前,我可能会保存表中所有行的副本(放入单独的“保存”表)。