我们的 MySQL 网络分析数据库包含一个汇总表,该汇总表在导入新活动时全天更新。我们使用 ON DUPLICATE KEY UPDATE 以便汇总覆盖之前的计算,但由于汇总表的 UNIQUE KEY 中的列之一是可选的 FK,并且包含 NULL 值,因此遇到了困难。
这些 NULL 旨在表示“不存在,并且所有此类情况都是等效的”。当然,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;
实际的汇总查询如下所示,计算会话和事务的数量,然后按活动和(可选)过滤器分组。
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`)
;
一切都很好,除了 filter_id 为 NULL 的情况的摘要。在这些情况下,ON DUPLICATE KEY UPDATE 子句与现有行不匹配,并且每次都会写入新行。这是由于“NULL!= NULL”这一事实。然而,在比较唯一键时,我们需要的是“NULL = NULL”。
我正在寻找解决方法的想法或对我们迄今为止提出的问题的反馈。到目前为止,我们已经想到的解决方法如下。
在运行汇总之前删除所有包含 NULL 键值的汇总条目。(这就是我们现在正在做的事情)如果在汇总过程中执行查询,这会产生负面影响,即返回缺少数据的结果。
将 DEFAULT NULL 列更改为 DEFAULT 0,这允许 UNIQUE KEY 一致地匹配。这具有使针对汇总表的查询开发过于复杂的负面影响。它迫使我们使用大量的“CASE filter_id = 0 THEN NULL ELSE filter_id END”,并且由于所有其他表的 filter_id 都具有实际的 NULL,因此连接起来很尴尬。
创建一个返回“CASE filter_id = 0 THEN NULL ELSE filter_id END”的视图,并直接使用此视图而不是表。汇总表包含几十万行,我被告知视图性能很差。
允许创建重复条目,并在汇总完成后删除旧条目。与提前删除它们有类似的问题。
添加一个包含 0 表示 NULL 的代理列,并在 UNIQUE KEY 中使用该代理项(实际上,如果所有列都不是 NULL,我们可以使用 PRIMARY KEY)。
这个解决方案似乎是合理的,只是上面的例子只是一个例子;实际的数据库包含六个汇总表,其中一个在 UNIQUE KEY 中包含四个可为空的列。有些人担心开销太大。
您是否有更好的解决方法、表结构、更新过程或 MySQL 最佳实践可以提供帮助?
编辑:澄清“空的含义”
包含 NULL 列的汇总行中的数据仅在汇总报告中作为单个“包罗万象”行的意义上被认为属于一起,汇总了该数据点不存在或未知的那些项目。因此,在汇总表本身的上下文中,含义是“那些不知道值的条目的总和”。另一方面,在关系表中,这些确实是 NULL 结果。
将它们放入汇总表上的唯一键的唯一原因是在重新计算汇总报告时允许自动更新(通过 ON DUPLICATE KEY UPDATE)。
也许更好的描述方式是通过具体示例,其中一个汇总表按地理区域按受访者提供的公司地址的邮政编码前缀分组结果。并非所有受访者都提供了营业地址,因此事务和地址表之间的关系是非常正确的 NULL。在此数据的汇总表中,为每个邮政编码前缀生成一行,其中包含该区域内的数据汇总。将生成一个附加行来显示不知道邮政编码前缀的数据的摘要。
将其余数据表更改为具有显式“THERE_IS_NO_ZIP_CODE”0 值,并在 ZipCodePrefix 表中放置一个表示该值的特殊记录是不正确的——这种关系确实是 NULL。