PSQL throwing error: relation "serial" does not exist, when creating a table - postgresql

It happens when i run this code:
CREATE TABLE distributors (
did integer PRIMARY KEY DEFAULT nextval('serial'),
name varchar(40) NOT NULL CHECK (name <> '')
);
I have tried remover the nextval('serial') but to no avail

You want to do this:
CREATE TABLE distributors (
did serial PRIMARY KEY DEFAULT,
name varchar(40) NOT NULL CHECK (name <> '')
);
The serial type is actually a macro. That per docs (Serial) does:
CREATE SEQUENCE tablename_colname_seq AS integer;
CREATE TABLE tablename (
colname integer NOT NULL DEFAULT nextval('tablename_colname_seq')
);
ALTER SEQUENCE tablename_colname_seq OWNED BY tablename.colname;
Assuming you are on a recent version(10+) of Postgres generated always as identity(see Create Table) is the preferred alternative these days.

nextval('someseries') relies on having an existing series. You can create that with:
CREATE SEQUENCE someseries;
When you removed the nextval, you probably still had the DEFAULT keyword there, which expects a value afterward to define what the default for the column is.

Related

Why do I get a foreign key not unique error on an identity column?

If I try to create two tables like this in PostgreSQL 14.3:
CREATE TABLE IF NOT EXISTS foo (
id INT4 GENERATED BY DEFAULT AS IDENTITY (INCREMENT BY 1 MINVALUE -2147483648 START -2147483648),
foo TEXT UNIQUE NOT NULL
);
CREATE TABLE IF NOT EXISTS bar (
id INT4 GENERATED BY DEFAULT AS IDENTITY (INCREMENT BY 1 MINVALUE -2147483648 START -2147483648),
foo_id INT4 NOT NULL REFERENCES foo(id)
);
I get this output:
$ psql test -f bug01.sql
CREATE TABLE
psql:bug01.sql:9: ERROR: there is no unique constraint matching given keys for referenced table "foo"
I thought GENERATED BY DEFAULT AS IDENTITY would have satisfied the uniqueness constraint. What am I missing? And how do I resolve the issue?
UPDATE: Applying Bergi's answer and comments, I added a primary key constraint to each identity column and that solved the problem:
CREATE TABLE IF NOT EXISTS foo (
id INT4 PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY (INCREMENT BY 1 MINVALUE -2147483648 START -2147483648),
foo TEXT UNIQUE NOT NULL
);
CREATE TABLE IF NOT EXISTS bar (
id INT4 PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY (INCREMENT BY 1 MINVALUE -2147483648 START -2147483648),
foo_id INT4 NOT NULL REFERENCES foo(id)
);
$ psql test -f bug01.sql
CREATE TABLE
CREATE TABLE
Thanks!
Well, no. GENERATED just means it's harder to overwrite (requiring special syntax) than a DEFAULT value, and AS IDENTITY is just a fancy way of defining an implicit sequence, similar to SERIAL.
You still can insert duplicates in your column, either by resetting the sequence or using OVERRIDING SYSTEM VALUE, you must declare the appropriate constraints to prevent this. Simply make your id the PRIMARY KEY of the table.

database record that can belong to one or another table in postgres

I am looking for some solution for my next use-case in postgres:
I have a table (tasks) that can belong to a user (another table) or a company (another table), it should belong to one of the two tables, that means that I should have nullable foreign keys but also I should check that on Insert then one and only one of those should be filled. How can I make this?
Adding nullable columns (lets say user_id and company_id) with corresponding FOREIGN KEY is correct.
You can add a check-constraint like this (to do it in the CREATE TABLE statement, you can list it "like a column" and start at CONSTRAINT):
ALTER TABLE tasks ADD CONSTRAINT tasks_fk_check
CHECK (
(user_id IS NOT NULL AND company_id IS NULL)
OR
(user_id IS NULL AND company_id IS NOT NULL)
)
;
There are some other possibilities like (user_id IS NULL) <> (company_id IS NULL) to express the XOR property. You can choose them as condition as well.
For further information, have a look into the documentation:
https://www.postgresql.org/docs/current/sql-altertable.html
https://www.postgresql.org/docs/current/sql-createtable.html
https://www.postgresql.org/docs/current/ddl-constraints.html#DDL-CONSTRAINTS-CHECK-CONSTRAINTS
You can create a function that counts the number of null values in a VARIADAC parameter list. Then create a check constraint accessing that function.
If this is an isolated use case then this is not necessary the best approach, but it is a generalized approach for requiring a certain number of nulls from a set of columns.
--setup create test table
create table task( id serial, user_id integer, company_id integer);
-- create function to count number of nulls in VARIADIC parameter
create or replace function num_of_nulls(VARIADIC integer[])
returns bigint
language sql immutable
as $$
with each_item as (select unnest($1) itm)
select sum(case when itm is null then 1 else 0 end) from each_item;
$$;
-- add check constraint
alter table task add constraint one_and_only_one_must_be_null check (num_of_nulls(user_id,company_id) = 1);
-- test
-- valid
insert into task(user_id,company_id)
values (1,null), (null,1);
select * from task;
-- invalid (must be run separately)
insert into task(user_id,company_id)
values (null,null);
insert into task(user_id,company_id)
values (2,2);

