Postgres:一对多关系的复杂多重更新查询
Postgres : Complex Multiple Update query for one to many relationship
我的数据库看起来像这样
https://dbfiddle.uk/?rdbms=postgres_10&fiddle=7e0cf7ad1e5bf9f4e0321a2e5ec970f7
所以
- 一个用户可以拥有多本书
- 基本的一对多关系
让我们假设如果用户更新了先前存在的书籍并添加了更多书籍,
我将如何去查询呢?
我做了一些研究,得出的结论是 cte 或 function 都可以帮我解决问题。
我的请求正文 (JSON) 数据 FOR PATCH QUERY 看起来像这样
{
// user data
user_id: 1,
name : 'Ryan',
books : [
{
book_id : 1,
stock : 500
},
{
book_id : 2,
stock : 500
},
{
// this book should be added to the users_books table
name: 'My new book 1',
stock : 500
}
]
}
The postgresql update queries for the above data should look like ->
UPDATE users_books(stock) VALUES(500) WHERE user_id = 1 AND book_id 1;
UPDATE users_books(stock) VALUES(500) WHERE user_id = 1 AND book_id 2;
INSERT INTO users_books(user_id,book_id,stock) VALUES(1,3,500);
所以看看上面的结构,我需要 books_users table 进行相应的更新。
我目前的理解是将 books 对象作为 jsonb 传递给 postgresql 的函数。然后循环遍历它,同时相应地更新/添加书籍。我不确定我将如何知道用户是否已经拥有一本书。
你们如何将此请求正文转换为上述复杂的更新查询?
在函数中执行此操作完全是事务性的吗?
你可以在一个语句中完成所有这些,但是最好有一些独特的约束来防止它做坏事。 users_books(book_id, user_id) 应该是唯一的,books(name) 应该是唯一的。
这是fiddle
这是重要的部分:
-- The test data
with data(d) as (VALUES ('
{
"user_id": 1,
"name" : "Ryan",
"books" : [
{
"book_id" : 1,
"stock" : 500
},
{
"book_id" : 2,
"stock" : 500
},
{
"name": "My new book 1",
"stock" : 500
}
]
}'::jsonb)
-- Parse the json to get user_id, book_id, stock, and name for each book
), normalized_data as (
select (d ->> 'user_id')::int as user_id,
book_id, stock, name
FROM data
JOIN LATERAL (SELECT * FROM jsonb_to_recordset(d -> 'books')
as books(book_id int, stock int, name text)
) sub ON TRUE
-- Update existing stock
), update_existing as (
UPDATE users_books set stock = normalized_data.stock
FROM normalized_data
WHERE users_books.user_id = normalized_data.user_id
AND users_books.book_id = normalized_data.book_id
AND normalized_data.book_id IS NOT NULL
-- insert any new books
), insert_new_book as (
INSERT INTO books (name)
SELECT name from normalized_data
WHERE book_id IS NULL
RETURNING id, name
)
-- insert a record into users_books for new books
INSERT INTO users_books (user_id, book_id, stock)
SELECT user_id, id, stock
FROM insert_new_book
JOIN normalized_data ON normalized_data.name = insert_new_book.name;
我的数据库看起来像这样 https://dbfiddle.uk/?rdbms=postgres_10&fiddle=7e0cf7ad1e5bf9f4e0321a2e5ec970f7
所以
- 一个用户可以拥有多本书
- 基本的一对多关系
让我们假设如果用户更新了先前存在的书籍并添加了更多书籍, 我将如何去查询呢?
我做了一些研究,得出的结论是 cte 或 function 都可以帮我解决问题。
我的请求正文 (JSON) 数据 FOR PATCH QUERY 看起来像这样
{
// user data
user_id: 1,
name : 'Ryan',
books : [
{
book_id : 1,
stock : 500
},
{
book_id : 2,
stock : 500
},
{
// this book should be added to the users_books table
name: 'My new book 1',
stock : 500
}
]
}
The postgresql update queries for the above data should look like ->
UPDATE users_books(stock) VALUES(500) WHERE user_id = 1 AND book_id 1;
UPDATE users_books(stock) VALUES(500) WHERE user_id = 1 AND book_id 2;
INSERT INTO users_books(user_id,book_id,stock) VALUES(1,3,500);
所以看看上面的结构,我需要 books_users table 进行相应的更新。
我目前的理解是将 books 对象作为 jsonb 传递给 postgresql 的函数。然后循环遍历它,同时相应地更新/添加书籍。我不确定我将如何知道用户是否已经拥有一本书。
你们如何将此请求正文转换为上述复杂的更新查询? 在函数中执行此操作完全是事务性的吗?
你可以在一个语句中完成所有这些,但是最好有一些独特的约束来防止它做坏事。 users_books(book_id, user_id) 应该是唯一的,books(name) 应该是唯一的。
这是fiddle
这是重要的部分:
-- The test data
with data(d) as (VALUES ('
{
"user_id": 1,
"name" : "Ryan",
"books" : [
{
"book_id" : 1,
"stock" : 500
},
{
"book_id" : 2,
"stock" : 500
},
{
"name": "My new book 1",
"stock" : 500
}
]
}'::jsonb)
-- Parse the json to get user_id, book_id, stock, and name for each book
), normalized_data as (
select (d ->> 'user_id')::int as user_id,
book_id, stock, name
FROM data
JOIN LATERAL (SELECT * FROM jsonb_to_recordset(d -> 'books')
as books(book_id int, stock int, name text)
) sub ON TRUE
-- Update existing stock
), update_existing as (
UPDATE users_books set stock = normalized_data.stock
FROM normalized_data
WHERE users_books.user_id = normalized_data.user_id
AND users_books.book_id = normalized_data.book_id
AND normalized_data.book_id IS NOT NULL
-- insert any new books
), insert_new_book as (
INSERT INTO books (name)
SELECT name from normalized_data
WHERE book_id IS NULL
RETURNING id, name
)
-- insert a record into users_books for new books
INSERT INTO users_books (user_id, book_id, stock)
SELECT user_id, id, stock
FROM insert_new_book
JOIN normalized_data ON normalized_data.name = insert_new_book.name;