Does the following work?
WITH upd AS (
UPDATE main_stat t
SET counter = s.counter
FROM tmp_stat s
WHERE t.date = s.date
AND t.site_id = s.site_id
AND t.ip = s.ip
AND t.block_id = s.block_id
RETURNING s.date, s.site_id, s.ip, s.block_id, s.counter
)
INSERT INTO main_stat
SELECT s.mydate, s.site_id, s.ip, s.block_id, s.counter
FROM tmp_stat s
LEFT JOIN upd ON (upd.date = s.date and upd.site_id = s.site_id and upd.ip = s.ip and upd.block_id = s.block_id)
WHERE upd.date IS NULL
;
Update:
It looks like this is only available for version 9.1 or newer.
Using just-somebody's suggestion of WHERE (t.date, t.site_id, t.ip, t.block_id) = (s.date, s.site_id, s.ip, s.block_id)
appears to give better performance.
WITH upd AS (
UPDATE main_stat t
SET counter = s.counter
FROM tmp_stat s
WHERE ( t.date, t.site_id, t.ip, t.block_id ) = ( s.date, s.site_id, s.ip, s.block_id )
RETURNING s.date, s.site_id, s.ip, s.block_id
)
INSERT INTO main_stat
SELECT s.date, s.site_id, s.ip, s.block_id, s.counter
FROM tmp_stat s
LEFT JOIN upd
ON ( upd.date = s.date
AND upd.site_id = s.site_id
AND upd.ip = s.ip
AND upd.block_id = s.block_id )
WHERE upd.date IS NULL
;
What's happening here is we are using a CTE to do the UPDATE with the CTE returning the identifying columns for the updated rows.
The INSERT then uses the updated row information to filter tmp_stat to only insert the new records.
There are some concurrency caveats which Dimitri Fontaine covers in this blog entry.
More information on CTEs can be found in the Postgresql documentation.