PostgreSQL update from subquery - postgresql

I have a following PostgreSQL table:
CREATE TABLE users (
user_id INTEGER DEFAULT nextval('users_user_id_seq') NOT NULL,
user_old_id CHARACTER VARYING(36),
created_by INTEGER,
created_by_old character varying(36),
last_updated_by INTEGER,
last_updated_by_old character varying(36),
CONSTRAINT users_pkey PRIMARY KEY (user_id)
);
Based on data in this table I need to update:
created_by field with user_id from this table for every row
where created_by_old = user_old_id Please note that created_by_old can be NULL and therefore must be avoided in this case.
last_updated_by field with user_id from this table for every
row where last_updated_by_old = user_old_id Please note that last_updated_by_old can be NULL and therefore must be avoided in this case.
This is a sample data:
Actual:
user_id | user_old_id | created_by | created_by_old | last_updated_by | last_updated_by_old
--------------------------------------------------------------------------------------------
1 | aaa | | ccc | | bbb
--------------------------------------------------------------------------------------------
2 | bbb | | ddd | | aaa
--------------------------------------------------------------------------------------------
3 | ccc | | | | ddd
--------------------------------------------------------------------------------------------
4 | ddd | | aaa | |
Expected:
user_id | user_old_id | created_by | created_by_old | last_updated_by | last_updated_by_old
--------------------------------------------------------------------------------------------
1 | aaa | 3 | ccc | 2 | bbb
--------------------------------------------------------------------------------------------
2 | bbb | 4 | ddd | 1 | aaa
--------------------------------------------------------------------------------------------
3 | ccc | | | 4 | ddd
--------------------------------------------------------------------------------------------
4 | ddd | 1 | aaa | |
I think it can be implemented with a subquery but can't figure out by myself right now how to implement this query. Please help.

If you want a more efficient update statement you can use a derived table that gets the information you want as the source of your update statement:
update users u1
set created_by = t.new_created_by,
last_updated_by = t.new_updated_by
from (
select u2.user_id, u2.user_old_id,
cr.user_id as new_created_by,
lu.user_id as new_updated_by
from users u2
left join users cr on cr.user_old_id = u2.created_by_old
left join users lu on lu.user_old_id = u2.last_updated_by_old
) t
where t.user_id = u1.user_id;
Usually the target table of the update should never be repeated in the from clause, but this case is a rare example where it can't be avoided.
But this will only work properly if user_old_id is also unique, not only user_id.
Online example: http://rextester.com/HKM21985

Not too efficient ,but if that's not an issue you can do this :
UPDATE users u
SET u.created_by = (SELECT t.user_id FROM users t
WHERE u.created_by_old = t.user_old_id),
u.last_updated_by = (SELECT s.user_id FROM users s
WHERE u.last_updated_by = s.user_old_id)

Related

How to select rows based on properties of another row?

