【问题标题】:Having an issue updating an intermediate table when an item already exists当项目已存在时更新中间表时出现问题
【发布时间】:2017-08-23 17:46:49
【问题描述】:

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

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();
}

到这里为止,一切正常。此时,如果项目表中已经存在现有项目,则我的中间表不会更新。但是 people 表更新得很好,而 items 表不需要更新,因为它已经包含了项目。

在这里我尝试了两种不同的方法来更新中间表。

首先我将选择和插入查询分开。

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();
}

我做错了什么?

【问题讨论】:

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


    【解决方案1】:

    因为你使用的是INSERT IGNORE,所以如果item已经存在,它不会插入任何东西,你会得到0或者最后一次成功插入的ID作为ID(per the docs)。所以你需要检查的是受影响的行数。如果为 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 等等,但你可以阅读这个here 来开始。

    【讨论】:

      【解决方案2】:

      抱歉,我没有早点解决这个问题,但我已经想通了,同时我也忙于工作。我想我会提供答案,以防将来有人遇到同样的问题。无论如何,问题出在我的 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();
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2022-12-05
        • 1970-01-01
        • 1970-01-01
        • 2021-12-20
        • 1970-01-01
        • 1970-01-01
        • 2020-05-29
        相关资源
        最近更新 更多