我可以控制在UPDATE中使用哪个JOINed行吗?

时间:2018-01-22 19:41:39

标签: mysql

曾几何时,我有一张这样的桌子:

CREATE TABLE `Events` (
  `EvtId`   INT UNSIGNED NOT NULL AUTO_INCREMENT,
  `AlarmId` INT UNSIGNED,
  -- Other fields omitted for brevity

  PRIMARY KEY (`EvtId`)
);

AlarmId被允许为NULL

现在,因为我想从每个事件的零或一个警报扩展到每个事件的零或多个警报,所以在软件更新中,我正在更改我的数据库实例,而不是:

CREATE TABLE `Events` (
  `EvtId`   INT UNSIGNED NOT NULL AUTO_INCREMENT,
  -- Other fields omitted for brevity

  PRIMARY KEY (`EvtId`)
);

CREATE TABLE `EventAlarms` (
  `EvtId`   INT UNSIGNED NOT NULL,
  `AlarmId` INT UNSIGNED NOT NULL,

  PRIMARY KEY (`EvtId`, `AlarmId`),
  CONSTRAINT `fk_evt` FOREIGN KEY (`EvtId`) REFERENCES `Events` (`EvtId`)
     ON DELETE CASCADE ON UPDATE CASCADE 
);

到目前为止一切顺利。

数据也很容易迁移:

INSERT INTO `EventAlarms`
  SELECT `EvtId`, `AlarmId` FROM `Events` WHERE `AlarmId` IS NOT NULL;

ALTER TABLE `Events` DROP COLUMN `AlarmId`;

事情是,我的系统要求降级也是可能的。我接受降级有时会在数据方面有损,这没关系。但是,它们确实需要在可能的情况下工作,并导致较旧的数据库结构,同时尽可能地保留尽可能多的原始数据。

在这种情况下,这意味着每个事件从零或多个警报到每个事件的零或一个警报。我可以这样做:

ALTER TABLE `Events` ADD COLUMN `AlarmId` INT UNSIGNED;

UPDATE `Events`
  LEFT JOIN `EventAlarms` USING(`EvtId`)
  SET `Events`.`AlarmId` = `EventAlarms`.`AlarmId`;

DROP TABLE `EventAlarms`;

...这很好,因为我并不关心哪一个被保留(这是最好的努力,记住)。但是,正如所警告的那样,这对复制不利,因为结果可能无法预测:

> SHOW WARNINGS;
Unsafe statement written to the binary log using statement format since
BINLOG_FORMAT = STATEMENT. Statements writing to a table with an auto-
increment column after selecting from another table are unsafe because the
order in which rows are retrieved determines what (if any) rows will be
written. This order cannot be predicted and may differ on master and the
slave.

有没有办法以某种方式“命令”或“限制”更新中的联接,或者我是否只是跳过整个企业并停止尝试变得聪明?如果是后者,我如何将降级的AlarmId保留为NULL iff 新表格中有多行我们无法安全区分?如果只有一个,我确实希望迁移AlarmId

由于降级是“一次性”维护操作,因此不必完全是实时的,但速度会很好。两个表都可能有数千行。

(CentOS 7上的MariaDB 5.5.56,但也必须适用于CentOS 6的任何附带产品。)

2 个答案:

答案 0 :(得分:1)

首先,我们可以使用自联接进行一些分析:

SELECT `A`.`EvtId`, COUNT(`B`.`EvtId`) AS `N`
FROM `EventAlarms` AS `A`
LEFT JOIN `EventAlarms` AS `B` ON (`A`.`EvtId` = `B`.`EvtId`)
GROUP BY `B`.`EvtId`

结果将如下所示:

EvtId       N
--------------
370         1
371         1
372         4
379         1
380         1
382        16
383         1
384         1

现在,如果您愿意,可以删除代表映射到多个警报的事件的所有行(您建议作为后备解决方案;我认为这是有道理的,尽管您可以修改以下内容以留下其中一个如果你真的想要的话。)

而不是实际DELETE任何东西,更容易引入一个新表,使用上面显示的自联接查询填充:

CREATE TEMPORARY TABLE `_migrate` (
   `EvtId` INT UNSIGNED,
   `n` INT UNSIGNED,

   PRIMARY KEY (`EvtId`),
   KEY `idx_n` (`n`)
);

INSERT INTO `_migrate`
   SELECT `A`.`EvtId`, COUNT(`B`.`EvtId`) AS `n`
   FROM `EventAlarms` AS `A`
   LEFT JOIN `EventAlarms` AS `B` ON(`A`.`EvtId` = `B`.`EvtId`)
   GROUP BY `B`.`EvtId`;

然后您的更新变为:

UPDATE `Events`
   LEFT JOIN `_migrate` ON (`Events`.`EvtId` = `_migrate`.`EvtId` AND `_migrate`.`n` = 1)
   LEFT JOIN `EventAlarms` ON (`_migrate`.`EvtId` = `EventAlarms`.`EvtId`)
   SET `Events`.`AlarmId` = `EventAlarms`.`AlarmId`
   WHERE `EventAlarms`.`AlarmId` IS NOT NULL

最后,自己清理一下:

DROP TABLE `_migrate`;
DROP TABLE `EventAlarms`;

MySQL仍然发出与以前相同的警告,但是因为知道最多只有一个值将从源表中提取,我们基本上可以忽略它。

它应该是合理有效的,正如我们从等效的EXPLAIN SELECT

中可以看出的那样
EXPLAIN SELECT `Events`.`EvtId` FROM `Events`
   LEFT JOIN `_migrate` ON (`Events`.`EvtId` = `_migrate`.`EvtId` AND `_migrate`.`n` = 1)
   LEFT JOIN `EventAlarms` ON (`_migrate`.`EvtId` = `EventAlarms`.`EvtId`)
   WHERE `EventAlarms`.`AlarmId` IS NOT NULL

id  select_type table       type   possible_keys      key     key_len ref               rows Extra
---------------------------------------------------------------------------------------------------------------------
1   SIMPLE      _migrate    ref    PRIMARY,idx_n      idx_n   5       const             6    Using index
1   SIMPLE      EventAlarms ref    PRIMARY,fk_AlarmId PRIMARY 8       db._migrate.EvtId 1    Using where; Using index
1   SIMPLE      Events      eq_ref PRIMARY            PRIMARY 8       db._migrate.EvtId 1    Using where; Using index

答案 1 :(得分:0)

使用子查询和用户变量只选择一个EventAlarms

在您的更新中,而不是@(Html.Kendo().Grid<ViewModels.AnimalViewModel>() .Name("animalGrid") .Columns(columns => { columns.Bound(c => c.Animals).ClientTemplate("#=printItems(Animals)#"); }) .DataSource(dataSource => dataSource .Ajax() .Read(read => read.Action("ReadAnimalData", "Animal")) ) ) <script type="text/javascript"> function printItems(Names) { var result = ""; var j = Names.length; for (var i = 0; i < j; i++) { result = Names[i]; } return result; } 使用

EventAlarms