开发者

How to upsert two database tables with one SQL statement?

There are two database tables described in the following. The asterisk highlights the primary key开发者_如何学Cs.

+----------------+    +----------------+
| posts          |    | url_references |
+----------------+    +----------------+
| id*            |    | url*           |
| post_content   |    | post_id        |
+----------------+    +----------------+

I want to insert or update a post based on the existence of the corresponding entry in the table url_references. What is the best combination of SQL commands to reach this? I would like to avoid to process the decision about insert or update in PHP.

The following scenarios describe the alternative step by step behavior using PHP commands.

SELECT * FROM url_references WHERE url = $url;

Scenario 1: Insert new entry.

// mysql_num_rows() returns 0
INSERT INTO post (post_content) VALUES ($postContent);
$postId = mysql_insert_id();
INSERT INTO url_references (url, post_id) VALUES ($url, $postId);

Scenario 2: Update existing entry.

// mysql_num_rows() returns 1
$row = mysql_fetch_array($rows);
$postId = $row['post_id'];
UPDATE posts SET post_content = $postContent WHERE id = post_id;

Edit 1: Note, that I cannot check for the id in posts directly! I want to manage (insert/update) posts based on their url as the primary key.


If you would like to do above in a classical way, do the following.

First we may agree that url is a natural primary key. Anyway you need an index in this column to speedup your lookups:

CREATE UNIQUE INDEX url_references_idx ON url_references(url);

After that if you execute:

INSERT INTO url_references (url) VALUES ($url);

you end up with two scenarios:

• The INSERT succeeds. That means your $url is new and you can proceed with:

INSERT INTO posts (id, post_content) values (NULL, $postContent);
SELECT LAST_INSERT_ID(); // This will return $post_id
UPDATE url_references SET post_id = $post_id WHERE url = $url;
COMMIT;

In this scenario the lock on newly inserted row in url_references guarantees that another thread will go the 2nd scenario, if 1st transaction is successfully committed (or 1st scenario if it fails).

• The INSERT fails. That means your $url is already known and you can proceed with:

SELECT post_id FROM url_references WHERE url = $url;
UPDATE posts SET post_content = $postContent WHERE id = $post_id;
COMMIT;

Note: The first INSERT statement guarantees that racing condition on url_references table is correctly handled provided you have enabled a correct transaction isolation level and autocommit=off.

Note: Using SELECT ... FOR UPDATE does not work in this case, as it will lock only existing rows (and we need to lock non-existing row, which is about to be inserted). Sorry if I confused you, please ignore my comment under your question.


You can use REPLACE INTO to avoid deciding between INSERT/UPDATE on the posts table, then based on the number of rows affected by REPLACE determine whether you need to INSERT into url_references. Something like this:

$sql = "REPLACE INTO posts (post_id, post_content) VALUES ($postId, $postContent)";
$result = mysql_query($sql);
$numRows = mysql_num_rows($result);

if ($numRows == 1) {
    // was INSERT not DELETE/INSERT (UPDATE) - need to insert into url_references

    $postId = mysql_insert_id();
    $sql = "INSERT INTO url_references (url, post_id) VALUES ($url, $postId)";

    ... do SQL INSERT etc
}

From the MySQL REPLACE documentation:

The REPLACE statement returns a count to indicate the number of rows affected. This is the sum of the rows deleted and inserted. If the count is 1 for a single-row REPLACE, a row was inserted and no rows were deleted. If the count is greater than 1, one or more old rows were deleted before the new row was inserted.

Note that I haven't tested the above pseudo-code, so you may have to tweak it.

See: http://dev.mysql.com/doc/refman/5.0/en/replace.html for reference.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