PostgreSQL: Delete a row and all of it's references (FK) existing in others tables? - sql

Let's say i have a table named "users" like this:
+----+------------+-----------+
| id | first_name | last_name |
+----+------------+-----------+
| 1 | 'Sid' | 'Barrett' |
| 2 | 'Roger' | 'Waters' |
| 3 | 'Richard' | 'Wright' |
| 4 | 'David' | 'Gilmour' |
| 5 | 'Nick' | 'Mason' |
+----+------------+-----------+
Each "user" have lots of references on several tables with the same column name "user_id" as a FK.
If i need to delete an element i believe the procedure is to delete the relations first (to avoid the "violates foreign key constraint" error) and then the element itself in "users" table, right?
But... Is there any possibility to delete a "user" element with all its references on all the others tables?
I'm working with NodeJS (Express) and using PostgreSQL.
Please apologize if the answer is obvious, I'm a newbie in SQL.
Thanks a lot in advise!!

Yes, use on delete cascade on the foreign keys.
create table users (
id bigserial primary key
...
);
create table posts (
...
user_id bigint not null references users on delete cascade
...
)
Now when a user is deleted, all their associated posts will also be deleted.
This will go on, for example, if a post has comments...
create table comments (
...
post_id biging not null references posts on delete cascade
...
)
When a user is deleted their posts will be deleted, and those posts' comments will be deleted. That's the "cascade" part.

Related

SQL N:M query merging results by condition flag in intermediate table

