cannot use column reference in DEFAULT expression in postgresql - postgresql

CREATE temp TABLE demo1(
col_a int,
col_b int DEFAULT (col_a)
);
PostgreSQL have generated columns, triggers. But why cannot use column reference in DEFAULT expression.
since the manual ddl-default page mention:
The default value can be an expression, which will be evaluated
whenever the default value is inserted (not when the table is
created). A common example is for a timestamp column to have a default
of CURRENT_TIMESTAMP, so that it gets set to the time of row
insertion.
Obviously, column references is value expression.
https://www.postgresql.org/docs/current/sql-expressions.html#SQL-EXPRESSIONS-COLUMN-REFS
A value expression is one of the following:
* A constant or literal value

The documentation states:
DEFAULT default_expr
The DEFAULT clause assigns a default data value for the column whose column definition it appears within. The value is any variable-free expression (in particular, cross-references to other columns in the current table are not allowed). Subqueries are not allowed either. The data type of the default expression must match the data type of the column.
So the restriction is clearly documented.
But it is fairly easy to see that it would pose problems to allow a column reference in a DEFAULT expression: For one, the value specified in the INSERT statement could be overridden by BEFORE INSERT triggers. In that case, should the value before or after the trigger execution be used? Also, what if the DEFAULT for column a references column b and vice versa? There may be other difficulties that I didn't think of.

Related

Convert column domain as smallint to boolean in firebird

I'm using Firebird 4.0 and I would convert a column from smallint 0|1 to boolean.
So I have this kind of domain:
CREATE DOMAIN D_BOOL
AS SMALLINT
DEFAULT 0
NOT NULL
CHECK (VALUE IN (0,1))
;
This domain is used in my test table:
CREATE TABLE TBOOL
(
ID INTEGER,
INTVAL D_BOOL
);
How can I convert the column INTVAL to BOOLEAN?
I tried this query but I got an error:
alter table tbool
alter column INTVAL TYPE BOOLEAN,
alter column INTVAL SET DEFAULT FALSE
Error:
Error: *** IBPP::SQLException ***
Context: Statement::Execute( alter table tbool
alter column INTVAL TYPE BOOLEAN,
alter column INTVAL SET DEFAULT FALSE )
Message: isc_dsql_execute2 failed
SQL Message : -607
This operation is not defined for system tables.
Engine Code : 335544351
Engine Message :
unsuccessful metadata update
ALTER TABLE TBOOL failed
MODIFY RDB$RELATION_FIELDS failed
Unfortunately, this is an incompatible column change, because there is no conversion defined from SMALLINT to BOOLEAN. Altering the type of a column only works for a limited combination of types (and there is no combination that allows modification to or from BOOLEAN).
The only real option is to add a new column, populate it based on the value of the old column, drop the old column and rename the new column. This can have a huge impact if this column is used in triggers, procedures and/or views.
Your options are basically:
Keep existing columns as-is, and only use BOOLEAN moving forward for new columns
Do a very invasive change to change all your columns.
If you have a lot of columns that need to change, this is likely easier to do by creating a new database from scratch and pumping the data over, than by changing the database in-place.
The background of this limitation is that Firebird doesn't actually modify existing values when changing the type of a column. Instead, it will convert values on the fly when reading rows created with an older "format version" (inserts and updates will write the new "format version").
This makes for fast DDL, but all conversions must be known to succeed. This basically means only "widening" conversions between similar types are allowed (e.g. longer (VAR)CHAR, longer integer types, etc).

insert function with side-effects: how to take insert parameter?

