ALTER TABLE tbl OWNER TO somethingbut
ALTER TABLE tbl SET SCHEMA somethingin PostgreSQL.
Maybe a committee faced with this inconsistency would arrive at the compromise
ALTER TABLE tbl [SET] {OWNER|SCHEMA} [TO] something?
on software development, open source, databases, and geek stuff
ALTER TABLE tbl OWNER TO somethingbut
ALTER TABLE tbl SET SCHEMA somethingin PostgreSQL.
Maybe a committee faced with this inconsistency would arrive at the compromise
ALTER TABLE tbl [SET] {OWNER|SCHEMA} [TO] something?
CREATE TABLE tab ( useful_data int, more_data varchar, start timestamp GENERATED ALWAYS AS SYSTEM VERSION START, end timestamp GENERATED ALWAYS AS SYSTEM VERSION END ) WITH SYSTEM VERSIONING;(This hilariously verbose syntax arises because this is defined so that it fits into the more general generated columns feature, e. g.,
GENERATED ALWAYS AS IDENTITY
, similar to PostgreSQL's serial type.)INSERT INTO tab (useful_data, more_data) VALUES (...);This sets the "start" column to the current transaction timestamp, and the "end" column to the highest possible timestamp value.
UPDATE tab SET useful_data = something WHERE more_data = whatever;For each row that would normally be updated, set the "end" timestamp to the current transaction timestamp, and insert a new row with the "start" timestamp set to the current transaction timestamp.
DELETE
works analogously.SELECT * FROM tab;This only shows rows where
current_timestamp
is between "start" and "end". To show the non-current data, the following options areSELECT * FROM tab AS OF SYSTEM TIME timestamp; SELECT * FROM tab VERSIONS BEFORE SYSTEM TIME timestamp; SELECT * FROM tab VERSIONS AFTER SYSTEM TIME timestamp; SELECT * FROM tab VERSIONS BETWEEN SYSTEM TIME timestamp AND timestamp;There's also the option of
CREATE TABLE tab ( ... ) WITH SYSTEM VERSIONING KEEP VERSIONS FOR interval;to automatically delete old versions.
MERGE
statement has gotten my attention again. For many years, PostgreSQL users have been longing for a way to do an "upsert" operation, meaning do an UPDATE
, and if no record was found do an INSERT
(or the other way around). Especially MySQL users are familiar with the REPLACE
statement and the INSERT ... ON DUPLICATE KEY UPDATE
statement, which are two variant ways to attempt to solve that problem (that have interesting issues of their own). Of course, you can achieve this in PostgreSQL with some programming, but the solutions tend to be specific to the situation, and they tend to be lengthier than one would want.MERGE
statement ought to be the proper way to solve this, and then it turns out that no one completely understands the MERGE
syntax or semantics, especially as they apply to this upsert problem. (I was in that group.) And that's been the end of that so far. OK, as I write this I am pointed, via Robert Haas's blog post, to an older mailing list post by Simon Riggs, who is surely one of the men most qualified to drive an eventual implementation, that contains a hint toward the solution, but it's hard to find in that post, if you want to try.DELETE
branch has been added to MERGE
. We also took some time after the official part of the meeting to work through some examples that illustrate the uses of the MERGE
statement.MERGE
statement is originally supposed to do, and where the term "merge" arose from. Let's say you have a table with outstanding balances, such asCREATE TABLE balances ( name text, balance numeric );and at intervals you get a list of payments that your organization has received, such as
CREATE TABLE payments ( name text, payment numeric );What you want to do then is to "merge" the payments table into the balances table in the following way:
MERGE INTO balances AS b USING payments AS p ON p.name = b.name WHEN MATCHED AND b.balance - p.payment = 0 THEN DELETE WHEN MATCHED AND b.balance - p.payment <> 0 THEN UPDATE SET balance = balance - p.payment WHEN NOT MATCHED THEN INSERT (name, balance) VALUES (p.name, -b.payment);Of course there are simpler cases, but this shows all of the interesting features of this command.
How does this get us upsert? There, you don't have two tables, but only one and some values. I have seen some claims and examples about this in the wild that turn out to be wrong because they evidently violate the syntax rules of the SQL standard. So I did the only sensible thing and implemented the MERGE
syntax into the PostgreSQL
parser on the flight back, because that seemed to be the best way to verify the syntax.
So the correct way, I believe, to do, say, an upsert of the balances table would be:
MERGE INTO balances AS b USING (VALUES ('foo', 10.00), ('bar', 20.00)) AS p (name, payment) ON p.name = b.name WHEN MATCHED AND b.balance - p.payment = 0 THEN DELETE WHEN MATCHED AND b.balance - p.payment <> 0 THEN UPDATE SET balance = balance - p.payment WHEN NOT MATCHED THEN INSERT (name, balance) VALUES (p.name, -b.payment);Not all that nice and compact, but that's how it works.
Note that the AS
clause after VALUES
is required. If you leave it off, the PostgreSQL parser complains that a subquery in FROM
needs an AS
clause. Which is obviously not what this is, but it uses the same grammar rules, and it makes sense in this case because you need a correlation name to join against. And it was also one of those rare moments when you implemented something that gives you correct feedback that you didn't even provide for.
Anyway, the examples above all parse correctly, but they don't do anything yet. But if someone wants to implement this further or just try out the syntax, I'll send my code.
WHERE
clause contains a subquery.CHECK OPTION
feature. See above.elog(WARNING, "not SQL standard")
in about five hundred places, but the trick would be to implement it in a way that is easy to maintain in the future. The mailing list archives also contain some discussions about this, key word "SQL flagger".CREATE TYPE new AS old;Unlike domains, this way the new type does not inherit any of the functions and operators from the old type. This might sound useless at first, but it can actually create better type safety. For example, you could create a type like
CREATE TYPE order_number AS int;while preventing that someone tries to, say, multiply order numbers.
AS $$ ... $$
, allow one unquoted SQL statement as routine body (see example below under RETURN
).LANGUAGE SQL
is the default.SPECIFIC xyz
clause, allowing the assignment of an explicit "specific routine name" that can be used to refer to the function even when overloaded. Probably not terribly useful for PostgreSQL.DETERMINISTIC
/ NOT DETERMINISTIC
clause. DETERMINISTIC
means the same as IMMUTABLE
in PostgreSQL; NOT DETERMINSTIC
is then STABLE
or VOLATILE
.CONTAINS SQL
/ READS SQL DATA
/ MODIFIES SQL DATA
clause. These also appear to overlap with the volatility property in PostgreSQL: MODIFIES
would make the function volatile, READS
would make itSTABLE
.DROP FUNCTION
the ability to drop a function by its "specific name" is required: DROP SPECIFIC FUNCTION specific_name;There are probably some more details missing, so part of finishing this item would also be some research.
CREATE PROCEDURE
that does that same thing as CREATE FUNCTION .. RETURNS void
, and a DROP PROCEDURE
command. CALL procname()
that does the same thing as SELECT procname()
but requires procname()
to not return a value, meaning it has to be a procedure in the above sense. RETURN
callable only from within SQL functions. Then, instead of writing a function like CREATE FUNCTION name(args) RETURNS type LANGUAGE SQL AS $$ SELECT something $$;write
CREATE FUNCTION name(args) RETURNS type LANGUAGE SQL RETURN something;
SELECT table_catalog, table_schema, table_nameThe last line is useful because many predefined tables don't have primary keys.
FROM information_schema.tables
WHERE (table_catalog, table_schema, table_name) NOT IN
(SELECT table_catalog, table_schema, table_name
FROM information_schema.table_constraints
WHERE constraint_type = 'PRIMARY KEY')
AND table_schema NOT IN ('information_schema', 'pg_catalog');
CREATE TABLE schema1.foo ( ... );then you can address these tables as just
CREATE TABLE schema2.bar ( ... );
SET search_path TO schema1, schema2;
SELECT ... FROM foo, bar ...instead of
SELECT ... FROM schema1.foo, schema2.bar ...Another way to describe it is that it allows you to set hidden traps that mysteriously make your perfectly good SQL behave in completely unpredictable ways. So a perfectly harmless query like
SELECT * FROM pg_class WHERE relname ~ 'stuff$';can be changed to do pretty much anything, if the user creates his own pg_class and puts it first into the search path, before the system schema pg_catalog.
SELECT * FROM pg_catalog.pg_class WHERE relname ~ 'stuff$';But this, and the above commit and all of psql is still wrong, because the actually correct way to write this is
SELECT * FROM pg_catalog.pg_class WHERE relname OPERATOR(pg_catalog.~) 'stuff$';because PostgreSQL of course allows user-defined operators, and those are subject to search path rules like anything else. Proof:
CREATE OPERATOR = ( PROCEDURE = charne, LEFTARG = "char", RIGHTARG = "char" );This command is supposed to show all indexes, but now it will show everything that is not an index. Try this yourself; it's a great way to mess with your colleagues' heads. :-) Anyway, surely no one wants to schema-qualify all operators, which is why attempting to qualify everything is probably a hopeless proposition.
SET search_path = public, pg_catalog;
\diS
$ ls -l '/usr/bin/['
-rwxr-xr-x 1 root root 38608 2009-06-05 03:17 /usr/bin/[
Peter Eisentraut's Blog by Peter Eisentraut is licensed under a Creative Commons Attribution 3.0 Unported License.