Had a question..
| a_id | name | r_id | message | date
_____________________________________________
| 1 | bob | 77 | bob here | 1-jan
| 1 | bob | 77 | bob here again | 2-jan
| 2 | jack | 77 | jack here. | 2-jan
| 1 | bob | 79 | in another room| 3-feb
| 3 | gill | 79 | gill here | 4-feb
These are basically accounts (a_id) chatting inside different rooms (r_id)
I'm trying to find the last chat message for every room that jack a_id = 2 is chatting in.
What i've tried so far is using distinct on (r_id) ... ORDER BY r_id, date DESC.
But this incorrectly gives me the last message in every room instead of only giving the last message in everyroom that jack belongs to.
| 2 | jack | 77 | jack here. | 2-jan
| 3 | gill | 79 | gill here | 4-feb
Is this a partition problem instead distinct on?
I would suggest :
to group the rows by r_id with a GROUP BY clause
to select only the groups where a_id = 2 is included with a HAVING clause which aggregates the a_id of each group : HAVING array_agg(a_id) #> array[2]
to select the latest message of each selected group by aggregating its rows in an array with ORDER BY date DESC and selecting the first element of the array : (array_agg(t.*))[1]
to convert the selected rows into a json object and then displaying the expected result by using the json_populate_record function
The full query is :
SELECT (json_populate_record(null :: my_table, (array_agg(to_json(t.*)))[1])).*
FROM my_table AS t
GROUP BY r_id
HAVING array_agg(a_id) #> array[2]
and the result is :
a_id
name
r_id
message
date
1
bob
77
bob here
2022-01-01
see dbfiddle
For last message in every chat room simply would be:
select a_id, name, r_id, to_char(max(date),'dd-mon') from chats
where a_id =2
group by r_id, a_id,name;
Fiddle https://www.db-fiddle.com/f/keCReoaXg2eScrhFetEq1b/0
Or seeing messages
with last_message as (
select a_id, name, r_id, to_char(max(date),'dd-mon') date from chats
where a_id =1
group by r_id, a_id,name
)
select l.*, c.message
from last_message l
join chats c on (c.a_id= l.a_id and l.r_id=c.r_id and l.date=to_char(c.date,'dd-mon'));
Fiddle https://www.db-fiddle.com/f/keCReoaXg2eScrhFetEq1b/1
Though all this complication could by avoided with a primary key on your table.

ERROR: null value in column " " of relation " " violates not-null constraint DETAIL: Failing row contains (21, null, null, null, ...)

I am new on PostgresQL, and I am experiencing the error in the title.
I have table_a with an empty column, which I want to fill up with data from a column in table_b, upon a match.
table_a:
id | name | town | town_id |
1 | name1 | city1 | - |
2 | name2 | city1 | - |
3 | name3 | city2 | - |
4 | name4 | city2 | - |
5 | name5 | city3 | - |
table_b
id | town_name
1 | city1
2 | city2
3 | city3
I want INSERT the table_b.id INTO table_a.town_id ON town = town_name
INSERT INTO
table_a(town_id)
SELECT
table_b.id
FROM
table_b
JOIN
table_a
ON town = town_name;
Problem is that I get the error in the title, in a row which is not actually existing (e.g.: table_a has 20 rows, but error is on row 21...). What is happening there? It seems it is correctly inserting the data, but then it does not stop at the end of the table, so the not null constraints is triggered.
FIXED:
As suggested by #Frank Heikens I had to use UPDATE
UPDATE table_a
SET town_id = table_b.id
FROM table_b
WHERE town = town_name;

postgres many to one unique constraint

I'm curious if there is a way to write a unique constraint to support the following situation.
Suppose I have table table1 with facts about a user, with four columns:
user_id: unique id for user
source: where the detail came from
d1: dimension 1 of the fact
d2: dimension 2 of the fact
The following is an example of data in this table:
| row_id | user_id | source | d1 | d2 |
|--------|---------|--------|--------|---------|
| 1 | aaa111 | foo | bar | 123 |
| 2 | aaa111 | foo | baz | 'horse' |
| 3 | aaa111 | scrog | bar | 123 |
| 4 | bbb222 | foo | goober | 456 |
Currently, a unique constraint exists on source + d1 + d2. This is good, because it allows the same user to have duplicates of (d1,d2), as long as they have a different source.
Rows #1 and #3 demonstrate this for user aaa111.
However, this constraint does not prevent the following row from getting added...
| row_id | user_id | source | d1 | d2 |
|--------|---------|--------|--------|---------|
| 1 | aaa111 | foo | bar | 123 |
| 2 | aaa111 | foo | baz | 'horse' |
| 3 | aaa111 | scrog | bar | 123 |
| 4 | bbb222 | foo | goober | 456 |
| 5 | bbb222 | turnip | baz | 'horse' | <---- allowed new row
...because source is different for rows #2 and #5.
I would like to add a unique constraint where the combination of (d1,d2) may only exist for a single user_id.
Said another way, a single user can have as many unique (source, d1, d2) combinations as needed, but cannot share (d1,d2) with another user_id.
Is this data model fundamentally flawed to support this constraint? or is there a unique constraint that might help enforce this? Thanks in advance for any suggestions.
It's a conditional-constraint, you can use a trigger BEFORE INSERT OR UPDATE that raise exception when violate the constraint:
CREATE OR REPLACE FUNCTION check_user_combination() RETURNS trigger AS
$$
DECLARE
vCheckUser INTEGER;
BEGIN
SELECT INTO vCheckUser user_id
FROM table1
WHERE d1 = NEW.d1
AND d2 = NEW.d2
AND user_id <> NEW.user_id;
IF vCheckUser IS NOT NULL THEN
RAISE EXCEPTION 'User % have already d1=% and d2=%',vCheckUser,NEW.d1, NEW.d2;
END IF;
RETURN NEW;
END;
$$
language 'plpgsql';
CREATE TRIGGER tr_check_combination BEFORE INSERT OR UPDATE ON table1 FOR EACH ROW EXECUTE PROCEDURE check_user_combination();
This prevent insert or update additional user for the same d1 and d2.