[First of all, if this is a duplicate, sorry, I couldn't find a response for this, as this is a strange solution for a limitation on an ORM and I'm clearly a noobie on SQL]
Domain requirements:
A brigades must be composed by one user (the commissar one) and, optionally, one and only one assistant (1:1)
A user can only be part of one brigade (1:1)
CREATE TABLE Users
(
id SERIAL PRIMARY KEY,
username VARCHAR(100) NOT NULL UNIQUE,
password VARCHAR(100) NOT NULL
);
CREATE TABLE Brigades
(
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL
);
-- N:M relationship with a flag inside which determine if that user is a commissar or not
CREATE TABLE Brigade_User
(
brigade_id INT NOT NULL REFERENCES Brigades(id)
ON DELETE CASCADE
ON UPDATE CASCADE,
user_id INT NOT NULL REFERENCES Users(id)
ON DELETE CASCADE
ON UPDATE CASCADE,
is_commissar BOOLEAN NOT NULL
PRIMARY KEY(brigade_id, user_id)
);
Ideally, as relations are 1:1, Brigade_User intermediate table could be erased and a Brigade table with two foreign keys could be created instead (this is not supported by Diesel Rust ORM, so I think I'm coupled to first approach)
CREATE TABLE Brigades
(
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL
-- 1:1
commisar_id INT NOT NULL REFERENCES Users(id)
ON DELETE CASCADE
ON UPDATE CASCADE,
-- 1:1
assistant_id INT NOT NULL REFERENCES Users(id)
ON DELETE CASCADE
ON UPDATE CASCADE
);
An example...
> SELECT * FROM brigade_user LEFT JOIN brigades ON brigade_user.brigade_id = brigades.id;
brigade_id | user_id | is_commissar | id | name
------------+---------+--------------+----+------------------
1 | 1 | t | 1 | Patrulla gatuna
1 | 2 | f | 1 | Patrulla gatuna
2 | 3 | t | 2 | Patrulla perruna
2 | 4 | f | 2 | Patrulla perruna
3 | 6 | t | 3 | Patrulla canina
3 | 5 | f | 3 | Patrulla canina
(4 rows)
Is it possible to make a query which returns a table like this?
brigade_id | commissar_id | assistant_id | name
-----------+--------------+--------------+--------------------
1 | 1 | 2 | Patrulla gatuna
2 | 3 | 4 | Patrulla perruna
3 | 6 | 5 | Patrulla canina
See that each two rows have been merged into one (remember, a brigade is composed by one commissary and, optionally, one assistant) depending on the flag.
Could this model be improved (having in mind the limitation on multiple foreign keys referencing the same table, discussed here)
Try the following:
with cte as
(
SELECT A.brigade_id,A.user_id,A.is_commissar,B.name
FROM brigade_user A LEFT JOIN brigades B ON A.brigade_id = B.id
)
select C1.brigade_id, C1.user_id as commissar_id , C2.user_id as assistant_id, C1.name from
cte C1 left join cte C2
on C1.brigade_id=C2.brigade_id
and C1.user_id<>C2.user_id
where C1.is_commissar=true
See a demo from here.

How would you store in database who's liked something?

How should database store whos liked a certain post, should I have a seperate table which keeps tracks of all likes, and keeps every like of every post together, storing article_id, user_id, and like/dislike?
Thanks!
You are describing a N-M relationship between users and posts, where each user might ligke serveral posts and each post can be liked by several users.
I would recommend using a bridge table, with foreign keys refering the posts and users tables.
In a nutshell, that would look like:
table: users
user_id
name
...
table: posts
post_id
title
...
table: users_like_posts
user_id -- foreign key to users(user_id)
post_id -- foreign key to posts(post_id)
like_dislike
One option could be creating a table which trucks likes and dislikes:
Example:
Like and dislike could be boolean or anything else that you like to implement.
Table:Like
-----------------
id | post_id | user_id | like_dislike |
1 | 1234 | 13783 | 0 |
2 | 1234 | 12083 | 1 |

How to add foreign key constraint to Table A (id, type) referencing either of two tables Table B (id, type) or Table C (id, type)?

I'm looking to use two columns in Table A as foreign keys for either one of two tables: Table B or Table C. Using columns table_a.item_id and table_a.item_type_id, I want to force any new rows to either have a matching item_id and item_type_id in Table B or Table C.
Example:
Table A: Inventory
+---------+--------------+-------+
| item_id | item_type_id | count |
+---------+--------------+-------+
| 2 | 1 | 32 |
| 3 | 1 | 24 |
| 1 | 2 | 10 |
+---------+--------------+-------+
Table B: Recipes
+----+--------------+-------------------+-------------+----------------------+
| id | item_type_id | name | consistency | gram_to_fluid_ounces |
+----+--------------+-------------------+-------------+----------------------+
| 1 | 1 | Delicious Juice | thin | .0048472 |
| 2 | 1 | Ok Tasting Juice | thin | .0057263 |
| 3 | 1 | Protein Smoothie | heavy | .0049847 |
+----+--------------+-------------------+-------------+----------------------+
Table C: Products
+----+--------------+----------+--------+----------+----------+
| id | item_type_id | name | price | in_stock | is_taxed |
+----+--------------+----------+--------+----------+----------+
| 1 | 2 | Purse | $200 | TRUE | TRUE |
| 2 | 2 | Notebook | $14.99 | TRUE | TRUE |
| 3 | 2 | Computer | $1,099 | FALSE | TRUE |
+----+--------------+----------+--------+----------+----------+
Other Table: Item_Types
+----+-----------+
| id | type_name |
+----+-----------+
| 1 | recipes |
| 2 | products |
+----+-----------+
I want to be able to have an inventory table where employees can enter inventory counts regardless of whether an item is a recipe or a product. I don't want to have to have a product_inventory and recipe_inventory table as there are many operations I need to do across all inventory items regardless of item types.
One solution would be to create a reference table like so:
Table CD: Items
+---------+--------------+------------+-----------+
| item_id | item_type_id | product_id | recipe_id |
+---------+--------------+------------+-----------+
| 2 | 1 | NULL | 2 |
| 3 | 1 | NULL | 3 |
| 1 | 2 | 1 | NULL |
+---------+--------------+------------+-----------+
It just seems very cumbersome, plus I'd now need to add/remove products/recipes from this new table whenever they are added/removed from their respective tables. (Is there an automatic way to achieve this?)
CREATE TABLE [dbo].[inventory] (
[id] [bigint] IDENTITY(1,1) NOT NULL,
[item_id] [smallint] NOT NULL,
[item_type_id] [tinyint] NOT NULL,
[count] [float] NOT NULL,
CONSTRAINT [PK_inventory_id] PRIMARY KEY CLUSTERED ([id] ASC)
) ON [PRIMARY]
What I would really like to do is something like this...
ALTER TABLE [inventory]
ADD CONSTRAINT [FK_inventory_sources] FOREIGN KEY ([item_id],[item_type_id])
REFERENCES {[products] ([id],[item_type_id]) OR [recipes] ([id],[item_type_id])}
Maybe there is no solution as I'm describing it, so if you have any ideas where I can maintain the same/similar schema, I'm definitely open to hearing them!
Thanks :)
Since your products and recipes are stored separately, and appear to mostly have separate columns, then separate inventory tables is probably the correct approach. e.g.
CREATE TABLE dbo.ProductInventory
(
Product_id INT NOT NULL,
[count] INT NOT NULL,
CONSTRAINT FK_ProductInventory__Product_id FOREIGN KEY (Product_id)
REFERENCES dbo.Product (Product_id)
);
CREATE TABLE dbo.RecipeInventory
(
Recipe_id INT NOT NULL,
[count] INT NOT NULL,
CONSTRAINT FK_RecipeInventory__Recipe_id FOREIGN KEY (Recipe_id)
REFERENCES dbo.Recipe (Recipe_id )
);
If you need all types combined, you can simply use a view:
CREATE VIEW dbo.Inventory
AS
SELECT Product_id AS item_id,
2 AS item_type_id,
[Count]
FROM ProductInventory
UNION ALL
SELECT recipe_id AS item_id,
1 AS item_type_id
[Count]
FROM RecipeInventory;
GO
IF you create a new item_type, then you need to amend the DB design anyway to create a new table, so you would just need to amend the view at the same time
Another possibility, would be to have a single Items table, and then have Products/Recipes reference this. So you start with your items table, each of which has a unique ID:
CREATE TABLE dbo.Items
(
item_id INT IDENTITY(1, 1) NOT NULL
Item_type_id INT NOT NULL,
CONSTRAINT PK_Items__ItemID PRIMARY KEY (item_id),
CONSTRAINT FK_Items__Item_Type_ID FOREIGN KEY (Item_Type_ID) REFERENCES Item_Type (Item_Type_ID),
CONSTRAINT UQ_Items__ItemID_ItemTypeID UNIQUE (Item_ID, Item_type_id)
);
Note the unique key added on (item_id, item_type_id), this is important for referential integrity later on.
Then each of your sub tables has a 1:1 relationship with this, so your product table would become:
CREATE TABLE dbo.Products
(
item_id BIGINT NOT NULL,
Item_type_id AS 2,
name VARCHAR(50) NOT NULL,
Price DECIMAL(10, 4) NOT NULL,
InStock BIT NOT NULL,
CONSTRAINT PK_Products__ItemID PRIMARY KEY (item_id),
CONSTRAINT FK_Products__Item_Type_ID FOREIGN KEY (Item_Type_ID)
REFERENCES Item_Type (Item_Type_ID),
CONSTRAINT FK_Products__ItemID_ItemTypeID FOREIGN KEY (item_id, Item_Type_ID)
REFERENCES dbo.Item (item_id, item_type_id)
);
A few things to note:
item_id is again the primary key, ensuring the 1:1 relationship.
the computed column item_type_id (as 2) ensuring all item_type_id's are set to 2. This is key as it allows a foreign key constraint to be added
the foreign key on (item_id, item_type_id) back to the items table. This ensures that you can only insert a record to the product table, if the original record in the items table has an item_type_id of 2.
A third option would be a single table for recipes and products and make any columns not required for both nullable. This answer on types of inheritance is well worth a read.
I think there is a flaw in your database design. The best way to solve your actual problem, is to have Recipies and products as one single table. Right now you have a redundant column in each table called item_type_id. That column is not worth anything, unless you actually have the items in the same table. I say redundant, because it has the same value for absolutely every entry in each table.
You have two options. If you can not change the database design, work without foreign keys, and make the logic layer select from the correct tables.
Or, if you can change the database design, make products and recipies exist in the same table. You already have a item_type table, which can identify item categorization, so it makes sense to put all items in the same table
you can only add one constraint for a column or pair of columns. Think about apples and oranges. A column cannot refer to both oranges and apples. It must be either orange or apple.
As a side note, this can be somehow achieved with PERSISTED COMPUTED columns, however It only introduces overhead and complexity.
Check This for Reference
You can add some computed columns to the Inventory table:
ALTER TABLE Inventory
ADD _recipe_item_id AS CASE WHEN item_type_id = 1 THEN item_id END persisted
ALTER TABLE Inventory
ADD _product_item_id AS CASE WHEN item_type_id = 2 THEN item_id END persisted
You can then add two separate foreign keys to the two tables, using those two columns instead of item_id. I'm assuming the item_type_id column in those two tables is already computed/constraint appropriately but if not you may want to consider that too.
Because these computed columns are NULL when the wrong type is selected, and because SQL Server doesn't check FK constraints if at least one column value is NULL, they can both exist and only one or the other will be satisfied at any time.

