使用postgres通过一次查询将数据插入到多个表中

Data insertion into multiple tables with one query using postgres

我想一次性将数据插入到多个相关表中 - 在单个事务中。我正在使用 Postgres 和 Dapper ORM。这是我的查询:

WITH userins AS (
    INSERT INTO public."user"
(
    "FirstName", 
    "LastName", 
    "Email", 
    "PasswordHash", 
    "Address", 
    "City", 
    "State", 
    "Zip", 
    "Phone", 
    "IsEnabled", 
    "IsVerified", 
    "IsDeleted",  
    "CreatedDate", 
)
    VALUES (@FirstName, @LastName, @Email, @PasswordHash, @Address, @City, @State, @Zip, @Phone, @IsEnabled, ?IsVerified, ?IsDeleted, @CreatedDate);
    RETURNING id AS user_id
   )
, clientins AS (
    INSERT INTO public.client( "Description", "PlanCustomerId", "IsActive", "IsDeleted", "CreatedBy", "CreatedDate", userid)
                           VALUES(@Description, @PlanCustomerId, @IsActive,@IsDeleted, user_id, @CreatedDate,user_id) RETURNING id as client_id;
   )
   , clientsubins
   (
    INSERT INTO public.client_subscription(
    "PlanId", 
    "IsActive",
    "StartDate", 
    "EndDate", 
    "IsDeleted", 
    "CreatedBy", 
    "CreatedDate", 
    clientid, 
    subscriptionid)
    VALUES (@PlanId, @IsActive, @StartDate, @EndDate, @IsDeleted, user_id, @CreatedDate, client_id, @subscriptionid);
   )
   RETURNING client_id

此查询是否有效或我需要更改哪些内容?

postgreSql 对此有不同的语法。几天前我也被困在这个问题上想知道如何处理它。您可以使用 BEGIN 块将数据插入到多个表中,请按照此查询完成您的工作。

BEGIN;
                WITH userins AS (
                INSERT INTO public.user(
                ""FirstName"", ""LastName"", ""Email"", ""PasswordHash"",""VerificationCode"", ""Phone"", ""IsEnabled"", ""IsVerified"", ""IsDeleted"",  ""CreatedDate"")VALUES(@FirstName, @LastName, @Email, @PasswordHash, @VerificationCode, @Phone, @IsEnabled, @IsVerified, @IsDeleted, @CreatedDate)
                    RETURNING id AS user_id)
                ,clientins AS(
                INSERT INTO public.client(""Description"", ""PlanCustomerId"", ""IsActive"", ""IsDeleted"", ""CreatedBy"", ""CreatedDate"", UserId) 
                        VALUES(@Description, @PlanCustomerId, @IsActive, @IsDeleted, (SELECT user_id from userins), @CreatedDate, (SELECT user_id from userins)) RETURNING id as client_id)
                ,clientsubins AS
                (
                   INSERT INTO public.client_subscription(""PlanId"", ""IsActive"",""StartDate"", ""EndDate"", ""IsDeleted"", ""CreatedBy"", ""CreatedDate"", ClientId, SubscriptionId)
                   VALUES(@PaymentMethodPlanId, @IsActive, @StartDate, @EndDate, @IsDeleted, (SELECT user_id from userins), @CreatedDate, (SELECT client_id from clientins), @PlanId)
                )
                ,clientpurchaseins AS
                (
                   INSERT INTO public.client_purchase_history(""PlanId"", ""InvoiceId"",""StartDate"", ""EndDate"", ""IsDeleted"", ""CreatedBy"", ""CreatedDate"", ClientId)
                   VALUES(@PlanId, @InvoiceId, @StartDate, @EndDate, @IsDeleted, (SELECT user_id from userins), @CreatedDate, (SELECT client_id FROM clientins))
                )
                SELECT client_id from clientins;
        COMMIT;

这是开始块将处理您的事务,如果发生任何错误,它将自动回滚。

最重要的是,您需要从早期的 CTE SELECT 获得 RETURNING 子句产生的值:

WITH userins AS (
   INSERT INTO public."user"
          ("FirstName", "LastName", "Email", "PasswordHash", "Address", "City", "State", "Zip", "Phone", "IsEnabled", "IsVerified", "IsDeleted", "CreatedDate") --  no dangling ,
   VALUES (@FirstName , @LastName , @Email , @PasswordHash , @Address , @City , @State , @Zip , @Phone , @IsEnabled , @IsVerified , @IsDeleted , @CreatedDate)  --  no ;
   RETURNING id AS userid
   )
 , clientins AS (
   INSERT INTO public.client
         ("Description", "PlanCustomerId", "IsActive", "IsDeleted", "CreatedBy", "CreatedDate", userid)
   SELECT @Description , @PlanCustomerId , @IsActive , @IsDeleted , userid     , @CreatedDate , userid
   FROM   userins
   RETURNING id AS clientid, userid  -- no ;
   )