Select all columns from two tables

Lets say I have the following:
table_a
| id | date | order_id | sku | price |
--------------------------------------------
| 10 | 2016-08-18 | 111 | ABC | 10 |
table_b
| id | date | order_id | description | type | notes | valid |
-------------------------------------------------------------------
| 50 | 2016-08-18 | 111 | test | AA | | true |
I want to get get all columns from both tables, so the resulting table looks like this:
| id | date | order_id | sku | price | description | type | notes | valid |
---------------------------------------------------------------------------------
| 10 | 2016-08-18 | 111 | ABC | 10 | | | | |
---------------------------------------------------------------------------------
| 50 | 2016-08-18 | 111 | | | test | AA | | true |
I tried union:
(
SELECT *
from table_a
where table_a.date > Date('today')
)
UNION
(
SELECT *
from table_b
where table_b.date > Date('today')
)
But I get a:
ERROR: each UNION query must have the same number of columns
How can this be fixed / is there another way to do this?
Easily :)
(
SELECT id, date, order_id, sku, price, NULL AS description, NULL AS type, NULL AS notes, NULL AS valid
from table_a
where table_a.date > Date('today')
)
UNION
(
SELECT id, date, order_id, NULL AS sku, NULL AS price, description, type, notes, valid
from table_b
where table_b.date > Date('today')
)
Alternatively, instead of UNION you can just JOIN them:
SELECT *
FROM table_a A
JOIN table_b B USING ( id )
WHERE A.date > TIMESTAMP 'TODAY'
AND B.date > TIMESTAMP 'TODAY';
See more options: https://www.postgresql.org/docs/9.5/static/queries-table-expressions.html#QUERIES-JOIN

Resolve many to many relationship in SQL

I'm using Postgresql. Let's say I have 3 tables:
Classes
id | name
1 | Biology
2 | Math
Students
id | name
1 | John
2 | Jane
Student_Classes
id | student_id | class_id | registration_token
1 | 1 | 1 | abc
2 | 1 | 2 | def
3 | 2 | 1 | zxc
I want to obtain a result set like this:
Results
student_name | biology | math
John | abc | def
Jane | zxc | NULL
I can get this result set with this query:
SELECT
student.name as student_name,
biology.registration_token as biology,
math.registration_token as math
FROM
Students
LEFT JOIN (
SELECT registration_token FROM Student_Classes WHERE class_id = (
SELECT id FROM Classes WHERE name = 'Biology'
)
) AS biology
ON Students.id = biology.student_id
LEFT JOIN (
SELECT registration_token FROM Student_Classes WHERE class_id = (
SELECT id FROM Classes WHERE name = 'Math'
)
) AS math
ON Students.id = math.student_id
Is there a way to get this same result set without having a join statement for each class? With this solution, if I want to add a class, I need to add another join statement.
You can do this via postgresql tablefunc extension crosstab but such presentation requirements may be handled better outside of sql.