Merge Query Builder
BackThe Merge Query Builder has a fluent, type-safe interface that builds PostgreSQL MERGE queries. MERGE (also known as "upsert" in some contexts) does INSERT, UPDATE, or DELETE operations in one statement. The operation depends on whether rows of a source table and a target table match.
Simple Merge with Update
<?php
use function Flow\PostgreSql\DSL\{merge, col, eq};
$query = merge('target_table', 't')
->using('source_table', 's')
->on(eq(col('t.id'), col('s.id')))
->whenMatched()
->thenUpdate([
'name' => col('s.name'),
'value' => col('s.value'),
]);
echo $query->toSql();
// MERGE INTO target_table t USING source_table s ON t.id = s.id WHEN MATCHED THEN UPDATE SET name = s.name, value = s.value
Merge with Delete
<?php
use function Flow\PostgreSql\DSL\{merge, col, eq};
$query = merge('target_table', 't')
->using('source_table', 's')
->on(eq(col('t.id'), col('s.id')))
->whenMatched()
->thenDelete();
echo $query->toSql();
// MERGE INTO target_table t USING source_table s ON t.id = s.id WHEN MATCHED THEN DELETE
Merge with Insert
<?php
use function Flow\PostgreSql\DSL\{merge, col, eq};
$query = merge('customers')
->using('new_customers', 'nc')
->on(eq(col('customers.id'), col('nc.id')))
->whenNotMatched()
->thenInsert(
['id', 'name', 'email'],
[col('nc.id'), col('nc.name'), col('nc.email')]
);
echo $query->toSql();
// MERGE INTO customers USING new_customers nc ON customers.id = nc.id WHEN NOT MATCHED THEN INSERT (id, name, email) VALUES (nc.id, nc.name, nc.email)
Merge with Insert Values
Alternative syntax using column => value pairs:
<?php
use function Flow\PostgreSql\DSL\{merge, col, eq, literal};
$query = merge('users')
->using('new_users', 'n')
->on(eq(col('users.id'), col('n.id')))
->whenNotMatched()
->thenInsertValues([
'id' => col('n.id'),
'name' => col('n.name'),
'status' => literal('active'),
]);
echo $query->toSql();
// MERGE INTO users USING new_users n ON users.id = n.id WHEN NOT MATCHED THEN INSERT (id, name, status) VALUES (n.id, n.name, 'active')
Multiple WHEN Clauses
Combine different actions based on matching conditions:
<?php
use function Flow\PostgreSql\DSL\{merge, col, eq};
$query = merge('inventory', 'i')
->using('updates', 'u')
->on(eq(col('i.product_id'), col('u.product_id')))
->whenMatched()
->thenUpdate([
'quantity' => col('u.quantity'),
'updated_at' => col('u.updated_at'),
])
->whenNotMatched()
->thenInsert(
['product_id', 'quantity'],
[col('u.product_id'), col('u.quantity')]
);
echo $query->toSql();
// MERGE INTO inventory i USING updates u ON i.product_id = u.product_id WHEN MATCHED THEN UPDATE SET quantity = u.quantity, updated_at = u.updated_at WHEN NOT MATCHED THEN INSERT (product_id, quantity) VALUES (u.product_id, u.quantity)
Conditional WHEN Clauses
Add conditions to the WHEN MATCHED and WHEN NOT MATCHED clauses:
<?php
use function Flow\PostgreSql\DSL\{merge, col, eq, gt, literal};
$query = merge('products', 'p')
->using('price_updates', 'pu')
->on(eq(col('p.id'), col('pu.product_id')))
->whenMatchedAnd(gt(col('pu.price'), literal(0)))
->thenUpdate([
'price' => col('pu.price'),
]);
echo $query->toSql();
// MERGE INTO products p USING price_updates pu ON p.id = pu.product_id WHEN MATCHED AND pu.price > 0 THEN UPDATE SET price = pu.price
DO NOTHING Action
<?php
use function Flow\PostgreSql\DSL\{merge, col, eq};
$query = merge('products')
->using('updates', 'u')
->on(eq(col('products.id'), col('u.id')))
->whenMatched()
->thenDoNothing();
echo $query->toSql();
// MERGE INTO products USING updates u ON products.id = u.id WHEN MATCHED THEN DO NOTHING
Merge Using Subquery
Use a subquery as the source:
<?php
use function Flow\PostgreSql\DSL\{merge, col, eq, select, table};
$sourceQuery = select()
->select(col('id'), col('name'), col('email'))
->from(table('staged_data'));
$query = merge('users')
->using($sourceQuery, 'src')
->on(eq(col('users.id'), col('src.id')))
->whenMatched()
->thenUpdate([
'name' => col('src.name'),
'email' => col('src.email'),
]);
echo $query->toSql();
// MERGE INTO users USING (SELECT id, name, email FROM staged_data) src ON users.id = src.id WHEN MATCHED THEN UPDATE SET name = src.name, email = src.email
Merge with CTE (WITH Clause)
<?php
use function Flow\PostgreSql\DSL\{with, col, eq, select, cte, table, merge};
$stagedData = select()
->select(col('id'), col('name'))
->from(table('raw_input'));
$query = with(
cte('staged_data', $stagedData)
)->merge('users')
->using('staged_data', 's')
->on(eq(col('users.id'), col('s.id')))
->whenMatched()
->thenUpdate([
'name' => col('s.name'),
]);
echo $query->toSql();
// WITH staged_data AS (SELECT id, name FROM raw_input) MERGE INTO users USING staged_data s ON users.id = s.id WHEN MATCHED THEN UPDATE SET name = s.name
Merge with Parameters
Use positional parameters for prepared statements:
<?php
use function Flow\PostgreSql\DSL\{merge, col, eq, param};
$query = merge('accounts')
->using('transactions', 't')
->on(eq(col('accounts.id'), col('t.account_id')))
->whenMatched()
->thenUpdate([
'balance' => param(1),
]);
echo $query->toSql();
// MERGE INTO accounts USING transactions t ON accounts.id = t.account_id WHEN MATCHED THEN UPDATE SET balance = $1
WHEN NOT MATCHED BY SOURCE
Handle the rows in the target that have no matching rows in the source:
<?php
use function Flow\PostgreSql\DSL\{merge, col, eq};
$query = merge('target', 't')
->using('source', 's')
->on(eq(col('t.id'), col('s.id')))
->whenNotMatchedBySource()
->thenDelete();
echo $query->toSql();
// MERGE INTO target t USING source s ON t.id = s.id WHEN NOT MATCHED BY SOURCE THEN DELETE
Schema-Qualified Tables
<?php
use function Flow\PostgreSql\DSL\{merge, col, eq};
$query = merge('myschema.users', 'u')
->using('public.updates', 's')
->on(eq(col('u.id'), col('s.id')))
->whenMatched()
->thenDoNothing();
echo $query->toSql();
// MERGE INTO myschema.users u USING public.updates s ON u.id = s.id WHEN MATCHED THEN DO NOTHING
DSL Functions
For a complete list of DSL functions, see the DSL reference.
Found a typo or an outdated section? Edit this page on GitHub