【问题标题】:Getting the final value to this MySQL query获取此 MySQL 查询的最终值
【发布时间】:2010-04-03 21:05:31
【问题描述】:

我的数据库设置了三个表 - 代码、标签和用于标记帖子的 code_tags。 这将是提交帖子时处理的 SQL 查询。每个标签都由 PHP 分割,并使用这些查询单独插入。

INSERT IGNORE INTO tags (tag) VALUES ('$tags[1]');
SELECT tags.id FROM tags WHERE tag = '$tags[1]' ORDER BY id DESC LIMIT 1;
INSERT INTO code_tags (code_id, tag_id) VALUES ($codeid, WHAT_GOES_HERE?)

WHAT_GOES_HERE?最后的价值是我需要知道的。它需要是第二个查询获取的标签的 ID。如何将该 ID 放入第三个查询中?

我希望我解释正确。如有需要我会改写。

编辑:到目前为止,感谢您的帮助,但对于所指出的内容,我仍然有些挣扎 - 如果它已经存在,我无法获得插入的 ID...?

【问题讨论】:

    标签: php database-design mysql tags


    【解决方案1】:

    如果您使用 INSERT IGNORE 并忽略新记录(因为违反唯一键)mysql_insert_id()LAST_INSERT_ID() 没有有意义的值。

    但是您可以使用INSERT ... ON DUPLICATE KEY UPDATELAST_INSERT_ID(expr) 来设置您希望LAST_INSERT_ID() 在出现双峰时返回的数据。

    一步一步:
    假设您有一张表tags 喜欢

    CREATE TABLE tags (
      id int auto_increment,
      tag varchar(32),
      dummy int NOT NULL DEFAULT 0, /* for demo purposes only */
      primary key(id),
      unique key(tag)
    )
    

    由于unique key(tag),两次插入标签会导致duplicate key 违规。这可能就是您使用INSERT IGNORE 的原因。在这种情况下,MySQL 会忽略违规,但也会忽略新记录。问题是您想要具有 tag='xyz' 的记录的 id,无论它是新创建的还是已经在数据库中。但是现在 mysql_insert_id()/LAST_INSERT_ID() 只能提供新记录的 id,而不是被忽略的。

    使用INSERT ...ON DUPLICATE,您可以对此类重复的密钥违规行为做出反应。如果可以插入新记录(无违规),则它的行为类似于“正常”插入。但是在重复键违规的情况下,ON DUPLICATE KEY 之后的部分将像表中已经存在的特定索引值的记录的 UPDATE 语句一样执行。例如。 (有一张空桌子tags

    INSERT INTO tags (tag) VALUES ('tag A') ON DUPLICATE KEY UPDATE dummy=dummy+1
    

    这将简单地插入记录,就好像没有 ON DUPLICATE ... 子句一样。 id 获取下一个自增值,虚拟默认值 0 和 tag='tag A'。假设新创建的 auto_increment 值为 1。存储在 MySQL 中的结果记录是 (id=1, tag='tag A', dummy=0) 并且 LAST_INSERT_ID() 将在此查询后立即返回 1。到目前为止一切顺利。
    现在,如果您使用相同的查询再次插入相同的记录,则会由于第一条记录(id=1,'tag=tag A',dummy=0)而发生冲突。对于已经存在,在执行 ON DUPLICATE KEY 后记录 UPDATE 语句,即记录变为 (id=1, tag='tag A', dummy=1)。但由于没有创建新记录,因此也没有新的 auto_increment 值,LAST_INSERT_ID() 变得无意义。所以仍然是与 INSERT IGNORE 相同的问题。
    但是有一个“特殊”构造允许您设置 LAST_INSERT_ID() 应该在执行 ON DUPLICATE KEY UPDATE 语句后返回的值。

    id=LAST_INSERT_ID(id)
    

    看起来很奇怪,但它实际上只设置了 LAST_INSERT_ID() 将返回的值。
    如果你使用语句

      INSERT INTO
        tags
        (tag)
      VALUES
        ('xyz')
      ON DUPLICATE KEY UPDATE
        id=LAST_INSERT_ID(id)
    

    LAST_INSERT_ID() 将始终返回 tag='xyz' 的记录的 ID,无论它是由 INSERT 部分添加还是由 ON DUPLICATE KEY 部分“更新”。
    IE。如果您的下一个查询是

    INSERT INTO
      code_tags
      (code_id, tag_id)
    VALUES
      (4711, LAST_INSERT_ID())
    

    使用标签“xyz”的tags.id。

    独立的示例脚本使用PDOprepared statements。它应该或多或少地完成您想要实现的目标。

    $pdo = new PDO("mysql:host=localhost;dbname=test", 'localonly', 'localonly'); 
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    
    // set up temporary table and demo data
    $pdo->exec('CREATE TEMPORARY TABLE tmpTags (id int auto_increment, tag varchar(32), primary key(id), unique key(tag))');
    $pdo->exec('CREATE TEMPORARY TABLE tmpCode_tags (code_id int, tag_id int)');
    $pdo->exec("INSERT INTO tmpTags (tag) VALUES ('tagA'), ('tagB')");
    
    // prepare the statements
    // set id=LAST_INSERT_ID(id), so LAST_INSERT_ID() gets a value even if the record is "ignored"
    $stmtTags = $pdo->prepare('
      INSERT INTO
        tmpTags
        (tag)
      VALUES
        (:tag)
      ON DUPLICATE KEY UPDATE
        id=LAST_INSERT_ID(id)
    ');
    $stmtTags->bindParam(':tag', $tag);
    
    $stmtCodeTags = $pdo->prepare('INSERT INTO tmpCode_tags (code_id, tag_id) VALUES (:codeid, LAST_INSERT_ID())');
    $stmtCodeTags->bindParam(':codeid', $codeid);
    
    // and some new records we want to insert
    $testdata = array(
      array('codeid'=>1, 'tags'=>'tagA tagC'), // tagA is already in the table "tags", tagC is a "new" tag
      array('codeid'=>2, 'tags'=>'tagC tagD tagE') // tagC will already be inserted, tagD and atgE are "new"
    );
    
    // process (test)data
    foreach($testdata as $data) {
      // the parameter :codeid of $stmtCodeTags is bound to $codeid; assign it the "current" value
      $codeid = $data['codeid'];
      // split the tags
      $tags = explode(' ', $data['tags']);
      foreach($tags as $tag) {
        // the parameter :tag is bound to $tag
        // nothing more to do than to execute the statement
        $stmtTags->execute();
        // the parameter :codeid is bound to $codeid which was set to $codeid=$data['codeid']
        // again nothing more to do than to execute the statement
        $stmtCodeTags->execute();
      }
    }
    unset($stmtTags);
    unset($stmtCodeTags);
    
    
    // let's see what we've got
    $query = '
      SELECT
        ct.code_id, t.tag
      FROM
        tmpCode_tags as ct
      JOIN
        tmpTags as t
      ON
        ct.tag_id=t.id
    '; 
    foreach( $pdo->query($query, PDO::FETCH_NUM) as $row ) {
      echo join(', ', $row), "\n";
    }
    

    打印

    1, tagA
    1, tagC
    2, tagC
    2, tagD
    2, tagE
    

    edit2:如果脚本的 PDO 部分和准备好的语句令人生畏,那么使用旧的 php-mysql 模块也是如此。但我敦促您使用参数化的准备好的语句。 必须是 PDO,但我碰巧喜欢它。例如。 mysqli 模块也提供预处理语句,旧的 mysql 模块没有。

    $mysql = mysql_connect('localhost', 'localonly', 'localonly') or die(mysql_error());
    mysql_select_db('test', $mysql) or die(mysql_error());
    
    // set up temporary table and demo data
    mysql_query('CREATE TEMPORARY TABLE tmpTags (id int auto_increment, tag varchar(32), primary key(id), unique key(tag))', $mysql) or die(mysql_error());
    mysql_query('CREATE TEMPORARY TABLE tmpCode_tags (code_id int, tag_id int)', $mysql) or die(mysql_error());
    mysql_query("INSERT INTO tmpTags (tag) VALUES ('tagA'), ('tagB')", $mysql) or die(mysql_error());
    
    
    // and some new records we want to insert
    $testdata = array(
      array('codeid'=>1, 'tags'=>'tagA tagC'), // tagA is already in the table "tags", tagC is a "new" tag
      array('codeid'=>2, 'tags'=>'tagC tagD tagE') // tagC will already be inserted, tagD and atgE are "new"
    );
    
    // "prepare" the statements.
    // This is nothing like the server-side prepared statements mysqli and pdo offer.
    // we have to insert the parameters into the query string, i.e. the parameters must
    // be escaped so that they cannot mess up the statement.
    // see mysql_real_escape_string() for string literals within the sql statement.
    $qsTags = "
      INSERT INTO
        tmpTags
        (tag)
      VALUES
        ('%s')
      ON DUPLICATE KEY UPDATE
        id=LAST_INSERT_ID(id)  
    ";
    
    $qsCodeTags = "
      INSERT INTO
        tmpCode_tags
        (code_id, tag_id)
      VALUES
        ('%s', LAST_INSERT_ID())
    ";
    
    foreach($testdata as $data) {
      // in this example codeid is a simple number
      // let's treat it as a string literal in the statement anyway
      $codeid = mysql_real_escape_string($data['codeid'], $mysql);
      $tags = explode(' ', $data['tags']);
      foreach($tags as $tag) {
        // now $tag is certainly a string parameter
        $tag = mysql_real_escape_string($tag, $mysql);
        $query = sprintf($qsTags, $tag);
        mysql_query($query, $mysql) or die(mysql_error());
    
        $query = sprintf($qsCodeTags, $codeid);
        mysql_query($query, $mysql) or die(mysql_error());
      }
    }
    
    // let's see what we've got
    $query = '
      SELECT
        ct.code_id, t.tag
      FROM
        tmpCode_tags as ct
      JOIN
        tmpTags as t
      ON
        ct.tag_id=t.id
    '; 
    $result = mysql_query($query, $mysql) or die(mysql_error());
    while ( false!==($row=mysql_fetch_row($result)) ) {
      echo join(', ', $row), "\n";
    }
    

    【讨论】:

    • 可能有点太激烈了,抱歉,我还是个新手,代码有点吓人。我会用 ON DUPLICATE KEY UPDATE 来“更新”什么?或许你可以再解释一下上下文?
    • 可能是我在 StackOverflow 上见过的最棒的答案... VolkerK,你太棒了。
    • 我已经将它付诸实践,现在一切正常 - 非常感谢!
    【解决方案2】:

    如果我理解您试图正确实现的目标,则不需要第二个查询 - 使用 mysql_insert_id 获取先前插入的行的 ID,我认为您需要“WHAT_GOES_HERE”。

    【讨论】:

    • 哦,不错 - 我从来不知道。谢谢 middaparka!
    • 但请记住,这只会返回一个值如果插入了一条记录。您可能使用 INSERT IGNORE 和 tags.tag 上的唯一索引来避免重复标签。如果您(再次)“添加”现有标签并因此忽略该记录,则 mysql_insert_id() 不会返回先前插入的标签记录的 id。
    • 啊...好点。我自己才意识到。有没有更好的方法?
    猜你喜欢
    • 2015-04-11
    • 1970-01-01
    • 2013-10-07
    • 1970-01-01
    • 1970-01-01
    • 2018-10-22
    • 1970-01-01
    • 2016-07-09
    • 2011-10-26
    相关资源
    最近更新 更多