I"new to PostgreSQL and am struggling with a DDL issue. Below script "hangs" unless I remove column "keel" from table and unique constraint definitions. What may be the error ? Your input highly appreciated.
CREATE TABLE k_keel
(
id integer NOT NULL,
nim character varying(30) NOT NULL,
kood character(2) NOT NULL, -- Kahetäheline ISO 639 keelekood
CONSTRAINT k_keel_pkey PRIMARY KEY (id),
CONSTRAINT k_keel_nim_uniq UNIQUE (nim)
)
WITH (
OIDS=FALSE
);
ALTER TABLE k_keel
OWNER TO postgres;
GRANT ALL ON TABLE k_keel TO postgres;
GRANT SELECT ON TABLE k_keel TO overload;
COMMENT ON COLUMN k_keel.kood IS 'Kahetäheline ISO 639 keelekood';
-- Table: ac.e_kiri
-- DROP TABLE ac.e_kiri CASCADE;
CREATE TABLE ac.e_kiri
(
id INTEGER PRIMARY KEY DEFAULT nextval('ac.email_id_seq'::regclass),
nimi character varying(100) NOT NULL UNIQUE,
memo TEXT
)
WITH (
OIDS=FALSE
);
ALTER TABLE ONLY ac.e_kiri
ADD CONSTRAINT "Nimi peal olema unikaalne" UNIQUE (nimi);
ALTER TABLE ac.e_kiri
OWNER TO postgres;
GRANT ALL ON TABLE ac.e_kiri TO postgres;
GRANT SELECT, UPDATE, INSERT, DELETE ON TABLE ac.e_kiri TO overload;
COMMENT ON TABLE ac.e_kiri IS 'Tüüpkirjade tabel';
-- DROP TABLE ac.e_kiri_tekst CASCADE;
CREATE TABLE ac.e_kiri_tekst
(
id INTEGER PRIMARY KEY DEFAULT nextval('ac.email_id_seq'::regclass),
e_kiri INTEGER REFERENCES ac.e_kiri (id) NOT NULL,
keel INTEGER REFERENCES public.k_keel (id) NOT NULL,
subjekt text,
sisu text
)
WITH (
OIDS=FALSE
);
ALTER TABLE ONLY ac.e_kiri_tekst
ADD CONSTRAINT "Nimi ja keel peavad olema unikaalsed" UNIQUE (e_kiri,keel);
ALTER TABLE ac.e_kiri_tekst
OWNER TO postgres;
GRANT ALL ON TABLE ac.e_kiri_tekst TO postgres;
GRANT SELECT, UPDATE, INSERT, DELETE ON TABLE ac.e_kiri_tekst TO overload;
COMMENT ON TABLE ac.e_kiri_tekst IS 'Tüüpkirja subjekt ja tekst antud keeles';
Related
I have 2 tables:
CREATE TABLE public."user"
(
id integer NOT NULL DEFAULT nextval('user_id_seq'::regclass),
username character varying(256) COLLATE pg_catalog."default",
CONSTRAINT user_pkey PRIMARY KEY (id)
)
CREATE TABLE public.user_tenant
(
user_id integer NOT NULL,
tenant_id integer NOT NULL,
CONSTRAINT user_fk FOREIGN KEY (user_id)
REFERENCES public."user" (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE CASCADE
)
I need to have unique values (user.username, user_tenant.tenant_id). How can I declare such a constraint?
I would make the username unique, just like the tenant that is in another table. When that is done, you can put a primary key on the user_id and tenant_id:
CREATE TABLE public."user"
(
id integer NOT NULL DEFAULT nextval('user_id_seq'::regclass),
username character varying(256) COLLATE pg_catalog."default" unique,
CONSTRAINT user_pkey PRIMARY KEY (id)
);
CREATE TABLE public.user_tenant
(
user_id integer NOT NULL,
tenant_id integer NOT NULL,
CONSTRAINT user_fk FOREIGN KEY (user_id)
REFERENCES public."user" (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE CASCADE,
CONSTRAINT user_tenant_pk PRIMARY KEY (user_id, tenant_id)
);
By the way, don't use reserved names like "user" for table names.
You can create a function which can check for uniqueness across multiple tables (example here: Postgres unique combination constraint across tables) but it looks like you may need to the structure of your tables or follow Frank Heikens' answer.
EDIT:
CREATE TABLE public."user"
(
id SERIAL,
username character varying(256) COLLATE pg_catalog."default",
PRIMARY KEY (id)
);
CREATE TABLE public.user_tenant
(
user_id integer NOT NULL,
tenant_id integer NOT NULL,
CONSTRAINT user_fk FOREIGN KEY (user_id)
REFERENCES public."user" (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE CASCADE
);
CREATE OR REPLACE FUNCTION public.check_user_tenant(user_id integer, tenant_id integer)
RETURNS boolean AS
$$
DECLARE b_result boolean;
BEGIN
SELECT (COUNT(*) = 0) INTO b_result
FROM public.user u
JOIN public.user_tenant ut ON ut.user_id IN (SELECT id
FROM public.user i_u
WHERE i_u.username = u.username)
WHERE u.id = $1 AND ut.tenant_id = $2;
RETURN b_result;
END
$$ LANGUAGE 'plpgsql';
ALTER TABLE public.user_tenant
ADD CONSTRAINT check_filename CHECK
(public.check_user_tenant(user_id, tenant_id));
-- Testing:
insert into public."user" (username) VALUES ('foo');
insert into public.user_tenant (user_id, tenant_id) VALUES (1,3);
insert into public."user" (username) VALUES ('foo');
-- Violates constraint:
insert into public.user_tenant (user_id, tenant_id) VALUES (2,3);
I try to create code for postgresql
CREATE TABLE kaart (
kaartid integer NOT NULL,
naam character varying,
saldo real,
kaarthouderid integer
);
CREATE TABLE kaart_product (
kaartkaartid integer,
productid2 integer
);
CREATE TABLE kaarthouder (
id integer NOT NULL,
naam character varying(255),
naw character varying(255),
geslacht "char"
);
CREATE TABLE product (
naam character varying,
id integer NOT NULL
);
ALTER TABLE ONLY kaart
ADD CONSTRAINT kaart_pkey PRIMARY KEY (kaartid);
ALTER TABLE ONLY kaarthouder
ADD CONSTRAINT kaarthouder_pkey PRIMARY KEY (id);
ALTER TABLE ONLY product
ADD CONSTRAINT product_pkey PRIMARY KEY (id);
ALTER TABLE ONLY kaart
ADD CONSTRAINT kaartco FOREIGN KEY (kaartid) REFERENCES kaarthouder(id) ON UPDATE CASCADE ON DELETE CASCADE;
ALTER TABLE ONLY kaart_product
ADD CONSTRAINT kaartkaartidco FOREIGN KEY (kaartkaartid) REFERENCES kaart(kaartid) ON UPDATE CASCADE ON DELETE CASCADE;
ALTER TABLE ONLY kaart_product
ADD CONSTRAINT productidco FOREIGN KEY (kaartkaartid) REFERENCES product(id) ON UPDATE CASCADE ON DELETE CASCADE;
INSERT INTO kaart VALUES (1, 'Sander',50.00 ,1);
INSERT INTO kaart_product VALUES (1,1);
INSERT INTO kaarthouder VALUES (1, 'Sander','test,testing','man');
INSERT INTO product VALUES ('studentenproduct',1);
But whenever i try to run it it gives me this error:
23503: insert or update on table "kaart" violates foreign key constraint "kaartco"
But i really dont know why this happens since it is the same to the other foreign keys that are below it
So can someone help me fix this?
You try to link to a product and a kaart that doesn't exist yet.
Move:
INSERT INTO kaart_product VALUES (1,1);
Two lines down under:
INSERT INTO product VALUES ('studentenproduct',1);
That should do the job for you.
Try to search google for forgein key contstraints and how they work.
I'm using RLS in a multi-tenancy model. No problems for the first several tables I enabled. Then I add RLS to a new table and suddenly I cannot insert a record to that table.
Here is a table that works:
CREATE TABLE wtr.adjustment (
id uuid UNIQUE NOT NULL DEFAULT uuid_generate_v1(),
created_at timestamp NOT NULL DEFAULT current_timestamp,
updated_at timestamp NOT NULL DEFAULT current_timestamp,
vendor_id uuid NOT NULL,
reporting_period_id uuid NOT NULL,
inventory_lot_id uuid NOT NULL,
adjustment_date date NOT NULL,
quantity_delta NUMERIC(50,2) NOT NULL,
adjustment_type wtr.adjustment_type NOT NULL,
comments text,
CONSTRAINT pk_adjustment PRIMARY KEY (id)
);
--||--
GRANT select, insert, update, delete ON TABLE wtr.adjustment TO wtr_user;
--||--
ALTER TABLE wtr.adjustment ADD CONSTRAINT fk_adjustment_vendor FOREIGN KEY ( vendor_id ) REFERENCES wtr.vendor( id );
--||--
ALTER TABLE wtr.adjustment ADD CONSTRAINT fk_adjustment_reporting_period FOREIGN KEY ( reporting_period_id ) REFERENCES wtr.reporting_period( id );
--||--
ALTER TABLE wtr.adjustment ADD CONSTRAINT fk_adjustment_inventory_lot FOREIGN KEY ( inventory_lot_id ) REFERENCES wtr.inventory_lot( id );
--||--
ALTER TABLE wtr.adjustment ENABLE ROW LEVEL SECURITY;
--||--
CREATE POLICY select_adjustment ON wtr.adjustment FOR SELECT
USING (vendor_id = wtr.current_vendor_id());
--||--
CREATE FUNCTION wtr.fn_timestamp_update_adjustment() RETURNS trigger AS $$
BEGIN
NEW.updated_at = current_timestamp;
RETURN NEW;
END; $$ LANGUAGE plpgsql;
--||--
CREATE TRIGGER tg_timestamp_update_adjustment
BEFORE UPDATE ON wtr.adjustment
FOR EACH ROW
EXECUTE PROCEDURE wtr.fn_timestamp_update_adjustment();
--||--
and the associated function that works:
CREATE OR REPLACE FUNCTION wtr.build_adjustment(
_reporting_period_id uuid,
_inventory_lot_gov_id text,
_strain_name text,
_room_name text,
_inventory_type_name text,
_adjustment_date text,
_quantity_delta NUMERIC(50,2),
_adjustment_type wtr.adjustment_type,
_comments text
)
RETURNS wtr.adjustment as $$
DECLARE
_vendor_id uuid;
_inventory_lot wtr.inventory_lot;
_adjustment wtr.adjustment;
_inventory_type_id uuid;
BEGIN
_vendor_id := wtr.current_vendor_id();
_inventory_lot := wtr.find_or_build_existing_inventory_lot(
_strain_name,
_room_name,
_inventory_lot_gov_id,
_inventory_type_name
);
INSERT INTO wtr.adjustment(
vendor_id,
reporting_period_id,
inventory_lot_id,
adjustment_date,
quantity_delta,
adjustment_type,
comments
)
SELECT
_vendor_id,
_reporting_period_id,
_inventory_lot.id,
_adjustment_date::DATE,
_quantity_delta,
_adjustment_type,
_comments
RETURNING *
INTO _adjustment;
RETURN _adjustment;
END;
$$ language plpgsql strict security definer;
--||--
GRANT execute ON FUNCTION wtr.build_adjustment(
uuid,
text,
text,
text,
text,
text,
numeric,
wtr.adjustment_type,
text
) TO wtr_user;
here is the table that fails:
CREATE TABLE wtr.received_inventory_transfer (
id uuid UNIQUE NOT NULL DEFAULT uuid_generate_v1(),
created_at timestamp NOT NULL DEFAULT current_timestamp,
updated_at timestamp NOT NULL DEFAULT current_timestamp,
vendor_id uuid NOT NULL,
reporting_period_id uuid NOT NULL,
inventory_lot_id uuid NOT NULL,
transfer_date DATE NOT NULL,
quantity_received NUMERIC(50,2) NOT NULL,
CONSTRAINT pk_received_inventory_transfer PRIMARY KEY (id)
);
--||--
GRANT select, insert, update, delete ON TABLE wtr.received_inventory_transfer TO wtr_user;
--||--
ALTER TABLE wtr.received_inventory_transfer ADD CONSTRAINT fk_received_inventory_transfer_vendor FOREIGN KEY ( vendor_id ) REFERENCES wtr.vendor( id );
--||--
ALTER TABLE wtr.received_inventory_transfer ADD CONSTRAINT fk_received_inventory_transfer_reporting_period FOREIGN KEY ( reporting_period_id ) REFERENCES wtr.reporting_period( id );
--||--
ALTER TABLE wtr.received_inventory_transfer ADD CONSTRAINT fk_received_inventory_transfer_inventory_lot FOREIGN KEY ( inventory_lot_id ) REFERENCES wtr.inventory_lot( id );
--||--
ALTER TABLE wtr.received_inventory_transfer ENABLE ROW LEVEL SECURITY;
--||--
CREATE POLICY select_received_inventory_transfer ON wtr.received_inventory_transfer FOR SELECT USING (vendor_id = wtr.current_vendor_id());
--||--
CREATE FUNCTION wtr.fn_timestamp_update_received_inventory_transfer() RETURNS trigger AS $$
BEGIN
NEW.updated_at = current_timestamp;
RETURN NEW;
END; $$ LANGUAGE plpgsql;
--||--
CREATE TRIGGER tg_timestamp_update_received_inventory_transfer
BEFORE UPDATE ON wtr.received_inventory_transfer
FOR EACH ROW
EXECUTE PROCEDURE wtr.fn_timestamp_update_received_inventory_transfer();
--||--
and the associated failing function:
CREATE OR REPLACE FUNCTION wtr.build_received_inventory_transfers(
_reporting_period_id uuid,
_transfer_date text
-- _received_inventory_transfers jsonb
)
RETURNS wtr.received_inventory_transfer as $$
DECLARE
_vendor_id uuid;
_inventory_lot wtr.inventory_lot;
_received_inventory_transfer_info jsonb;
_received_inventory_transfer wtr.received_inventory_transfer;
_quantity numeric(50,2);
BEGIN
_vendor_id := wtr.current_vendor_id();
-- this call is currently hard coded for debug purposes
_inventory_lot := wtr.find_or_build_existing_inventory_lot(
'tacos',
'N/A',
'1234123412341234',
'Hash'
);
-- again, this is hard-coded for debug purposes
RAISE EXCEPTION 'v: %, rp: %, il: %, td: %, q: %',
_vendor_id,
_reporting_period_id,
_inventory_lot.id,
_transfer_date::DATE,
20
;
-- this is the call that fails
INSERT INTO wtr.received_inventory_transfer(
vendor_id,
reporting_period_id,
inventory_lot_id,
transfer_date,
quantity_received
)
SELECT
_vendor_id,
_reporting_period_id,
_inventory_lot.id,
_transfer_date::DATE,
20
RETURNING *
INTO _received_inventory_transfer;
RETURN _received_inventory_transfer;
END;
$$ language plpgsql;
--||--
GRANT execute ON FUNCTION wtr.build_received_inventory_transfers(
uuid,
text
-- jsonb
) TO wtr_user;
the current_vendor_id function uses a jwt token claim that is passed in by the server, which is postgraphile.
At the insert statement, the call fails with:
new row violates row-level security policy for table
\"received_inventory_transfer\"",
This info is all that the logs will show me.
I think my real question is - how can I further debug RLS policy? If I only enable RLS, but create no select policy, I get the same failure.
of course, the next thing i figured out:
CREATE POLICY insert_received_inventory_transfer
ON wtr.received_inventory_transfer
FOR INSERT
WITH CHECK (vendor_id = wtr.current_vendor_id());
Table A is a kind of unique sequence for all my tables.
-- Table: public."IdCentral"
-- DROP TABLE public."IdCentral";
CREATE TABLE public."IdCentral"
(
"Id" bigint NOT NULL DEFAULT nextval('"IdCentral_Id_seq"'::regclass),
"Tag" character varying(127) COLLATE pg_catalog."default",
CONSTRAINT "IdCentral_pkey" PRIMARY KEY ("Id")
)
WITH (
OIDS = FALSE
)
TABLESPACE pg_default;
Table B is any table of my database
-- Table: public."Users"
-- DROP TABLE public."Users";
CREATE TABLE public."Users"
(
"Id" bigint NOT NULL,
"Login" character varying(30) COLLATE pg_catalog."default" NOT NULL,
CONSTRAINT "Users_pkey" PRIMARY KEY ("Id"),
CONSTRAINT "PK" FOREIGN KEY ("Id")
REFERENCES public."IdCentral" ("Id") MATCH FULL
ON UPDATE NO ACTION
ON DELETE NO ACTION
)
WITH (
OIDS = FALSE
)
TABLESPACE pg_default;
ALTER TABLE public."Users"
OWNER to dba;
When I want to insert into B, I need to create a new record in A passing the B Table name as Tag.
What you want is CURRVAL:
SELECT CURRVAL('IdCentral_Id_seq');
... which will give you the current value for the ID sequence after insert. For safety, it's best to use it inside a transaction, especially if you're combining it with load-balancing:
BEGIN;
INSERT INTO "a" ...
INSERT INTO "b" VALUES ( CURRVAL('IdCentral_Id_seq', ... )
COMMIT;
That being said, it appears that you're implementing a "universal ID" system for your database. This is something every new DBA tries (I did), and it's inevitably a bad idea which you end up spending a lot of time refactoring out later. If there's some reason you genuinely need some kind of universal ID, consider using a UUID instead.
For Example:
Consider my original tables are:
CREATE TABLE employees(
id serial primary key,
first_name varchar(40) NOT NULL,
last_name varchar(40) NOT NULL
);
CREATE TABLE employees_detail
(
eid integer NOT NULL,
id integer,
first_name character varying(40),
last_name character varying(40),
CONSTRAINT employees_detail_pkey PRIMARY KEY (eid),
CONSTRAINT employees_detail_id_fkey FOREIGN KEY (id)
REFERENCES employees (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
The above tables have foreign key relation. So when I insert a row into both the tables, at the same time I want to insert them into history tables.
consider history tables are:
CREATE TABLE employee_audits (
id serial primary key,
employee_id int4 NOT NULL,
last_name varchar(40) NOT NULL,
changed_on timestamp(6) NOT NULL
)
CREATE TABLE employee_audits_detail
(
eid integer NOT NULL,
id integer,
last_name character varying(40),
changed_on timestamp(6) NOT NULL,
CONSTRAINT employee_audits_detail_pkey PRIMARY KEY (eid),
CONSTRAINT employee_audits_detail_fkey FOREIGN KEY (id)
REFERENCES employee_audits (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
I have created below trigger to insert into original parent table(employees) to history parent table(employee_audits). My trigger is as follows:
CREATE OR REPLACE FUNCTION log_last_name_changes()
RETURNS trigger AS
$BODY$
BEGIN
IF NEW.last_name <> OLD.last_name THEN
INSERT INTO employee_audits(employee_id,last_name,changed_on)
VALUES(OLD.id,OLD.last_name,now());
END IF;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql
CREATE TRIGGER last_name_changes
BEFORE UPDATE
ON employees
FOR EACH ROW
EXECUTE PROCEDURE log_last_name_changes();
Same way I want to insert original child table rows into history child table.