错误:“sql”不是已知变量

Gon*_*uez 3 postgresql scripting plpgsql postgresql-9.4 unicode

这一定是我在这里问过的最愚蠢的问题之一,但是我的 SQL 脚本中肯定隐藏着一些非常恶心的问题,阻止它运行。

我正在使用以下示例 CLI 语法调用 cobertura.sql 文件:

psql -h localhost -U myUser -f cobertura.sql myDB
Run Code Online (Sandbox Code Playgroud)

但它抱怨以下错误:

psql:cobertura.sql:29: ERROR:  "sql " is not a known variable
LINE 14: sql := format('insert into cobertura_tmp select count(*) as ... cobertura.sql file:
Run Code Online (Sandbox Code Playgroud)
DO language plpgsql $$
declare 
    eq  record;
    sql varchar;
BEGIN

create table if not exists cobertura_tmp  (num integer, realtime char(1), lat numeric, lng numeric);

truncate table cobertura_tmp;
for eq in select imei_equipo as imei  from cliente_avl_equipo where id_cliente in (select id from cliente where nombre ilike '%enangab%') limit 3

loop

sql := format('insert into cobertura_tmp select count(*) as num, tipo as realtime, round(CAST(latitud as numeric), 4) as lat ,round(CAST(longitud as numeric), 4) as lng   from reports.avl_historico_%s where latitud between -38.67405472 and -36.75131149 and longitud between  -73.08429161 and -69.65333954 group by tipo, round(CAST(latitud as numeric), 4),round(CAST(longitud as numeric), 4)', eq.imei);

execute sql;

end loop;

update cobertura_tmp set num= -1* num where realtime='S';

create table if not exists cobertura_tmp_resumen  (num integer, lat numeric, lng numeric);
truncate cobertura_tmp_resumen;
--    select sum(num) as num , lat, lng into cobertura_tmp_resumen from cobertura_tmp group by lat,lng;

--    drop table if exists cobertura_tmp;

END;
$$;
Run Code Online (Sandbox Code Playgroud)

相同的脚本使用 Postico 版本 1.3.2 (2318) 从 Mac OSX 远程平稳运行。

问题:为什么不找到sqldeclare部分中的变量?

Erw*_*ter 8

您的错误消息说:

psql:cobertura.sql:29: ERROR:  "sql " is not a known variable
LINE 14: sql := format('insert into cobertura_tmp select count(*) as ...
Run Code Online (Sandbox Code Playgroud)

仔细看:"sql ",不是"sql"

这意味着您在“sql”之后有一个偷偷摸摸的隐形字符,而不是一个无辜的空格字符。这不在您的问题中,当您将其复制并粘贴到您的问题中时,可能在翻译中迷失了方向。您的原始代码包含以下内容:

sql?:= format ...
Run Code Online (Sandbox Code Playgroud)

你看到了吗?不?因为一个人看不到。(在这种特殊情况下,您可能会注意到该空间稍微宽了一些 - 如果您的浏览器、字体和字符集产生的结果与我的相同。)这是一个“表意空间”、 Unicode U+3000、 HTML &#12288。只是一个随机的例子。Unicode 中有各种类似的字符。用一个简单的空间替换它来解决您的问题。

dbfiddle在这里

我讨厌关于 Unicode 的这一点,太多的字符只会让人们感到困惑......

快速测试:

SELECT ascii(' ');   -- 32    -- plain space
SELECT ascii('?');  -- 12288 -- not a plain space
SELECT '?' = ' ';   -- f     -- not the same!
Run Code Online (Sandbox Code Playgroud)

快速测试字符串中是否有任何非 ASCII 字符:

SELECT octet_length(string) = char_length(string)  -- f
FROM  (SELECT text 'sql?:= format' AS string) t;
Run Code Online (Sandbox Code Playgroud)

假设编码为 UTF8,非 ASCII 字符占用 2-4 个字节,而纯 ASCII 字符占用一个字节。octet_length()返回

字符串中的字节数

while char_length()(与 相同length())返回字符数。对于全 ASCII,两者都返回相同的值。如果octet_length()返回更高的数字,则您有可疑字符。不一定意味着什么。字符串中的任何重音字符都适合它。

或者使用可以突出显示嫌疑人的编辑器。


除此之外,在执行此操作时,您的 plpgsql 代码会更高效,如下所示:

DO
$$
DECLARE
   _imei text;
   _sql  text;
BEGIN

IF to_regclass('pg_temp.cobertura_tmp') IS NULL  -- if tmp table does not exist
THEN
   CREATE TABLE cobertura_tmp (
      num      integer
    , realtime char(1)
    , lat      numeric
    , lng      numeric
    );
ELSE
   TRUNCATE TABLE cobertura_tmp;
END IF;

FOR _imei IN
   SELECT e.imei_equipo::text  -- imei is NOT NULL & *safe* against SQL injection
   FROM   cliente c
   JOIN   cliente_avl_equipo e ON e.id_cliente = c.id  -- assuming c.id is UNIQUE
   WHERE  c.nombre ILIKE '%enangab%'
   LIMIT  3
LOOP
   _sql := format(
   $s$
   INSERT INTO cobertura_tmp (num, realtime, lat, lng)
   SELECT count(*)::int * (CASE WHEN a.tipo = 'S' THEN -1 ELSE 1 END) -- AS num
        , a.tipo                         -- AS realtime
        , round(a.latitud::numeric, 4)   -- AS lat
        , round(a.longitud::numeric, 4)  -- AS lon
   FROM   reports.avl_historico_%s a
   WHERE  a.latitud  BETWEEN -38.67405472 AND -36.75131149
   AND    a.longitud BETWEEN -73.08429161 AND -69.65333954
   GROUP  BY 2,3,4
   $s$
 , _imei);

   EXECUTE _sql;
END LOOP;

/* integrated above to avoid costly extra update:
UPDATE cobertura_tmp
SET    num = -1 * num
WHERE  realtime = 'S';
*/

-- ... etc

END
$$;
Run Code Online (Sandbox Code Playgroud)

许多小细节,但这不是问题的主题。我怀疑您根本不需要临时表...

如果您想改进代码,请尝试一下。有关的: