Если вы используете INSERT IGNORE
, и новая запись игнорируется (из-за уникального нарушения ключа) mysql_insert_id() и LAST_INSERT_ID() не имеют значащего значения.
Но вы можете использовать INSERT ... ON DUPLICATE KEY UPDATE и LAST_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 игнорирует нарушение, но новая запись также игнорируется. Проблема в том, что вы хотите, чтобы идентификатор записи имел тег = 'xyz', независимо от того, был ли он создан или уже был в базе данных. Но прямо сейчас mysql_insert_id()/LAST_INSERT_ID() может предоставить идентификатор новой записи, а не игнорируемый.
С помощью INSERT ...ON DUPLICATE
вы можете отреагировать на такие дубликаты ключей. Если новая запись может быть вставлена (без нарушения), она ведет себя как «нормальный» INSERT. Но в случае дублирования ключевого нарушения часть после ON DUPLICATE KEY выполняется как оператор UPDATE для записи с указанным значением индекса, уже существующим в таблице. Например. (С пустым столом tags
)
INSERT INTO tags (tag) VALUES ('tag A') ON DUPLICATE KEY UPDATE dummy=dummy+1
Это будет просто вставить запись, как если бы не было дублирования ... пункт. 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). Для это уже exisitng записывает инструкцию UPDATE после нажатия кнопки DUPLICATE KEY, то есть запись становится (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() всегда возвращают идентификатор записи, имеющий тег = «хуг» независимо от того, если бы он был добавлен в вставной части или «обновленный» по дублированию KEY часть.
I.e. если ваш следующий запрос:
INSERT INTO
code_tags
(code_id, tag_id)
VALUES
(4711, LAST_INSERT_ID())
the tags.id для тега 'xyz' используется.
Самостоятельный сценарий примера использует PDO и prepared 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 модуля. Но I попросите использовать параметризованные подготовленные заявления. У есть быть 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";
}
Интенсивный ответ! Благодарю. – Jack
Возможно, слишком интенсивный, извините, я все еще немного новичок, и этот код немного запугивает. Что бы я «обновил» с помощью ON DUPLICATE KEY UPDATE? Возможно, вы могли бы объяснить контекст немного больше? – Jack
ответ расширенный – VolkerK