I have three tables: clients, items and client_has_item.
CREATE TABLE clients (id_c serial primary key, name text);
CREATE TABLE items (id_i serial primary key, name text);
CREATE TABLE client_has_item
(id_c int references clients (id_c),
id_i int references items (id_i));
INSERT INTO clients (name) values ('client a'), ('client b');
INSERT INTO items (name) values ('item a'), ('item a');
Note that a client can have several items, an item can be owned by several clients and names are not unique.
If I want to insert some items for one client (1, 'client a') it is easy:
WITH inserted_items as (
INSERT INTO items (name) values ('item a'), ('item b')
RETURNING id_i
)
INSERT INTO client_has_item (id_c, id_i) select 1, id_i from inserted_items
My question is how to do this for several clients. Ideally I would do:
WITH inserted_items as (
INSERT INTO items (name) SELECT new_items.item_name from (values (1, 'item a'), (2, 'item b')) new_items (id_client, item_name)
RETURNING items.id_i, new_items.id_client
)
INSERT INTO client_has_item (id_c, id_i) select id_client, id_i from inserted_items
But I get the following error:
RETURNING items.id_i, new_items.id_client
********** Error **********
ERROR: missing FROM-clause entry for table "new_items"
What would be the best alternative? Of course I could do a loop, but I'd like to avoid that.
Related
I have a database with 2 tables, foo and foo_trash.
Both have the same structure with an id (primary key), and a title. foo_trash gets populated with data copied from foo with a statement like this:
INSERT INTO foo_trash (SELECT * FROM foo WHERE id = 253)
I would like to add a constraint on the table foo_trash so that no rows may be inserted into foo_trash if the same pair id and title is not present in foo.
How do I write that?
Given the table foo:
create table foo (
id int,
title varchar(50),
primary key (id, title)
);
Define the table foo_trash to reference the two columns you mentioned:
create table foo_trash (
id int primary key,
title varchar(50),
FOREIGN KEY (id, title) REFERENCES foo (id, title)
);
Now you can insert data into foo:
insert into foo values (1, 'title1');
insert into foo values (2, 'title2');
insert into foo values (3, 'title3');
insert into foo values (253, 'title253');
If you try to insert a row into foo_trash that doesn't exist in foo, you will receive an error:
insert into foo_trash values (4, 'title4');
Output:
ERROR: insert or update on table "foo_trash" violates foreign key constraint "foo_trash_id_title_fkey"
DETAIL: Key (id, title)=(4, title4) is not present in table "foo".
You can insert a row in foo_trash that exists in foo:
insert into foo_trash values (3, 'title3');
And you can do your insert into foo_trash as select from foo successfully, assuming that id exists:
INSERT INTO foo_trash (SELECT * FROM foo WHERE id = 253);
I am trying to insert (clone) some records in a table and need to get source ids and ids that got generated. This simplified example demonstrates my issue. After new records are created, referencing their ids in a SELECT produces no results even though records do get created and subsequent SELECT on the table shows them. It feels like insert and select are happening in different transaction scopes.
CREATE TABLE tbl_value(
id int4 NOT NULL GENERATED ALWAYS AS identity PRIMARY KEY,
some_id INTEGER NOT NULL,
value VARCHAR NOT NULL
);
INSERT INTO tbl_value(some_id, value) VALUES(1000, 'value 1'), (1000, 'value 2'), (1000, 'value 3');
with
outer_input as
(
select id, some_id, value from tbl_value where id in (1,2)
),
inner_insert as
(
INSERT INTO tbl_value(some_id, value)
select 2000, value from outer_input
returning id
)
select * from tbl_value v inner join inner_insert i on v.id = i.id;
I'm trying to construct an object for use from my postgres backend. The tables in question look something like this:
We have some Things that essentially act as rows for a matrix where the columns are Field_Columns. Field_Values are filled cells.
Create Table Platform_User (
serial id PRIMARY KEY
)
Create Table Things (
serial id PRIMARY KEY,
INTEGER user_id REFERENCES Platform_User(id)
)
Create Table Field_Columns (
serial id PRIMARY KEY,
TEXT name,
)
Create Table Field_Values (
INTEGER field_column_id REFERENCES Field_Columns(id),
INTEGER thing_id REFERENCES Things(id)
TEXT content,
PRIMARY_KEY(field_column_id, thing_id)
)
This would be simple if I were trying to load just the Field_Values for a single Thing as JSON, which would look like this:
SELECT JSONB_OBJECT(
ARRAY(
SELECT name
FROM Field_Columns
ORDER BY Field_Columns.id
),
ARRAY(
SELECT Field_Values.content
FROM Fields_Columns
LEFT JOIN Field_Values ON Field_Values.field_column_id = Field_Columns.id
AND Field_Values.thing_id = Things.id
ORDER BY Field_Columns.id)
)
)
FROM Things
WHERE Thing.id = $1
however, I'd like to construct the JSON object to look like this when returned. I want to get an object of all the Fields:Field_Values objects for the Things that a user owns
{
14:
{
'first field':'asdf',
'other field':''
}
25:
{
'first field':'qwer',
'other field':'dfgdsfg'
}
43:
{
'first field':'',
'other field':''
}
}
My efforts to construct this query look like this, but I'm running into the problem where the JSONB object function doesn't want to construct an object where the value of the field is an object itself
SELECT (
JSONB_OBJECT(
ARRAY(SELECT Things.id::TEXT
FROM Things
WHERE Things.user_id = $2
ORDER BY Things.id
),
ARRAY(SELECT JSONB_OBJECT(
ARRAY(
SELECT name
FROM Field_Columns
ORDER BY Field_Columns.id),
ARRAY(
SELECT Field_Values.content
FROM Field_Columns
LEFT JOIN Field_Values ON Field_Values.field_column_Id = Field_Columns.id
AND Field_Values.thing_id = Things.id
ORDER BY Field_Columns.id)
)
FROM Things
WHERE Things.user_id = $2
ORDER BY Things.id
)
)
) AS thing_fields
The specific error I get is function jsonb_object(text[], jsonb[]) does not exist. Is there a way to do this that doesn't involve copious text conversions and nonsense like that? Or will I just need to abandon trying to sort my data in the query and do it in my code instead.
Your DDL scripts are syntactically incorrect so I created these for you:
create table platform_users (
id int8 PRIMARY KEY
);
create table things (
id int8 PRIMARY KEY,
user_id int8 REFERENCES platform_users(id)
);
create table field_columns (
id int8 PRIMARY KEY,
name text
);
create table field_values (
field_column_id int8 REFERENCES field_columns(id),
thing_id int8 REFERENCES things(id),
content text,
PRIMARY KEY(field_column_id, thing_id)
);
I also created some scripts to populate the db:
insert into platform_users(id) values (1);
insert into platform_users(id) values (2);
insert into platform_users(id) values (3);
insert into platform_users(id) values (4);
insert into platform_users(id) values (5);
insert into things(id, user_id) values(1, 1);
insert into things(id, user_id) values(2, 1);
insert into things(id, user_id) values(3, 2);
insert into things(id, user_id) values(4, 2);
insert into field_columns(id, name) values(1, 'col1');
insert into field_columns(id, name) values(2, 'col2');
insert into field_values(field_column_id, thing_id, content) values(1, 1, 'thing1 val1');
insert into field_values(field_column_id, thing_id, content) values(2, 1, 'thing1 val2');
insert into field_values(field_column_id, thing_id, content) values(1, 2, 'thing2 val1');
insert into field_values(field_column_id, thing_id, content) values(2, 2, 'thing2 val2');
Please include such scripts next time when you ask for help, and make sure that your scripts are correct. This will reduce the work needed to answer your question.
You can get your jsonb value by aggregating the key value pairs with jsonb_object_agg
select
t.id,
jsonb_object_agg(fc.name, fv.content)
from
things t inner join
field_values fv on fv.thing_id = t.id inner join
field_columns fc on fv.field_column_id = fc.id
group by 1
The results looking like this:
thing_id;jsonb_value
1;"{"col1": "thing1 val1", "col2": "thing1 val2"}"
2;"{"col1": "thing2 val1", "col2": "thing2 val2"}"
I have table tariffs, with two columns: (tariff_id, reception)
I have table users, with two columns: (user_id, reception)
And I have table users_tariffs with two columns: (user_id, tariff_id).
I want to prevent situation when tariff from one reception is assigned to user from another reception. How can I do that?
E.G
Users:
user_id | reception
Putin | Russia
Trump | USA
Tariffs:
tariff_id | reception
cheap | USA
expensive | Russia
Wrong situation at users_tariffs, because Cheap tariff is for USA only:
user_id | tariff_id
Putin | Cheap
SOLUTION 1: FOREIGN KEY CONSTRAINTS
I am assuming the following table definitions.
In particular, the composite key in user_tariffs makes this a many-to-many relationship between users and tariffs.
CREATE TABLE tariffs (tariff_id int NOT NULL PRIMARY KEY,
reception text NOT NULL);
CREATE TABLE users (user_id int NOT NULL PRIMARY KEY,
reception text NOT NULL);
CREATE TABLE user_tariffs (tariff_id int NOT NULL REFERENCES tariffs (tariff_id),
user_id int NOT NULL REFERENCES users (user_id),
PRIMARY KEY (tariff_id, user_id));
You probably need a combination of all three columns somewhere, so let's create this:
ALTER TABLE user_tariffs ADD COLUMN reception text;
UPDATE user_tariffs a
SET reception = b.reception
FROM (SELECT * FROM tariffs) b
WHERE a.tariff_id = b.tariff_id;
ALTER TABLE user_tariffs ALTER COLUMN reception SET NOT NULL;
Now we can use FOREIGN KEY REFERENCES (user_id, reception) into users.
CREATE UNIQUE INDEX ON tariffs (tariff_id, reception);
ALTER TABLE user_tariffs ADD FOREIGN KEY (tariff_id, reception)
REFERENCES tariffs (tariff_id, reception);
In addition, we can use FK REFs (tariff_id, reception) into tariffs.
CREATE UNIQUE INDEX ON users (user_id, reception);
ALTER TABLE user_tariffs ADD FOREIGN KEY (user_id, reception)
REFERENCES users (user_id, reception);
Populate with data:
INSERT INTO users VALUES (1, 'cheap'), (2, 'expensive');
INSERT INTO tariffs VALUES (1, 'cheap'), (2, 'expensive');
Now assume we have the following data (user_id, tariff_id) to insert:
WITH data (user_id, tariff_id)
AS (VALUES (1, 2), (2, 1)), -- here is your application data
datas (user_id, tariff_id, reception)
AS (SELECT user_id,
tariff_id,
(SELECT u.reception -- reception calculated by user
FROM users u
WHERE u.user_id = d.user_id)
FROM data d)
INSERT INTO user_tariffs SELECT * FROM datas ;
Then you cannot insert the data, because you can only add (1, 1) or (2, 2) with the same reception, but not (1, 2) or (2, 1) with different reception's. The error message is:
ERROR: insert or update on table "user_tariffs" violates foreign key constraint "user_tariffs_user_id_fkey1"
DETAIL: Key (user_id, reception)=(2, cheap) is not present in table "users".
But you can insert with data AS VALUES (1, 1), (2, 2).
I think the FOREIGN KEY CONSTRAINT solution is to be preferred.
Please describe your functional dependencies, if you want better table designs.
SOLUTION 2: TRIGGER
-- DROP TABLE user_tariffs CASCADE;
-- DROP TABLE users CASCADE;
-- DROP TABLE tariffs CASCADE;
CREATE TABLE tariffs (tariff_id int NOT NULL PRIMARY KEY,
reception text NOT NULL);
CREATE TABLE users (user_id int NOT NULL PRIMARY KEY,
reception text NOT NULL);
CREATE TABLE user_tariffs (tariff_id int NOT NULL REFERENCES tariffs (tariff_id),
user_id int NOT NULL REFERENCES users (user_id),
PRIMARY KEY (tariff_id, user_id));
INSERT INTO users VALUES (1, 'cheap'), (2, 'expensive');
INSERT INTO tariffs VALUES (1, 'cheap'), (2, 'expensive');
-- table user_tariffs (user_id, tariff_id) only, without reception column.
Create a function with return type trigger:
CREATE OR REPLACE FUNCTION check_reception()
RETURNS trigger AS $$
DECLARE valid boolean := false;
BEGIN
SELECT (SELECT u.reception FROM users u WHERE u.user_id = NEW.user_id)
= (SELECT t.reception FROM tariffs t WHERE t.tariff_id = NEW.tariff_id)
INTO valid FROM user_tariffs ;
IF valid = false
THEN RAISE EXCEPTION '(user, tariff, reception) invalid.';
END IF;
RETURN NEW;
END; $$ LANGUAGE plpgsql ;
and register it:
CREATE TRIGGER reception_trigger
AFTER INSERT OR UPDATE ON user_tariffs
FOR EACH ROW EXECUTE PROCEDURE check_reception();
Now try to insert (1, 2), which would be (cheap, expensive) and is not allowed:
INSERT INTO user_tariffs VALUES (1, 2);
ERROR: (user, tariff, reception) invalid.
KONTEXT: PL/pgSQL function check_reception() line 7 at RAISE
But we can insert (1, 1), which is (cheap, cheap) without problem:
INSERT INTO user_tariffs VALUES (1, 1);
SELECT * FROM user_tariffs;
Remark
Triggers are not the best solution here, in my opinion. Try to avoid triggers, if possible. They can have side effects (transactions etc). Check StackOverflow for further details :)
I have the following three tables:
Please note that the below DDL came models generated by Django then grabbed out of Postgresql after they were created. So modifying the tables is not an option.
CREATE TABLE "parentTeacherCon_grade"
(
id INTEGER PRIMARY KEY NOT NULL,
"currentGrade" VARCHAR(2) NOT NULL
);
CREATE TABLE "parentTeacherCon_parent"
(
id INTEGER PRIMARY KEY NOT NULL,
name VARCHAR(50) NOT NULL,
grade_id INTEGER NOT NULL
);
CREATE TABLE "parentTeacherCon_teacher"
(
id INTEGER PRIMARY KEY NOT NULL,
name VARCHAR(50) NOT NULL
);
CREATE TABLE "parentTeacherCon_teacher_grade"
(
id INTEGER PRIMARY KEY NOT NULL,
teacher_id INTEGER NOT NULL,
grade_id INTEGER NOT NULL
);
ALTER TABLE "parentTeacherCon_parent" ADD FOREIGN KEY (grade_id) REFERENCES "parentTeacherCon_grade" (id);
CREATE INDEX "parentTeacherCon_parent_5c853be8" ON "parentTeacherCon_parent" (grade_id);
CREATE INDEX "parentTeacherCon_teacher_5c853be8" ON "parentTeacherCon_teacher" (grade_id);
ALTER TABLE "parentTeacherCon_teacher_grade" ADD FOREIGN KEY (teacher_id) REFERENCES "parentTeacherCon_teacher" (id);
ALTER TABLE "parentTeacherCon_teacher_grade" ADD FOREIGN KEY (grade_id) REFERENCES "parentTeacherCon_grade" (id);
CREATE UNIQUE INDEX "parentTeacherCon_teacher_grade_teacher_id_20e07c38_uniq" ON "parentTeacherCon_teacher_grade" (teacher_id, grade_id);
CREATE INDEX "parentTeacherCon_teacher_grade_d9614d40" ON "parentTeacherCon_teacher_grade" (teacher_id);
CREATE INDEX "parentTeacherCon_teacher_grade_5c853be8" ON "parentTeacherCon_teacher_grade" (grade_id);
My Question is: How do I write an insert statement (or statements) where I do not have keep track of the IDs? More specifically I have a teacher table, where teachers can teach relate to more than one grade and I am attempting to write my insert statements to start populating my DB. Such that I am only declaring a teacher's name, and grades they relate to.
For example, if I have a teacher that belong to only one grade then the insert statement looks like this.
INSERT INTO "parentTeacherCon_teacher" (name, grade_id) VALUES ('foo bar', 1 );
Where grades K-12 are enumerated 0,12
But Need to do something like (I realize this does not work)
INSERT INTO "parentTeacherCon_teacher" (name, grade_id) VALUES ('foo bar', (0,1,3) );
To indicate that this teacher relates to K, 1, and 3 grades
leaving me with this table for the parentTeacherCon_teacher_grade
+----+------------+----------+
| id | teacher_id | grade_id |
+----+------------+----------+
| 1 | 3 | 0 |
| 2 | 3 | 1 |
| 3 | 3 | 3 |
+----+------------+----------+
This is how I can currently (successfully) insert into the Teacher Table.
INSERT INTO public."parentTeacherCon_teacher" (id, name) VALUES (3, 'Foo Bar');
Then into the grade table
INSERT INTO public.parentTeacherCon_teacher_grade (id, teacher_id, grade_id) VALUES (1, 3, 0);
INSERT INTO public.parentTeacherCon_teacher_grade (id, teacher_id, grade_id) VALUES (2, 3, 1);
INSERT INTO public.parentTeacherCon_teacher_grade (id, teacher_id, grade_id) VALUES (3, 3, 3);
A bit more information.
Here is a diagram of the database
Other things I have tried.
WITH i1 AS (INSERT INTO "parentTeacherCon_teacher" (name) VALUES ('foo bar')
RETURNING id) INSERT INTO "parentTeacherCon_teacher_grade"
SELECT
i1.id
, v.val
FROM i1, (VALUES (1), (2), (3)) v(val);
Then I get this error.
[2016-08-10 16:07:46] [23502] ERROR: null value in column "grade_id" violates not-null constraint
Detail: Failing row contains (6, 1, null).
If you want to insert all three rows in one statement, you can use:
INSERT INTO "parentTeacherCon_teacher" (name, grade_id)
SELECT 'foo bar', g.grade_id
FROM (SELECT 0 as grade_id UNION ALL SELECT 1 UNION ALL SELECT 3) g;
Or, if you prefer:
INSERT INTO "parentTeacherCon_teacher" (name, grade_id)
SELECT 'foo bar', g.grade_id
FROM (VALUES (0), (2), (3)) g(grade_id);
EDIT:
In Postgres, you can have data modification statements as a CTE:
WITH i as (
INSERT INTO public."parentTeacherCon_teacher" (id, name)
VALUES (3, 'Foo Bar')
RETURNING *
)
INSERT INTO "parentTeacherCon_teacher" (name, teacher_id, grade_id)
SELECT 'foo bar', i.id, g.grade_id
FROM (VALUES (0), (2), (3)) g(grade_id) CROSS JOIN
i