在项目已存在时更新中间表时出现问题

时间:2017-08-23 17:46:50

标签: php mysql mysqli many-to-many prepared-statement

我使用三表结构来处理多对多关系。我有一个表有人员列表,另一个表有项目列表。有时多个人拥有相同的项目,有时多个项目链接到同一个人,所以我设置了下表结构:

CREATE TABLE people (
id int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
fname varchar(128) NOT NULL,
lname varchar(128) NOT NULL,
);

CREATE TABLE items (
id int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
name varchar(128) NOT NULL UNIQUE,
);

UNIQUE可防止项目名称重复。

CREATE TABLE people_items (
pid int(11) NOT NULL,
iid int(11) NOT NULL,
FOREIGN KEY (pid) REFERENCES people(id)
ON UPDATE CASCADE
ON DELETE CASCADE
FOREIGN KEY (iid) REFERENCES items(id)
ON UPDATE CASCADE
ON DELETE CASCADE
);

这允许我将多个项目链接到多个人,反之亦然。它还允许我从中间表中删除不必要的记录。

只要输入了新项目,所有工作都可以正常工作,但如果输入现有项目,则不会更新中间表,即使人员表格是。我也没有任何错误。

项目是以逗号分隔的文本条目,它们已展开并缩小为$items

首先,我插入任何新项目并检索id:

for ($i=0;$i<count($items);$i++){
$sql="INSERT IGNORE INTO items (name) VALUES (?);";
$stmt=$conn->prepare($sql);
$stmt->bind_param('s',$items[$i]);
$stmt->execute();
$itemid=$stmt->insert_id;

如果返回新的id,则执行以下操作:

if ($itemid){
$sql="INSERT INTO people_items (pid,iid) VALUES (?,?);";
$stmt=$conn->prepare($sql);
$stmt->bind_param('ii',$peopleid,$itemid);//the $peopleid is acquired the same way that the $itemid is acquired above
$stmt->execute();
}

到此为止,一切正常。此时如果现有项已经在items表中,那么我的中间表不会更新。然而,people表更新得很好,而items表不需要更新,因为它已经包含了该项。

这是我尝试两种不同的方法来更新中间表的地方。

首先,我将select和insert查询分开。

elseif(!$itemid){
$sql="SELECT id,name FROM items WHERE name=?;";
$stmt=$conn->prepare($sql);
$stmt->bind_param('s',$items[$i]);
$stmt->execute();
$stmt->store_result();
$stmt->bind_result($iid,$name);
$stmt->fetch();
$sql="INSERT INTO people_items (pid,iid) VALUES (?,?);";
$stmt=$conn->prepare($sql);
$stmt->bind_param('ii',$pid,$iid);
$stmt->execute();
}

这是我的替代方法,它也不会更新中间表:

elseif(!$itemid){
$sql="INSERT INTO people_items (pid, iid) SELECT id,name FROM items WHERE name IN (?);";
$stmt=$conn->prepare($sql);
$stmt->bind_param('s',$items[$i]);
$stmt->execute();
}

我做错了什么?

2 个答案:

答案 0 :(得分:0)

这是因为您正在使用INSERT IGNORE,因此如果该项目已经存在,则不会插入任何内容,您将获得0或上次成功插入的ID作为ID({{3} })。所以你需要检查的是受影响的行数。如果它为0,则忽略它,你可以从数据库中SELECT它。如果它不是0,那么您可以安全地插入最后一个ID。

for ($i=0;$i<count($items);$i++){
    $sql="INSERT IGNORE INTO items (name) VALUES (?);";
    $stmt=$conn->prepare($sql);
    $stmt->bind_param('s',$items[$i]);
    $stmt->execute();
    $itemid=$stmt->insert_id;
    $affected_rows = $stmt->affected_rows;
    if ($affected_rows !== 0) {
        // insert
    }
    else {
        // select and insert
    }
}

旁注:

您在循环中插入项目的方式对于性能来说非常糟糕,特别是如果数据库与服务器不在同一主机中(想到去超市:你去N次并自己获取每个项目) ,或者你只去一次并得到你的N项?)。你应该做的是在一个查询中插入所有元素。很明显,你的ID不是那么明确,因为你没有,但你可以阅读这个per the docs来开始。

答案 1 :(得分:0)

对不起,我没有早点到达这里,但我已经弄清楚了,并且在此期间忙于工作。我想我会提供答案,以防将来有人遇到同样的问题。无论如何,问题在于我的SQL查询。

我有这个:

elseif(!$itemid){
$sql="INSERT INTO people_items (pid, iid) SELECT id,name FROM items WHERE name IN (?);";
$stmt=$conn->prepare($sql);
$stmt->bind_param('s',$items[$i]);
$stmt->execute();
}

我应该有这个:

elseif(!$itemid){
$sql="INSERT INTO people_items (pid,iid) VALUES (?,(SELECT id FROM items WHERE name IN (?)));";
$stmt=$conn->prepare($sql);
$stmt->bind_param('ss',$peopleid,$items[$i]);
$stmt->execute();
}