带有准备好的语句的 PostgreSQL 内部查询

Posted

技术标签:

【中文标题】带有准备好的语句的 PostgreSQL 内部查询【英文标题】:PostgreSQL inner queries with prepared statements 【发布时间】:2015-08-08 17:54:31 【问题描述】:

我有一张表来存储联系人。

我想获取添加 user_id 为 some number 的列的最大值,并将其设置为当前插入记录的相同列值。

我正在使用准备好的语句:

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 是添加此联系人的用户的 ID,还有另一列作为索引 (id),这是一个常见的 serial 列,每行递增,c_user_serial 是序列号每个用户递增。假设一位用户添加了一位联系人,所以它是1。在其他用户添加了许多联系人之后,当该用户添加他的第二个联系人时,我希望此列存储 2,因此是一种自动递增类型的列,但应按用户递增。

不确定如何在此处使用内部查询来获取列的最大值并使用增量值进行当前插入。

【问题讨论】:

我试图写一个答案,但后来我注意到:(MAX OF c_user_id where c_user_id = 1234) 没有意义 - 如果您知道 c_user_id 值是 1234,那么 MAX 肯定是 1234。所以有没有必要使用 MAX 功能。问这个问题时有什么混淆吗? 我试图解决你的问题,但不清楚你想检索什么样的数字,就像 Turophile 评论的那样。您还需要定义如果子查询将找到 NULL 或没有行时要执行的操作。 【参考方案1】:

此查询将满足您的要求:

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;

您的原件忘记插入 user_id 本身,这是必需的。我加了。

然而,这带来了几个主要问题:

    当前的“最大值”可能会受到并发事务之间的竞争条件的影响,并且不可靠。 (虽然serial 对并发访问是安全的。)

    如果没有找到c_user = $1 的行,则插入nothing。可能是也可能不是你想要的。

    如果 max(c_user_serial) 返回 NULL,则为 c_user_id 插入另一个 NULL 值。 如果 c_user_id 定义为 NOT NULL,则不会发生。

为了避免问题 2. 和 3. 并为每个新用户使用1

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" 在这里被转换为 NULL,COALESCE 在这种情况下默认为1

简单的解决方案

所有东西放在一起,简单的解决方案是:

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));

确保CONTACTS 包含正确转义的表名,否则您对SQL 注入持开放态度!

如果您正在寻找序列没有间隙,则必须处理UPDATEDELETE 不知何故,随着时间的推移,这将不可避免地使您的序列蜂窝化,即原因之一,为什么整个想法不是那么好

另一个更重要的原因是我提到的竞争条件。 没有廉价而优雅的解决方案。 (有解决方案,但或多或​​少有点贵……)

如果可能的话,坚持 one 所有行的纯 serial 列。检索数据时,您始终可以为每个用户附加从 1 开始的数字:

Serial numbers per group of rows for compound key

【讨论】:

序列号适用于所有联系人,但我也确实需要这个分区的序列号,因为如果我们在删除/更新时在 URL 中传递不同的 ID 并在列表(#您在检索数据时总是可以从 1 开始为每个用户附加数字:),所以我需要一个真正存在于 DB 中的列,但我不会将它放在我的 INSERT 语句中,但 DB 引擎应该做一个使用用户 ID 分区自动递增,似乎可以使用存储过程,但还不是很清楚

以上是关于带有准备好的语句的 PostgreSQL 内部查询的主要内容,如果未能解决你的问题,请参考以下文章

带有子查询的准备好的语句给出语法错误[关闭]

带有 sql 转义的动态 mysql 查询是不是与准备好的语句一样安全?

带有位域的 PDO 准备好的语句

带有准备好的语句的 SELECT 的 JSON 输出

带有变量的Mysqli准备好的语句列[重复]

对于 postgresql,java sql 准备好的语句不能正确转义