How to dump selected PostgreSQL data from one database to other databse

I have three tables with foreign key relationship with each other.
The table school will be uploaded manually. A student will login to the website and check their marks
The entire data is to be uploaded to another new database of different instance
The Login Id(stud_id) of the student in DB1 is 10 and Login Id(stud_id) of the student in DB2 is 1 in another instance.
For retaining the data of student_marks table, I intend to do the following steps,
1. Dump student_marks table from DB1
2. Copy it to DB2
NOTE: stud_id would be different for both the databases
Is there any way to do the above.
Refer the table below,
school:
id | name| place
-----+-------------
1 | sch1 | test
student:
id | school_id| stud_name
-----+-------------
1 | 1 | stud1
student_marks:
id | stud_id| subj1 | subj2
-----+-----------------------
1 | 1 | 30 | 30
Thanks in advance!
First Disable the foreign key constraint, then dump the data and after that again enable the foreign key constraint or you can put foreign key constraint after migrating the data.

Complex SQL query for messaging app

I'm building a messaging feature in a Django 1.6.2 application using PostgreSQL 9.3.4. On a user's "Messages" home page, I'll show a list of conversations that a user has with other users. Each conversation "tile" or block will show the picture and name of the other user in that conversation, the date the last message in that conversation was sent, and the first 25 characters in that last message. I'll also show a small "reply" icon if the last message was sent by user who is viewing these conversations. I've got my query to the point where I can identify all of the conversations between the viewer and all the other users but I'm having trouble pulling in the fields I need from the User and Message tables.
My tables (shown at the bottom) are User, Message, and Conversation. Although I've implemented my table schemas so that there is a many-to-many relationship between users and conversations, in the beginning I'm going to create my interface so that a user can only send a message to one other user instead of multiple users.
When I run my query on the data shown below, what I'm trying to get back is the conversation and user IDs for users 3, 4, 5 along with their associated usernames, the last message in that conversation, who sent it, and the date it was sent. Instead, I'm getting the error:
ERROR: syntax error at or near "WHERE"
Can anyone help me fix this query? I'm more interested in speed than elegance.
Test case
Data in conversation_user linking table:
id | conversation_id | user_id
----+-----------------+---------
1 | 1 | 32
2 | 1 | 3 <- want this
3 | 2 | 32
4 | 2 | 4 <- want this
6 | 3 | 3
7 | 3 | 1
8 | 4 | 32
9 | 4 | 5 <- want this
10 | 5 | 7
11 | 5 | 9
Rows I want to return. Each message is last message in that conversation.
conversation_id | user_id | username | from_user | message | send_date
----------------+---------+-----------+-----------+---------+----------
1 | 3 | user3 | u3 or u32 | <msg3> | <date>
2 | 4 | user4 | u4 or u32 | <msg4> | <date>
4 | 5 | user5 | u5 or u32 | <msg5> | <date>
Query that isn't working:
SELECT cu.conversation_id,
cu.user_id,
au.username,
m.from_user,
m.message,
m.send_date
FROM conversation_user cu
INNER JOIN auth_user au ON cu.user_id = au.id
INNER JOIN message m ON cu.conversation_id = m.conversation_id
ORDER BY m.send_date DESC LIMIT 1
WHERE conversation_id IN
(SELECT conversation_id
FROM conversation_user
WHERE user_id = 32)
AND user_id != 32;
Table definitions
# auth_user
--------------+--------------------------+------------------------------
id | integer | not null default nextval(...
username | character varying(30) | not null
Referenced by:
TABLE "conversation_user" CONSTRAINT "conversation_user_user_id_fkey" FOREIGN KEY (user_id) REFERENCES auth_user(id) DEFERRABLE INITIALLY DEFERRED
TABLE "message" CONSTRAINT "message_from_user_id_fkey" FOREIGN KEY (from_user_id) REFERENCES auth_user(id) DEFERRABLE INITIALLY DEFERRED
# conversation
------------+--------------------------+--------------------------------
id | integer | not null default nextval(...
start_date | timestamp with time zone | not null
Referenced by:
TABLE "conversation_user" CONSTRAINT "conversation_id_refs_id_4344ca71" FOREIGN KEY (conversation_id) REFERENCES conversation(id) DEFERRABLE INITIALLY DEFERRED
TABLE "message" CONSTRAINT "message_conversation_id_fkey" FOREIGN KEY (conversation_id) REFERENCES conversation(id) DEFERRABLE INITIALLY DEFERRED
# conversation_user
-----------------+---------+--------------------------------------------
id | integer | not null default nextval(...
conversation_id | integer | not null
user_id | integer | not null
Foreign-key constraints:
"conversation_id_refs_id_4344ca71" FOREIGN KEY (conversation_id) REFERENCES conversation(id) DEFERRABLE INITIALLY DEFERRED
"conversation_user_user_id_fkey" FOREIGN KEY (user_id) REFERENCES auth_user(id) DEFERRABLE INITIALLY DEFERRED
# message
Column | Type |
-----------------+--------------------------+---------------------------
id | integer | not null default nextval(...
conversation_id | integer | not null
from_user_id | integer | not null
to_user_uid | integer | not null
message | text | not null
send_date | timestamp with time zone | not null
Foreign-key constraints:
"message_conversation_id_fkey" FOREIGN KEY (conversation_id) REFERENCES conversation(id) DEFERRABLE INITIALLY DEFERRED
"message_from_user_id_fkey" FOREIGN KEY (from_user_id) REFERENCES auth_user(id) DEFERRABLE INITIALLY DEFERRED
Fix syntax
Basically, you just need to move the WHERE condition to its proper place, like #Lamak commented:
SELECT ...
FROM conversation_user cu
INNER JOIN ...
WHERE conversation_id IN
(SELECT conversation_id
FROM conversation_user
WHERE user_id = 32)
AND user_id != 32
ORDER BY m.send_date DESC
LIMIT 1;
Make it fast
According to comment:
I'm trying to select the last message in each of the [...] conversations user 32 is having.
SELECT cu.conversation_id
, ufrom.username AS from_user
, uto.username AS to_user
, m.message
, m.send_date
FROM conversation_user cu
LEFT JOIN LATERAL (
SELECT from_user_id, to_user_id, message, send_date
FROM message m
WHERE m.conversation_id = cu.conversation_id
ORDER BY send_date DESC
LIMIT 1
) m ON TRUE
LEFT JOIN auth_user ufrom ON ufrom.id = m.from_user_id
LEFT JOIN auth_user uto ON uto.id = m.to_user_id
WHERE cu.user_id = 32;
Notes
A join is typically faster than an IN construct on a subquery, especially with big sets. But you don't need either. You have been overcomplicating things.
You could have a simpler query with DISTINCT ON, but I expect this one to be faster.
Details:
Select first row in each GROUP BY group?
Optimize GROUP BY query to retrieve latest record per user
DB design
The query assumes that (user_id, conversation_id) is UNIQUE - which you confirmed in the comment. Be sure to add an actual UNIQUE constraint, which provides the much needed index automatically.
An index on message on (conversation_id, send_date DESC) would help, too. Details:
Is a composite index also good for queries on the first field?
Assuming auth_user.id is the PK, so it would be indexed.
message.to_user_uid is probably supposed to be to_user_id - like from_user_id.
You probably want to add another FK to stay consistent:
"message_to_user_id_fkey" FOREIGN KEY (to_user_id) REFERENCES auth_user(id)
Not sure why you think you need DEFERRABLE INITIALLY DEFERRED. If you don't know you need this, remove it. It's for special purposes and makes regular operations more expensive.
If only two users can take part in the same conversation, it would be more efficient to remove conversation_user altogether and add user1 and user2 or similar to conversation - unless there are more attributes for each combination of user/conversation. Potentially simplify message, too. You only need a boolean information instead of from_user and to_user.
According to relational theory, conversation can be seen as a the implementation of many-to-many relationship between table auth_user and itself.