获取此MySQL查询的最终值

时间:2010-04-03 21:05:31

标签: php database-design mysql tags

我的数据库设置有三个表 - 代码,标签和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 ......?

2 个答案:

答案 0 :(得分:2)

如果您使用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)
)

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

使用INSERT ...ON DUPLICATE,您可以对此类重复的密钥违规做出反应。如果可以插入新记录(无违规),则其行为类似于“正常”INSERT。但是在重复键冲突的情况下,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部件“更新”。
即如果您的下一个查询是

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";
}

答案 1 :(得分:1)

如果我理解你正在尝试正确实现的目标,那么第二个查询是不必要的 - 使用mysql_insert_id来获取先前插入行的ID,这就是我假设你需要“WHAT_GOES_HERE”。