php - 当项目已经存在时更新中间表时出现问题

标签 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
);

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

只要输入一个新项目,一切正常,但如果输入一个现有项目,中间表不会更新,即使 people 表更新了。我也没有收到任何错误。

Items 是一个逗号分隔的文本条目,它被展开并小写成 $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();
}

我做错了什么?

最佳答案

因为你使用的是INSERT IGNORE,所以如果该项目已经存在,它不会插入任何东西,你会得到 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开始。

关于php - 当项目已经存在时更新中间表时出现问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45846304/

相关文章:

javascript - 如何使用 AJAX 发布多个值?

php - 通过谷歌地图 API 查找经销商区域

mysql - SQL查询找不到错误

php - 您能否在动态数据透视表上使用此代码帮助我。错误:(!) fatal error :调用成员函数fetch_all()

php - 我应该将我的网站升级到 PHP MySQLi 还是 PDO?

php - 我将如何加密我的 php 文件,以便只有拥有 key 的人才能访问它们?

php - echo() 接受数字吗?

mysql - 批量插入操作速度太慢

mysql - 如何在 mysql 中设置 bulk_insert_buffer_size?

php - 获取日期并插入字段 - 表单中的隐藏字段