2010-04-03 2 views
2

У меня установлена ​​моя база данных с тремя таблицами - кодом, тегами и code_tags для пометки сообщений. Это будет SQL-запрос, обрабатываемый при отправке сообщения. Каждый тег нарезается PHP и индивидуально вставляется с использованием этих запросов.Получение окончательного значения для этого запроса MySQL

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 ...?

ответ

2

Если вы используете 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"; 
} 
+0

Интенсивный ответ! Благодарю. – Jack

+0

Возможно, слишком интенсивный, извините, я все еще немного новичок, и этот код немного запугивает. Что бы я «обновил» с помощью ON DUPLICATE KEY UPDATE? Возможно, вы могли бы объяснить контекст немного больше? – Jack

+0

ответ расширенный – VolkerK

1

Если я понимаю, что вы пытаетесь достичь правильно, второй запрос не нужен - используйте mysql_insert_id, чтобы получить идентификатор ранее вставленной строки, и я предполагаю, что вам нужно «WHAT_GOES_HERE».

+0

Ох, хороший - я никогда не знал об этом. Спасибо middaparka! – Jack

+0

Но имейте в виду, что это только вернет значение _if_, запись была вставлена. Вероятно, вы используете INSERT _IGNORE_ и уникальный индекс в tag.tag, чтобы избежать дублирования тегов. Если вы «добавили» существующий тег (снова), и поэтому запись игнорируется, mysql \ _insert_id() не возвращает идентификатор ранее вставленной записи тега. – VolkerK

+0

А ... хороший момент. Просто осознал это сам. Есть ли лучший способ сделать это? – Jack

Смежные вопросы