INSERT INTO public.client_subscription
      ("PlanId", "IsActive","StartDate", "EndDate", "IsDeleted", "CreatedBy", "CreatedDate", clientid , subscriptionid)
SELECT @PlanId , @IsActive , @StartDate, @EndDate , @IsDeleted , userid     , @CreatedDate , clientid,  @subscriptionid
FROM   clientins
RETURNING clientid;

您还有一些语法错误。而且我统一拼写了clientid

每个 CTE(和外部 INSERT)取决于前一个 CTE 的结果行。这样,如果第一个 INSERT 不插入(和 return)任何内容,则不会插入任何内容。

由于这是一个 语句,它会自动在单个事务中运行。任何错误都会取消整个操作。

甚至可以在您过时的 Postgres 9.3 中使用。

参见:

  • Insert data in 3 tables at a time using Postgres

您收到的答案很好,但是我会给出我的建议。由于您正在处理一个事务块,因此最好将其作为函数或存储过程的一部分包含在内。例如,我更喜欢在变量中分配 returning id,这样我就可以在整个执行过程中使用它。如果一切正常,我提交 return user_id 或者我也可以根据需要将 return 类型更改为布尔值。如果有异常,我catch了,return-1,说明没有成功,这里还可以进行其他的选择,比如在table中注册异常的原因。如果一个 table 失败,则全部回滚:

CREATE OR REPLACE FUNCTION test_function(p_name CHAR(50), p_other_params CHAR(50)) 
RETURNS INT AS
$$
DECLARE
  new_user_id INT;
  new_purchase_id INT;
BEGIN
    INSERT INTO public.user(name)
    VALUES(p_name) 
    RETURNING public.user.id INTO new_user_id;
    
    INSERT INTO public.purchase(user_id)
    VALUES(new_user_id) 
    RETURNING public.purchase.id INTO new_purchase_id;
    
     INSERT INTO public.purchase_detail(purchase_id)
    VALUES(new_purchase_id);
    
    RAISE EXCEPTION 'Exception when inserting user: %', p_name; --remove to test when there is no issue
    --commited if reaches this point
    RETURN new_user_id;
    
    EXCEPTION WHEN OTHERS THEN
    --ROLLED back if exception
    RETURN -1; -- this value indicates the app it failed
END;
$$
LANGUAGE plpgsql;
          
 select * from test_function('SomeName', 'OtherParameter');
 select id, name from public.user;
 select id, user_id from public.purchase;
 select id, purchase_id from public.purchase_detail;

这是架构:

CREATE TABLE public.user(  name CHAR(50)); 
CREATE TABLE public.purchase( user_id int);
CREATE TABLE public.purchase_detail( purchase_id INT);
ALTER TABLE public.user ADD COLUMN id SERIAL PRIMARY KEY;
ALTER TABLE public.purchase ADD COLUMN id SERIAL PRIMARY KEY;
ALTER TABLE public.purchase_detail ADD COLUMN id SERIAL PRIMARY KEY;

勾选this fiddle in action,,没有异常时记得去掉RAISE Exception行测试。 注意:由于某些原因,在 fiddle 中 $$ 应该用引号代替,因此里面的单引号是双引号。

更多关于 exception handling(40.6.6. 陷阱错误)。

如果您能说明为什么要在一个事务中插入多个表的总体意图,将会有所帮助。

我不想听起来很无聊,但要充分利用某些东西并有效地使用它,您应该首先对正在运行的组件有一个合理到高级的理解。可悲的是,在当今世界,很多人只是离开 'doing stuff' 然后不得不寻求帮助,因为他们使用的技术没有按照他们的意愿行事,但没有对其进行配置。我不能也不会判断这里是否是这种情况。

总之。如果您想确保数据更改是否应用于所有相关表,对我来说第一个显而易见的事情就是查看 dapper transactions。这是否执行原子事务(因此应用所有更改或 none)?我不知道,但愿如此。

你可以像其他发帖人建议的那样,将 DML(数据操作语言)语句包装在 'transaction block' 中,如果发生错误,它将回滚事务块中的所有更改,或者将其包装在一个函数中,自己处理错误。