I'm interested in writing a Postgres function that inserts a new row to e.g. an invoice table, and performs some side effects based on the result of the insertion. My invoice table has some columns with default values (e.g. auto-generated primary key column id), and some that are optional.
I'm wondering if there's a way to take a parameter that represents a row of the invoice table, possibly without default and optional fields, and insert that value directly as a row.
CREATE OR REPLACE FUNCTION public.insert_invoice(new_invoice invoice)
RETURNS uuid
LANGUAGE sql
AS $$
WITH invoice_insert_result AS (
-- This fails: new_invoice has type invoice, but expected type uuid (because it thinks we want to put `new_invoice` in the "id" column)
INSERT INTO invoice VALUES (new_invoice)
RETURNING id
)
-- Use the result to perform side-effects
SELECT invoice_insert_result.id
$$;
I know this is possible to do by replicating the schema of the invoice table in the list of parameters of the function, however I'd prefer not to do that since it would mean additional boilerplate and maintenance burden.
The uuid is a value that is automatically generated, you cannot insert a uuid value to a table.
The target column names can be listed in any order. If no list of
column names is given at all, the default is all the columns of the
table in their declared order; or the first N column names, if there
are only N columns supplied by the VALUES clause or query. The values
supplied by the VALUES clause or query are associated with the
explicit or implicit column list left-to-right.
https://www.postgresql.org/docs/current/sql-insert.html
The quote part means that you insert command can either explicit mention column list. Or you not mention column list then the to be inserted command (after values) should have all the column list's value.
to achiever your intended result, Insert command,you must specify columns list. If not, then you need insert uuid value. But you cannot uuid is auto generated. The same example would be like if a table have a column bigserial then you cannot insert bigserial value to that column. Since bigserial is auto-generated.
For other non-automatic column, You can aggregated them use customized type.
denmo
create type inv_insert_template as (receiver text, base_amount numeric,tax_rate numeric);
full function:
CREATE OR REPLACE FUNCTION public.insert_invoice(new_invoice inv_insert_template)
RETURNS bigint
LANGUAGE sql
AS $$
WITH invoice_insert_result AS (
INSERT INTO invoices(receiver,base_amount, tax_rate)
VALUES (new_invoice.receiver,
new_invoice.base_amount,
new_invoice.tax_rate) RETURNING inv_no
)
SELECT invoice_insert_result.inv_no from invoice_insert_result;
$$;
call it: select * from public.insert_invoice(row('person_c', 1000, 0.1));
db fiddle demo

How can I enter empty records into a postgres table

I have a table in postgres that basically just serves to collect multiple object in a bag. Think like a shopping cart. It only has a uuid that is generated automatically.
How can I insert an empty record into such a table?
INSERT INTO cart() values();
Doesn't work
From the fine INSERT manual:
This section covers parameters that may be used when only inserting new rows. [...]
DEFAULT VALUES
All columns will be filled with their default values, as if DEFAULT were explicitly specified for each column. (An OVERRIDING clause is not permitted in this form.)
[...]
DEFAULT
The corresponding column will be filled with its default value. An identity column will be filled with a new value generated by the associated sequence. For a generated column, specifying this is permitted but merely specifies the normal behavior of computing the column from its generation expression.
So you can use DEFAULT as a value to explicitly use the column's default value:
INSERT INTO cart(your_uuid_column) values(default);
or you can say:
INSERT INTO cart default values;
to use defaults for all the columns.

What do "constant", "expression", and "sequence" represent in a DEFAULT clause?

Currently creating my PostgreSQL tables through Postico, and I came across this field for when creating new columns. It is called DEFAULT and its default value is no default. You can select constant, expression, and sequence as options, though.
What exactly do these mean?
The manual on CREATE TABLE:
DEFAULT default_expr
The DEFAULT clause assigns a default data value for the column whose column definition it appears within. The value is any
variable-free expression (subqueries and cross-references to other
columns in the current table are not allowed). The data type of the
default expression must match the data type of the column.
The default expression will be used in any insert operation that does
not specify a value for the column. If there is no default for a
column, then the default is null.
constant and expression should be clear now. sequence is a special feature to make it a serial column:
Creating a PostgreSQL sequence to a field (which is not the ID of the record)
More details on the page #mu provided:
https://www.postgresql.org/docs/current/static/ddl.html

postgres lag has type but row does not. Why?

In the following query, the lag column gets the type of the table (temp_junk) but the row column gets generic type record (although it can be cast to temp_junk). Is this the way it is supposed to work? Or am I misunderstanding something?
drop table if exists temp_junk;
create temp table temp_junk
(
val integer
);
insert into temp_junk
values (1), (2), (3), (4);
select row(tj.*), row(tj.*)::temp_junk, lag(tj.*) over ()
from temp_junk tj;
EDIT: I guess my question becomes, why is lag() smart enough to return a type but row() isn't?
This is stated in doc
By default, the value created by a ROW expression is of an anonymous
record type. If necessary, it can be cast to a named composite type —
either the row type of a table, or a composite type created with
CREATE TYPE AS. An explicit cast might be needed to avoid ambiguity.
So if it's not casted, you get an anonymous record type => record.