how-to modify a column type and set its default and current value in postgresql

I would like to perform the following change, from
CREATE TABLE IF NOT EXISTS scheme.xxx (
id SERIAL PRIMARY KEY,
to
CREATE TABLE IF NOT EXISTS scheme.xxx (
id UUID DEFAULT uuid_generate_v4(),
in an existing table with records, but I fail to achieve it.
An example that doesn't work is:
ALTER TABLE scheme.xxx
ALTER COLUMN id TYPE UUID SET DEFAULT uuid_generate_v4()
USING id::uuid_generate_v4() ;
You'll have to remove the default value first, then change the type, then add the new default value.
But first find out the sequence:
SELECT pg_get_serial_sequence('scheme.xxx', 'id');
pg_get_serial_sequence
------------------------
scheme.xxx_id_seq
(1 row)
Now do it:
ALTER TABLE scheme.xxx
ALTER id DROP DEFAULT,
ALTER id TYPE uuid USING uuid_generate_v4(),
ALTER id SET DEFAULT uuid_generate_v4();
All in one statement!
Now get rid of the sequence:
DROP SEQUENCE public.xxx_id_seq;

PostgreSQL bigserial & nextval

I've got a PgSQL 9.4.3 server setup and previously I was only using the public schema and for example I created a table like this:
CREATE TABLE ma_accessed_by_members_tracking (
reference bigserial NOT NULL,
ma_reference bigint NOT NULL,
membership_reference bigint NOT NULL,
date_accessed timestamp without time zone,
points_awarded bigint NOT NULL
);
Using the Windows Program PgAdmin III I can see it created the proper information and sequence.
However I've recently added another schema called "test" to the same database and created the exact same table, just like before.
However this time I see:
CREATE TABLE test.ma_accessed_by_members_tracking
(
reference bigint NOT NULL DEFAULT nextval('ma_accessed_by_members_tracking_reference_seq'::regclass),
ma_reference bigint NOT NULL,
membership_reference bigint NOT NULL,
date_accessed timestamp without time zone,
points_awarded bigint NOT NULL
);
My question / curiosity is why in a public schema the reference shows bigserial but in the test schema reference shows bigint with a nextval?
Both work as expected. I just do not understand why the difference in schema's would show different table creations. I realize that bigint and bigserial allow the same volume of ints to be used.
Merely A Notational Convenience
According to the documentation on Serial Types, smallserial, serial, and bigserial are not true data types. Rather, they are a notation to create at once both sequence and column with default value pointing to that sequence.
I created test table on schema public. The command psql \d shows bigint column type. Maybe it's PgAdmin behavior ?
Update
I checked PgAdmin source code. In function pgColumn::GetDefinition() it scans table pg_depend for auto dependency and when found it - replaces bigint with bigserial to simulate original table create code.
When you create a serial column in the standard way:
CREATE TABLE new_table (
new_id serial);
Postgres creates a sequence with commands:
CREATE SEQUENCE new_table_new_id_seq ...
ALTER SEQUENCE new_table_new_id_seq OWNED BY new_table.new_id;
From documentation: The OWNED BY option causes the sequence to be associated with a specific table column, such that if that column (or its whole table) is dropped, the sequence will be automatically dropped as well.
Standard name of a sequence is built from table name, column name and suffix _seq.
If a serial column was created in such a way, PgAdmin shows its type as serial.
If a sequence has non-standard name or is not associated with a column, PgAdmin shows nextval() as default value.

How create a serial field in a postgresql query?

In postgresql update/insert are very slow. So often is faster create a new table with the new field/row and then replace the old table
The thing is I need an autonumeric so in this case I had to create the table first and do the insert later.
Is there a way create an autonumeric field in the select so I can use
CREATE TABLE source.road_nodes AS
SELECT serial_field, node
instead of CREATE TABLE + INSERT.
CREATE TABLE source.road_nodes (
node_id serial,
node TEXT
);
INSERT INTO source.road_nodes (node)
SELECT DISTINCT node
FROM
(
SELECT DISTINCT node_begin AS node
FROM source.vzla_rto
) as node_pool;
In a way this is possible. You can take advantage of the fact that as the PostgreSQL documentation - 8.1.4. Serial Types states:
CREATE TABLE tablename (
colname SERIAL
);
is equivalent to specifying:
CREATE SEQUENCE tablename_colname_seq;
CREATE TABLE tablename (
colname integer NOT NULL DEFAULT nextval('tablename_colname_seq')
);
ALTER SEQUENCE tablename_colname_seq OWNED BY tablename.colname;
Creating a sequence separately and making the next value the default for the column you effectively make it a SERIAL column.
CREATE SEQUENCE road_nodes_node_id_seq;
CREATE TABLE road_nodes AS
SELECT DISTINCT ON (node) nextval('road_nodes_node_id_seq') AS node_id, node FROM vzla_rto;
ALTER TABLE road_nodes ALTER node_id SET NOT NULL, ALTER COLUMN node_id SET DEFAULT nextval('road_nodes_node_id_seq');
You could keep the sequence and just reset it to one when you would recreate the table.
I wouldn't say that this is an elegant way however and probably the easiest way is just to create the table separately.