MySQL ON DUPLICATE KEY UPDATE,唯一键中的可空列

rya*_*nki 14 mysql nullable summarization

我们的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`)
);
Run Code Online (Sandbox Code Playgroud)

"摘要"表,其中包含活动表中会话总数的每日汇总,d表示包含事务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;
Run Code Online (Sandbox Code Playgroud)

实际的摘要查询类似于以下内容,计算会话数和事务数,然后按广告系列和(可选)过滤器进行分组.

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`)
;
Run Code Online (Sandbox Code Playgroud)

一切都很好,除了filter_id为NULL的情况的摘要.在这些情况下,ON DUPLICATE KEY UPDATE子句与现有行不匹配,每次都会写入一个新行.这是因为"NULL!= NULL".但是,在比较唯一键时,我们需要的是"NULL = NULL".

我正在寻找有关变通方法的建议或对我们迄今为止提出的建议的反馈.到目前为止我们已经考虑过的变通方法如下.

  1. 在运行摘要之前,删除包含NULL键值的所有摘要条目.(这就是我们现在正在做的事情)如果在汇总过程中执行查询,这会导致返回带有缺少数据的结果的负面影响.

  2. 将DEFAULT NULL列更改为DEFAULT 0,这允许UNIQUE KEY一致地匹配.这会产生负面的副作用,使查询对摘要表的开发过于复杂.它迫使我们使用大量"CASE filter_id = 0 THEN NULL ELSE filter_id END",并且因为所有其他表都具有filter_id的实际NULL,所以难以加入.

  3. 创建一个返回"CASE filter_id = 0 THEN NULL ELSE filter_id END"的视图,并直接使用此视图而不是表格.摘要表包含几十万行,我被告知视图性能很差.

  4. 允许创建重复条目,并在汇总完成后删除旧条目.有类似的问题提前删除它们.

  5. 添加一个包含0表示NULL的代理列,并在UNIQUE KEY中使用该代理(实际上,如果所有列都是NOT NULL,我们可以使用PRIMARY KEY).
    这个解决方案似乎是合理的,除了上面的例子只是一个例子; 实际的数据库包含六个汇总表,其中一个汇总表包含UNIQUE KEY中的四个可为空的列.有人担心开销太大.

您是否有更好的解决方法,表格结构,更新过程或MySQL最佳实践可以提供帮助?

编辑:澄清"无效的意思"

包含NULL列的摘要行中的数据仅在汇总报告中作为单个"全部捕获"行的意义上被认为属于一起,总结了该数据点不存在或未知的那些项.因此,在摘要表本身的上下文中,含义是"没有值已知的那些条目的总和".另一方面,在关系表中,这些确实是NULL结果.

将它们放入摘要表中的唯一键的唯一原因是在重新计算摘要报告时允许自动更新(通过ON DUPLICATE KEY UPDATE).

也许更好的方式来描述它是通过具体的例子,其中一个汇总表按照受访者给出的商业地址的邮政编码前缀在地理上对结果进行分组.并非所有受访者都提供了业务地址,因此事务和地址表之间的关系非常正确.在此数据的摘要表中,为每个邮政编码前缀生成一行,其中包含该区域内的数据摘要.生成另一行以显示未知邮政编码前缀的数据摘要.

将其余数据表更改为具有明确的"THERE_IS_NO_ZIP_CODE"0值,并在ZipCodePrefix表中放置表示此值的特殊记录是不合适的 - 该关系确实为NULL.

der*_*ert 4

我认为类似 (2) 的东西确实是最好的选择 \xe2\x80\x94 或者,至少,如果你从头开始的话。在SQL中,NULL表示未知。如果你想要一些其他的含义,你真的应该使用一个特殊的值,而 0 肯定是一个不错的选择。

\n\n

您应该在整个数据库中执行此操作,而不仅仅是这一表。那么你就不应该遇到奇怪的特殊情况。事实上,您应该能够删除当前的许多行(例如:当前,如果您想要没有过滤器的摘要行,则有特殊情况“过滤器为空”,而不是正常情况“过滤器=?”。)

\n\n

您还应该继续在引用的表中创建一个“不存在”条目,以保持 FK 约束有效(并避免特殊情况)。

\n\n

PS:没有主键的表不是关系表,应该避免。

\n\n

编辑1

\n\n

嗯,在这种情况下,您真的需要重复密钥更新吗?如果您正在执行 INSERT ... SELECT,那么您可能会这样做。但是,如果您的应用程序提供数据,只需手动执行 \xe2\x80\x94 执行更新(映射zip = nullzip is null),检查更改了多少行(MySQL 返回此数据),如果 0 则执行插入。

\n