如何确定更新语句中导致 "Cannot add or update a child row" 的值?

How do I identify what value is cauasing a "Cannot add or update a child row" in my update statement?

我正在使用 MySql 5.5.37。我有一个带有 varchar(32) id

的 table
mysql> desc grade;
+-------------+-------------+------+-----+---------+-------+
| Field       | Type        | Null | Key | Default | Extra |
+-------------+-------------+------+-----+---------+-------+
| ID          | varchar(32) | NO   | PRI |         |       |
| NAME        | varchar(20) | NO   |     | NULL    |       |
| GRADE_ORDER | int(11)     | NO   |     | NULL    |       |
+-------------+-------------+------+-----+---------+-------+

我正在尝试更新链接到上述 ID 列的列,但我收到一条错误消息,抱怨找不到匹配值...

mysql> update resource r set grade_id = convert(substring_index(substring_index(r.description, 'Grade ', -1), ' ', 1), unsigned integer) where r.description like '% Grade%';
ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`my_db`.`resource`, CONSTRAINT `FK3_RESOURCE` FOREIGN KEY (`GRADE_ID`) REFERENCES `grade` (`ID`))

如何确定违规值是多少?我注意到我拥有的资源数量与我能够在我的两个 table 之间进行的交叉连接数量相匹配 ...

mysql> select count(*) FROM (select convert(substring_index(substring_index(r.description, 'Grade ', -1), ' ', 1), unsigned integer) from resource r where r.description like '% Grade%') q;
+----------+
| count(*) |
+----------+
|     1340 |
+----------+
1 row in set, 7 warnings (0.01 sec)

mysql> select count(*) from resource r, grade g where convert(substring_index(substring_index(r.description, 'Grade ', -1), ' ', 1), unsigned integer) = g.id and r.description like '% Grade%'  ;
+----------+
| count(*) |
+----------+
|     1340 |
+----------+
1 row in set, 1431 warnings (0.02 sec)

所以如果一切都匹配,我如何找出更新失败的原因?

编辑: 所有 table 的所有主键都是 VARCHAR(32)

类型

写一个SELECT语句,return表达式returns "new value" for grade_id.

 SELECT r.description
      , SUBSTRING_INDEX(SUBSTRING_INDEX(r.description, 'Grade ',-1),' ',1) AS foo 
   FROM resource r
  WHERE r.description like '% Grade%'

要测试哪些新的 grade_id 值与 grade 中的 id 不匹配,我们可以使用 anti-join 图案。这是 return 来自 resource 的行以及来自成绩的匹配行的外部连接,然后排除所有匹配的行。在 grade.

中没有匹配行的地方保留 resource 中的行
 SELECT r.description
      , SUBSTRING_INDEX(SUBSTRING_INDEX(r.description, 'Grade ',-1),' ',1) AS foo 
      , CONVERT(SUBSTRING_INDEX(SUBSTRING_INDEX(r.description, 'Grade ',-1),' ',1),UNSIGNED) AS fu
   FROM resource r
     -- anti-join exclude matching rows
   LEFT
   JOIN grade g
     ON g.id = CONVERT(SUBSTRING_INDEX(SUBSTRING_INDEX(r.description, 'Grade ',-1),' ',1),UNSIGNED)
  WHERE g.id IS NULL
    AND r.description like '% Grade%'

g.id 上的相等性比较保证了匹配行的表达式不会为 NULL。如果我们排除 g.id 的所有非 NULL 值,则排除所有匹配的行。


最可能的解释是描述列中出现了意外的数据,或者可能是 SUBSTRING_INDEX 函数在未找到匹配项时的意外行为。

跟进

如果该查询成功执行并且 return 零行,则每个新的 grade_id 值都与 grade 中的 id 相匹配。

所以问题在于为什么 UPDATE 语句会引发外键约束冲突。

我们需要排除与 grade_id 混淆的 BEFORE UPDATE ON resource 触发器。一旦我们排除了这种可能性...

可能是隐式数据类型转换导致了这个问题。在进行比较之前,让我们将该整数值转换为字符串。与上面相同的查询,但带有 CONVERT( expr ,CHAR(32)) 包装器。

 SELECT r.description
      , SUBSTRING_INDEX(SUBSTRING_INDEX(r.description, 'Grade ',-1),' ',1) AS foo 
      , CONVERT(
          CONVERT(
            SUBSTRING_INDEX(SUBSTRING_INDEX(r.description, 'Grade ',-1),' ',1)
          ,UNSIGNED)
        ,CHAR(32)) AS new_grade_id
   FROM resource r
     -- anti-join exclude matching rows
   LEFT
   JOIN grade g
     ON g.id
      = CONVERT(
          CONVERT(
            SUBSTRING_INDEX(SUBSTRING_INDEX(r.description, 'Grade ',-1),' ',1)
          ,UNSIGNED)
        ,CHAR(32))
  WHERE g.id IS NULL
    AND r.description like '% Grade%'

如果那仍然是 return 零行,那么我们可能会怀疑操作顺序问题。就像要分配的新 grade_id 值正在检查 应用 WHERE 子句之前。但这没有意义。 (如果是这样,我会感到非常惊讶。)

作为测试,我将编写一个查询,将 return 的旧 grade_id 值和要分配给 grade_id 列的新值。假设 resource 有一个 id 列作为主键:

 SELECT r.id
      , r.description
      , r.grade_id   AS old_grade_id
      , CONVERT(
          CONVERT(
            SUBSTRING_INDEX(SUBSTRING_INDEX(r.description, 'Grade ',-1),' ',1)
          ,UNSIGNED)
        ,CHAR(32))   AS new_grade_id
   FROM resource r
  WHERE r.description like '% Grade%'

如果该查询是 return 我们期望的结果,我们可以将其引用为多 table 更新语句中的内联视图,并在 id 上进行连接列。

像这样:

 UPDATE resource t
   JOIN ( SELECT r.id
               , r.description
               , r.grade_id   AS old_grade_id
              , CONVERT(
                  CONVERT(
                    SUBSTRING_INDEX(SUBSTRING_INDEX(r.description, 'Grade ',-1),' ',1)
                  ,UNSIGNED)
                ,CHAR(32))   AS new_grade_id
           FROM resource r
          WHERE r.description like '% Grade%'
        ) v
     ON v.id = t.id
    SET t.grade_id = v.new_grade_id

您正在将 grade_id 值转换为无符号整数。它应该是 varchar(32)。这是否也失败了:

update resource r set grade_id = convert(substring_index(substring_index(r.description, 'Grade ', -1), ' ', 1), char(32)) where r.description like '% Grade%';

由于自动类型转换,您的测试查询有效。 https://dev.mysql.com/doc/refman/5.7/en/type-conversion.html