如何确定更新语句中导致 "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
我正在使用 MySql 5.5.37。我有一个带有 varchar(32) id
的 tablemysql> 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