duancaiyi7567 2015-05-26 21:14
浏览 46

PostgreSQL内部查询与准备好的语句

I have a table to store contacts.

I want to grab the max value of a column where adding user_id is {some number} and set that as the same column value for the current inserting record.

I'm using prepared statements:

pg_prepare($db, "add", 'INSERT INTO '.CONTACTS.' (c_user_serial,c_name,c_company,c_email)  VALUES ($1, $2, $3, $4)');

$insert_co = pg_execute($db, "add", array({(MAX OF c_user_serial where c_user_id = 1234) + 1 increment },$name,$company,$email));

c_user_id is the ID of the user who is adding this contact, there is another column as index (id) that is a common serial column that increments for every row, c_user_serial is a serial number that increments per user. Let's say one user added one contact so it is 1. After other users added many contacts, when this user adds his second contact I want this column to store 2, so an auto increment kind of column but that should increment per user.

Not sure how to use inner queries here to get the max value of the column and use the incremented value for the current insertion.

  • 写回答

1条回答 默认 最新

  • doudang1890 2015-05-26 23:52
    关注

    This query would do what you are asking for:

    INSERT INTO contacts (c_user_serial, c_user_id, c_name, c_company, c_email)
    SELECT max(c_user_serial) + 1, $1, $2, $3, $4
    FROM   tbl
    WHERE  c_user_id = $1;
    

    Your original forgets to insert user_id itself, which is needed. I added it.

    However, this carries a couple of principal problems:

    1. The current "maximum" can be to subject of a race condition between concurrent transactions and is unreliable. (While a serial is safe with concurrent access.)

    2. If no row with c_user = $1 is found, nothing is inserted. May or may not be what you want.

    3. If max(c_user_serial) returns NULL, another NULL value is inserted for c_user_id.
      If c_user_id is defined NOT NULL, that cannot happen.

    To avoid problem 2. and 3. and start with 1 for every new user instead:

    INSERT INTO contacts (c_user_serial, c_user_id, c_name, c_company, c_email)
    VALUES (COALESCE((SELECT max(c_user_serial) + 1 FROM tbl WHERE c_user_id = $1), 1)
           , $1, $2, $3, $4)
    

    "no row" is converted to NULL here and COALESCE defaults to 1 in this case.

    Simple solution

    Everything put together, the simple solution is:

    pg_prepare($db, "add"
         , 'INSERT INTO ' . CONTACTS . ' (c_user_serial, c_user_id, c_name, c_company, c_email)
            VALUES (COALESCE((SELECT max(c_user_serial) + 1 FROM tbl WHERE c_user_id = $1), 1)
                   , $1, $2, $3, $4)');    
    $insert_co = pg_execute($db, "add", array($user_id,$name,$company,$email));
    

    Make sure that CONTACTS holds a properly escaped table name or you are wide open to SQL injection!

    If you are looking for sequences without gaps, you must deal with UPDATE and DELETE somehow, which will inevitable honeycomb your sequences over time, which is one of the reasons, why the whole idea is not that good.

    The other, more important reason is the race condition I mentioned. There is no cheap and elegant solution for it. (There are solutions, but more or less expensive ...)

    If at all possible stick to one plain serial column for all rows. You can always attach numbers per user starting from 1 when retrieving data:

    评论

报告相同问题?

悬赏问题

  • ¥500 火焰左右视图、视差(基于双目相机)
  • ¥100 set_link_state
  • ¥15 虚幻5 UE美术毛发渲染
  • ¥15 CVRP 图论 物流运输优化
  • ¥15 Tableau online 嵌入ppt失败
  • ¥100 支付宝网页转账系统不识别账号
  • ¥15 基于单片机的靶位控制系统
  • ¥15 真我手机蓝牙传输进度消息被关闭了,怎么打开?(关键词-消息通知)
  • ¥15 装 pytorch 的时候出了好多问题,遇到这种情况怎么处理?
  • ¥20 IOS游览器某宝手机网页版自动立即购买JavaScript脚本