I'm using Postresql 9.3.5. I have a many-to-many relationship between entities "Foo" and "Bar" that I've modeled as something like:
CREATE TABLE Foo
(
id SERIAL PRIMARY KEY NOT NULL,
.... various columns for foo ....
);
CREATE TABLE Bar
(
id SERIAL PRIMARY KEY NOT NULL,
field1 varchar(50) UNIQUE NOT NULL,
.... various columns for bar ....
);
CREATE TABLE FooBar
(
fooID int NOT NULL,
barID int NOT NULL,
PRIMARY KEY (fooID, barID),
FOREIGN KEY (fooID) REFERENCES Foo(id),
FOREIGN KEY (barID) REFERENCES Bar(id)
);
Now what I want to do is insert a record into Foo, insert a corresponding record into Bar, and then insert the matching FooBar record containing the ids of the foo & bar entries.
Catch: I don't know when I go to insert the Bar records if they already exist, so currently my insert for Bar looks something like:
INSERT INTO Bar(field1, .... other fields for Bar....)
SELECT 'value1', .... other values for the insert....
WHERE NOT EXISTS (
SELECT 1 FROM Bar WHERE field1 = 'value1')
Which works fine, but my question: how do I get the id of the newly inserted (or existing) Bar record so that I can insert it into the FooBar table?
This seems to work, although it is far from elegant and is probably very inefficient:
WITH new AS (
INSERT INTO bar(field1)
SELECT ('aaa') WHERE NOT EXISTS (
SELECT 1 FROM bar WHERE field1='aaa'
) RETURNING id
),
existing AS (
SELECT id FROM bar WHERE field1='aaa'
)
SELECT id FROM existing UNION SELECT id FROM new
I imagine this would be inefficient due to repeated searches in bar for the matching value. A more efficient solution might be to write a stored procedure.
Try This
INSERT INTO Bar(field1, field2,etc...) values(value1, value2,etc...) RETURNING id;
SQL FIDDLE
Related
I want to create the following tables (simplified to the keys for example):
CREATE TABLE a (
TestVer VARCHAR(50) PRIMARY KEY,
TestID INT NOT NULL
);
CREATE TABLE b (
RunID SERIAL PRIMARY KEY,
TestID INT NOT NULL
);
Where TestID is not unique, but I want table b's TestID to only contain values from table a's `TestID'.
I'm fairly certain I can't make it a foreign key, as the target of a foreign key has to be either a key or unique, and found that supported by this post.
It appears possible with Triggers according to this post where mine on insert would look something like:
CREATE TRIGGER id_constraint FOR b
BEFORE INSERT
POSITION 0
AS BEGIN
IF (NOT EXISTS(
SELECT TestID
FROM a
WHERE TestID = NEW.TestID)) THEN
EXCEPTION my_exception 'There is no Test with id=' ||
NEW.TestID;
END
But I would rather not use a trigger. What are other ways to do this if any?
A trigger is the only way to continuously maintain such a constraint, however you can delete all unwanted rows as part of a query that uses table b:
with clean_b as (
delete from b
where not exists (select from a where a.TestID = b.TestID)
)
select *
from b
where ...
I'd like to have column constraint based combination of 2 columns. I don't find the way to use foreign key here, because it should be conditional FK, then. Hope this basic SQL shows the problem:
CREATE TABLE performer_type (
id serial primary key,
type varchar
);
INSERT INTO performer_type ( id, type ) VALUES (1, 'singer'), ( 2, 'band');
CREATE TABLE singer (
id serial primary key,
name varchar
);
INSERT INTO singer ( id, name ) VALUES (1, 'Robert');
CREATE TABLE band (
id serial primary key,
name varchar
);
INSERT INTO band ( id, name ) VALUES (1, 'Animates'), ( 2, 'Zed Leppelin');
CREATE TABLE gig (
id serial primary key,
performer_type_id int default null, /* FK, no problem */
performer_id int default null /* want FK based on previous FK, no good solution so far */
);
INSERT INTO gig ( performer_type_id, performer_id ) VALUES ( 1,1 ), (2,1), (2,2), (1,2), (2,3);
Now, the last INSERT works, but for last 2 value pairs I'd like it fail, because there is no singer ID 2 nor band ID 3. How to set such constraint?
I already asked similar question in Mysql context and only solution was to use trigger. Problem with trigger was: you can't have dynamic list of types and table set. I'd like to add types (and related tables) on the fly.
I also found very promising pattern, but this is upside down for me, I did not figured out, how to turn it to work in my case.
What I am looking here seems to me so useful pattern, I think there must be some common way for it. Is it?
Edit.
Seems, I choose bad items in my examples, so I try make it clear: different performer tables (singer and band) have NO relation between them. gig-table just has to list tasks for different performers, without setting any relations between them.
Another example would items in stock: I may have item_type-table, which defines hundreds of item-types with related tables (for example, orange and house), and there should be table stock which enlists all appearances of items.
PostgreSQL I use is 9.6
Based on #Laurenz Albe answer I form a solution for example above. Main difference: there is parent table performer, which PK is FK/PK for specific performer-tables and is referenced also from gig table.
CREATE TABLE performer_type (
id serial primary key,
type varchar
);
INSERT INTO performer_type ( id, type ) VALUES (1, 'singer' ), ( 2, 'band' );
CREATE TABLE performer (
id serial primary key,
performer_type_id int REFERENCES performer_type(id)
);
CREATE TABLE singer (
id int primary key REFERENCES performer(id),
name varchar
);
INSERT INTO performer ( performer_type_id ) VALUES (1); -- get PK 1 for next statement
INSERT INTO singer ( id, name ) VALUES (1, 'Robert');
CREATE TABLE band (
id int primary key REFERENCES performer(id),
name varchar
);
INSERT INTO performer ( performer_type_id ) VALUES (2); -- get PK 2 for next statement
INSERT INTO singer ( id, name ) VALUES (2, 'Animates');
INSERT INTO performer ( performer_type_id ) VALUES (2); -- get PK 3 for next statement
INSERT INTO singer ( id, name ) VALUES (3, 'Zed Leppelin');
CREATE TABLE gig (
id serial primary key,
performer_id int REFERENCES performer(id)
);
INSERT INTO gig ( performer_id ) VALUES (1), (2), (3), (4);
And the last INSERT fails, as expected:
ERROR: insert or update on table "gig" violates foreign key constraint "gig_performer_id_fkey"
DETAIL: Key (performer_id)=(4) is not present in table "performer".
But
For me there is annoying problem: I have no good way to make distinction which ID is for singer and which for band etc. (in original example I had performer_type_id in gig-table for that), because any performer_id may belong any performer. So I'd like any performer type has it's own ID range, so I create dummy table for every sequence
CREATE TABLE band_id (
id int primary key,
dummy boolean default null
);
CREATE SEQUENCE band_id_seq START 1;
ALTER TABLE band_id ALTER COLUMN id SET DEFAULT nextval('band_id_seq');
CREATE TABLE singer_id (
id int primary key,
dummy boolean default null
);
CREATE SEQUENCE singer_id_seq START 2000000;
ALTER TABLE singer_id ALTER COLUMN id SET DEFAULT nextval('singer_id_seq');
Now, to insert new row into specific perfomer table I have to get next ID for it:
INSERT INTO band_id (dummy) VALUES (NULL);
Trying to figure out, is it possible to solve this process on DB level, or has something to done in App-level. It would be nice, if inserting into band table could:
before trigger inserting into band_id to genereate specific ID
before trigger inserting this new ID into performer-table
include this new ID into INSERT into band
Frist 2 points are easy, but the last point is not clear for now.
I have Table A. Table A owns a sequence.
I create Table B, inheriting from Table A.
Table A and B now use the same default value for their primary key column.
For a simplified example, Table A is "person", and B is "bulk_upload_person".
CREATE TABLE "testing"."person" (
"person_id" serial, --Resulting DDL: int4 NOT NULL DEFAULT nextval('person_person_id_seq'::regclass)
"public" bool NOT NULL DEFAULT false
);
--SQL Ran
CREATE TABLE "testing"."bulk_upload_person" (
"upload_id" int4 NOT NULL
)
INHERITS ("testing"."person");
--Resulting DDL
CREATE TABLE "testing"."bulk_upload_person" (
"person_id" int4 NOT NULL DEFAULT nextval('person_person_id_seq'::regclass),
"public" bool NOT NULL DEFAULT false,
"upload_id" int4 NOT NULL
)
INHERITS ("testing"."person");
For table A, I can get the sequence by using pg_get_table_serial_seqence.
How can I get and then set the next value of the sequence if I only know about Table B? I want to add n to the value.
I need to do this in order to populate multiple related objects at once, while being able to know what primary IDs they will have, rather than having to query the tables I've just populated to determine the IDs.
By populate, I mean inserting multiple rows in one statement.
insert into "testing"."bulk_upload_person" ( "person_id", "public", "upload_id") values ( '1', 'f', '1'), ( '2', 't', '1'); --etc
I think our situation is similar to https://stackoverflow.com/a/8007835/89211 but we don't want to keep the lock on the table beyond getting and setting the next value of the serial for each table.
Currently we are doing this by getting the name of the sequence by regexing the default value of the primary key for Table B, but it feels like there's probably a better way to do this that we don't realise.
I'm still a newbie.
I created db like this :
DROP DATABASE IF EXISTS image_store_db;
CREATE DATABASE image_store_db;
\c image_store_db;
CREATE TABLE categories_images (
categories_images_id SERIAL PRIMARY KEY,
title VARCHAR NOT NULL,
image_url VARCHAR NOT NULL,
design_url VARCHAR NOT NULL
);
CREATE TABLE images (
images_id SERIAL PRIMARY KEY,
title VARCHAR NOT NULL,
rating REAL NOT NULL,
image_url VARCHAR NOT NULL,
desc_short TEXT NOT NULL,
desc_full TEXT NOT NULL
);
CREATE TABLE ref_categories_images (
categories_images_id integer REFERENCES categories_images (categories_images_id) ON UPDATE CASCADE,
images_id integer REFERENCES images (images_id) ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT ref_categories_images_pkey PRIMARY KEY (images_id, categories_images_id)
);
INSERT INTO categories_images(title, image_url, design_url)
VALUES ('SIMPLE TITLE TEST', '/TEST_URL.PNG', '/TEST_URL.PNG');
INSERT INTO images(title, rating, image_url, desc_short, desc_full)
VALUES ('SIMPLE TITLE TEST', 4.5, '/TEST_URL.PNG', 'TEST_SHORT', 'TEST_FULL');
Pls, help. Teach me, how write a query : insert (for image in category) and select (image from category id) and etc ...
pls ...
My answer assumes that categories_images is the table of categories and images is the table of images. The table and attribute names seem to suggest that the concept of many-to-many join is not all clear to you yet; I would have called the three tables category, image and category_image_map.
For INSERT: if your problem are the serial primary keys, use INSERT ... RETURNING.
You can insert into all three tables in a single statement:
WITH im(im_id) AS (
INSERT INTO categories_images ...
RETURNING categories_images_id
),
cat(cat_id) AS (
INSERT INTO images ...
RETURNING images_id
)
INSERT INTO ref_categories_images (categories_images_id, images_id)
VALUES ((SELECT cat_id FROM cat), (SELECT im_id FROM im));
For the query, you just join the three tables:
SELECT ...
FROM categories_images c
JOIN ref_categories_images r
ON r.categories_images_id = c.categories_images_id
JOIN images i
ON r.images_id = i.images_id
and add a WHERE clause for your condition, for example
WHERE c.categories_images_id = 42
or
WHERE i.image_title = 'Mona Lisa'
I have two tables, connected in E/R by a is-relation. One representing the "mother table"
CREATE TABLE PERSONS(
id SERIAL NOT NULL,
name character varying NOT NULL,
address character varying NOT NULL,
day_of_creation timestamp NOT NULL DEFAULT current_timestamp,
PRIMARY KEY (id)
)
the other representing the "child table"
CREATE TABLE EMPLOYEES (
id integer NOT NULL,
store character varying NOT NULL,
paychecksize integer NOT NULL,
FOREIGN KEY (id)
REFERENCES PERSONS(id),
PRIMARY KEY (id)
)
Now those two tables are joined in a view
CREATE VIEW EMPLOYEES_VIEW AS
SELECT
P.id,name,address,store,paychecksize,day_of_creation
FROM
PERSONS AS P
JOIN
EMPLOYEES AS E ON P.id = E.id
I want to write either a rule or a trigger to enable a db user to make an insert on that view, sparing him the nasty details of the splitted columns into different tables.
But I also want to make it convenient, as the id is a SERIAL and the day_of_creation has a default value there is no actual need that a user has to provide those, therefore a statement like
INSERT INTO EMPLOYEES_VIEW (name, address, store, paychecksize)
VALUES ("bob", "top secret", "drugstore", 42)
should be enough to result in
PERSONS
id|name|address |day_of_creation
-------------------------------
1 |bob |top secret| 2013-08-13 15:32:42
EMPLOYEES
id| store |paychecksize
---------------------
1 |drugstore|42
A basic rule would be easy as
CREATE RULE EMPLOYEE_VIEW_INSERT AS ON INSERT TO EMPLOYEE_VIEW
DO INSTED (
INSERT INTO PERSONS
VALUES (NEW.id,NEW.name,NEW.address,NEW.day_of_creation),
INSERT INTO EMPLOYEES
VALUES (NEW.id,NEW.store,NEW.paychecksize)
)
should be sufficient. But this will not be convenient as a user will have to provide the id and timestamp, even though it actually is not necessary.
How can I rewrite/extend that code base to match my criteria of convenience?
Something like:
CREATE RULE EMPLOYEE_VIEW_INSERT AS ON INSERT TO EMPLOYEES_VIEW
DO INSTEAD
(
INSERT INTO PERSONS (id, name, address, day_of_creation)
VALUES (default,NEW.name,NEW.address,default);
INSERT INTO EMPLOYEES (id, store, paychecksize)
VALUES (currval('persons_id_seq'),NEW.store,NEW.paychecksize)
);
That way the default values for persons.id and persons.day_of_creation will be the default values. Another option would have been to simply remove those columns from the insert:
INSERT INTO PERSONS (name, address)
VALUES (NEW.name,NEW.address);
Once the rule is defined, the following insert should work:
insert into employees_view (name, address, store, paychecksize)
values ('Arthur Dent', 'Some Street', 'Some Store', 42);
Btw: with a current Postgres version an instead of trigger is the preferred way to make a view updateable.