【问题标题】:Data insertion into multiple tables with one query using postgres使用 postgres 通过一个查询将数据插入到多个表中
【发布时间】:2021-10-04 07:04:52
【问题描述】:

我想一次性将数据插入到多个相关表中 - 在单个事务中。我正在使用 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

此查询是否有效或我需要更改什么?

【问题讨论】:

  • 这不是处理交易的正常方式
  • 你能告诉我哪种方式好
  • 您可能想要结帐交易?这个问题可能会帮助stackoverflow.com/questions/5178697/…
  • 紧急考虑升级到a current version of Postgres。 9.3 版已在 2018 年达到 EOL (!)
  • @ShaileshSuryawanshi:为什么要链接到 MySQL 问题?这是具有不同(高级)事务处理的 Postgres。而LAST_INSERT_ID()(如那里所建议的那样)是特定于 MySQL 的。

标签: sql postgresql sql-insert common-table-expression postgresql-9.3


【解决方案1】:

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;

如果发生任何错误,Begin block 将处理您的事务,然后它将自动回滚。

【讨论】:

    【解决方案2】:

    最重要的是,您需要使用早期 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 没有插入(并返回)任何内容,则不会插入任何内容。

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

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

    见:

    【讨论】:

      【解决方案3】:

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

      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 行以在没有异常时进行测试。 注意:由于某种原因,小提琴中的 $$ 应该用引号代替,因此里面的单引号是双引号。

      更多关于exception handling(40.6.6. 捕获错误)。

      【讨论】:

        【解决方案4】:

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

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

        无论如何。如果您想确保数据更改是否应用于所有相关表,对我来说第一件显而易见的事情就是查看dapper transactions。这是否进行原子事务(所以应用所有更改或不应用)?我不知道,希望如此。

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

        【讨论】:

          猜你喜欢
          • 2020-09-11
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-09-05
          • 2011-01-16
          • 1970-01-01
          • 2015-02-24
          • 1970-01-01
          相关资源
          最近更新 更多