了解 PL/pgSQL 函数中 int 文字与 int 参数之间的区别

Understanding difference between int literal vs int parameter in PL/pgSQL function

我有一个在 PostgreSQL 9.5 中左填充位字符串的功能:

CREATE OR REPLACE FUNCTION lpad_bits(val bit varying) 
RETURNS bit varying as
$BODY$
  BEGIN return val::bit(32) >> (32-length(val));
  END;
$BODY$
LANGUAGE plpgsql IMMUTABLE;

效果很好:

 # select lpad_bits(b'1001100111000');
        lpad_bits
 ----------------------------------
 00000000000000000001001100111000
 (1 row)

我的问题是当我尝试添加一个参数来更改填充量时:

CREATE OR REPLACE FUNCTION lpad_bits(val bit varying, sz integer default 1024) 
  RETURNS bit varying as
$BODY$
  BEGIN return val::bit(sz) >> (sz-length(val));
  END;
$BODY$
LANGUAGE plpgsql IMMUTABLE;

功能现已损坏:

# select lpad_bits(b'1001100111000', 32);                                      
ERROR:  invalid input syntax for integer: "sz"
LINE 1: SELECT val::bit(sz) >> (sz-length(val))
                ^
QUERY:  SELECT val::bit(sz) >> (sz-length(val))
CONTEXT:  PL/pgSQL function lpad_bits(bit varying,integer) line 2 at RETURN

我一直盯着 bitstring documentation and PL/pgSQL function documentation,只是看不出这两个实现之间有什么根本不同。

解析器不允许在该位置使用变量。另一种方法是使用常量 trim 它:

select right((val::bit(128) >> (128 -length(val)))::text, sz)::bit(sz)
from (values (b'1001100111000', 32)) s(val,sz)
;
              right               
----------------------------------
 00000000000000000001001100111000

或评论中建议的lpad函数。

为什么?

PL/pgSQL 执行 SQL 查询,例如 准备语句 The manual about parameter substituion:

Prepared statements can take parameters: values that are substituted into the statement when it is executed.

注意术语 。只能参数化实际值,但不能参数化关键字、标识符或类型名称。 32 in bit(32) 看起来是一个值,但是数据类型的修饰符在内部只是一个"value",不能参数化。 SQL需要在计划阶段就知道数据类型,不能等到执行阶段。

可以通过动态 SQL 和 EXECUTE 实现您的目标。作为概念验证

CREATE OR REPLACE FUNCTION lpad_bits(val varbit, sz int = 32, OUT outval varbit) AS
$func$
BEGIN
   EXECUTE format('SELECT ::bit(%s) >> ', sz)  -- literal
   USING val, sz - length(val)                     -- values
   INTO outval;
END
$func$  LANGUAGE plpgsql IMMUTABLE;

致电:

SELECT lpad_bits(b'1001100111000', 32);  

请注意 sz 用作 文字 以构建语句和它第二次出现的区别,可以作为参数传递。

更快的选择

这个特定任务的一个更好的解决方案是只使用 lpad() 就像 :

CREATE OR REPLACE FUNCTION lpad_bits2(val varbit, sz int = 32)
  RETURNS varbit AS
$func$
SELECT lpad(val::text, sz, '0')::varbit;
$func$  LANGUAGE sql IMMUTABLE;

(与普通 SQL 函数一样简单,它还允许在外部查询的上下文中 函数内联 。)

比上面的函数快几倍。一个小缺陷:我们必须强制转换为 text 并返回到 varbit。不幸的是,lpad() 目前没有为 varbit 实现。 The manual:

The following SQL-standard functions work on bit strings as well as character strings: length, bit_length, octet_length, position, substring, overlay.

overlay()可用,我们可以有更便宜的功能:

CREATE OR REPLACE FUNCTION lpad_bits3(val varbit, base varbit = '00000000000000000000000000000000')
  RETURNS varbit AS
$func$
SELECT overlay(base PLACING val FROM bit_length(base) - bit_length(val))
$func$  LANGUAGE sql IMMUTABLE;

如果您可以使用 varbit 值开始,速度会更快。 (如果您无论如何都必须将 text 转换为 varbit,则优势(部分)无效。)

致电:

SELECT lpad_bits3(b'1001100111000', '00000000000000000000000000000000');
SELECT lpad_bits3(b'1001100111000',  repeat('0', 32)::varbit);

我们可能 覆盖 带有变体的函数采用整数生成 base 本身:

CREATE OR REPLACE FUNCTION lpad_bits3(val varbit, sz int = 32)
  RETURNS varbit AS
$func$
SELECT overlay(repeat('0', sz)::varbit PLACING val FROM sz - bit_length(val))
$func$  LANGUAGE sql IMMUTABLE;

致电:

SELECT lpad_bits3(b'1001100111000', 32;

相关:

  • Postgresql Convert bit varying to integer
  • Convert hex in text representation to decimal number