MySQL在重复密钥更新中使用唯一密钥中的可空列
我们的MySQL web analytics数据库包含一个汇总表,随着新活动的导入,该汇总表会在一天中不断更新。我们使用ON DUPLICATE KEY UPDATE以使汇总覆盖早期的计算,但由于汇总表的唯一键中的一列是可选的FK,并且包含空值,因此很难实现 这些空的意思是“不存在,所有这些情况都是等效的”。当然,MySQL通常将null视为“未知”,并且所有这些情况都不是等价的 基本结构如下: 一个“活动”表,包含每个会话的一个条目,每个条目都属于一个活动,对于某些条目有可选的筛选器和事务IDMySQL在重复密钥更新中使用唯一密钥中的可空列,mysql,nullable,summarization,Mysql,Nullable,Summarization,我们的MySQL web analytics数据库包含一个汇总表,随着新活动的导入,该汇总表会在一天中不断更新。我们使用ON DUPLICATE KEY UPDATE以使汇总覆盖早期的计算,但由于汇总表的唯一键中的一列是可选的FK,并且包含空值,因此很难实现 这些空的意思是“不存在,所有这些情况都是等效的”。当然,MySQL通常将null视为“未知”,并且所有这些情况都不是等价的 基本结构如下: 一个“活动”表,包含每个会话的一个条目,每个条目都属于一个活动,对于某些条目有可选的筛选器和事务ID
CREATE TABLE `Activity` (
`session_id` INTEGER AUTO_INCREMENT
, `campaign_id` INTEGER NOT NULL
, `filter_id` INTEGER DEFAULT NULL
, `transaction_id` INTEGER DEFAULT NULL
, PRIMARY KEY (`session_id`)
);
“摘要”表,包含活动表中会话总数的每日汇总,以及包含事务ID的会话总数。这些摘要被拆分,每个活动和(可选)筛选器组合一个摘要。这是一个使用MyISAM的非事务表
CREATE TABLE `Summary` (
`day` DATE NOT NULL
, `campaign_id` INTEGER NOT NULL
, `filter_id` INTEGER DEFAULT NULL
, `sessions` INTEGER UNSIGNED DEFAULT NULL
, `transactions` INTEGER UNSIGNED DEFAULT NULL
, UNIQUE KEY (`day`, `campaign_id`, `filter_id`)
) ENGINE=MyISAM;
实际的摘要查询如下所示,计算会话和事务的数量,然后按活动和(可选)过滤器分组
一切都很好,除了过滤器id为空的情况的摘要。在这些情况下,ON DUPLICATE KEY UPDATE子句与现有行不匹配,并且每次都会写入一个新行。这是由于“NULL!=NULL”这一事实造成的。但是,在比较唯一键时,我们需要的是“NULL=NULL”
我正在寻找解决方法的想法,或是我们迄今为止提出的建议的反馈。到目前为止,我们已经想到的解决办法如下
这个解决方案似乎是合理的,除了上面的例子只是一个例子;实际数据库包含六个摘要表,其中一个在唯一键中包含四个可为空的列。有些人担心开销太大
将其余的数据表更改为显式的“THERE_IS_NO_ZIP_CODE”0值,并在ZipCodePrefix表中放置一条表示该值的特殊记录,这是不正确的——这种关系实际上是空的。我认为(2)中的某些内容确实是最好的选择——或者至少,如果你是从零开始的话。在SQL中,NULL表示未知。如果你想要一些其他的含义,你真的应该使用一个特殊的值,0当然是一个不错的选择 您应该在整个数据库中执行此操作,而不仅仅是在这个表中。那你就不应该有奇怪的特殊情况了。事实上,您应该能够去掉很多当前的筛选器(例如:当前,如果您想要没有筛选器的摘要行,则使用特殊情况“filter is null”,而不是正常情况下的“filter=?”) 您还应该继续在引用的表中创建一个“notpresent”条目,以保持FK约束有效(并避免特殊情况) PS:没有主键的表不是关系表,应该避免使用 编辑1 嗯,在这种情况下,你真的需要重复密钥更新吗?如果你在做插入。。。选择,然后您可能会选择。但是如果你的应用程序正在提供数据,只需手工操作-进行更新(映射)
INSERT INTO `Summary`
(`day`, `campaign_id`, `filter_id`, `sessions`, `transactions`)
SELECT `day`, `campaign_id`, `filter_id
, COUNT(`session_id`) AS `sessions`
, COUNT(`transaction_id` IS NOT NULL) AS `transactions`
FROM Activity
GROUP BY `day`, `campaign_id`, `filter_id`
ON DUPLICATE KEY UPDATE
`sessions` = VALUES(`sessions`)
, `transactions` = VALUES(`transactions`)
;
CREATE TABLE IF NOT EXISTS bar (
id INT PRIMARY KEY AUTO_INCREMENT,
datebin DATE NOT NULL,
baz1_id INT DEFAULT NULL,
vbaz1_id INT AS (COALESCE(baz1_id, -1)) STORED,
baz2_id INT DEFAULT NULL,
vbaz2_id INT AS (COALESCE(baz2_id, -1)) STORED,
blam DOUBLE NOT NULL,
UNIQUE(datebin, vbaz1_id, vbaz2_id)
);
INSERT INTO bar (datebin, baz1_id, baz2_id, blam)
VALUES ('2016-06-01', null, null, 777)
ON DUPLICATE KEY UPDATE
blam = VALUES(blam);
UPDATE `Summary` s
LEFT JOIN `Activity` a
ON s.`campaign_id` = a.`campaign_id`
SET s.`sessions` = a.COUNT(`session_id`) ,
SET s.`transactions` = a.COUNT(`transaction_id` IS NOT NULL)
WHERE s.`day` = a.`day`
AND s.`campaign_id` = a.`campaign_id`
AND s.`filter_id` IS NULL
AND a.`filter_id` IS NULL;
INSERT INTO `Summary`
(`day`, `campaign_id`, `filter_id`, `sessions`, `transactions`)
SELECT `day`, `campaign_id`, `filter_id`
, COUNT(`session_id`) AS `sessions`
, COUNT(`transaction_id` IS NOT NULL) AS `transactions`
FROM Activity
WHERE `filter_id` IS NOT NULL
GROUP BY `day`, `campaign_id`, `filter_id`
ON DUPLICATE KEY UPDATE
`sessions` = VALUES(`sessions`)
, `transactions` = VALUES(`transactions`);