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)
但它抱怨以下错误:
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:
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 远程平稳运行。
问题:为什么不找到sql该declare部分中的变量?
您的错误消息说:
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 ...
仔细看:"sql ",不是"sql"
这意味着您在“sql”之后有一个偷偷摸摸的隐形字符,而不是一个无辜的空格字符。这不在您的问题中,当您将其复制并粘贴到您的问题中时,可能在翻译中迷失了方向。您的原始代码包含以下内容:
sql?:= format ...
Run Code Online (Sandbox Code Playgroud)
你看到了吗?不?因为一个人看不到。(在这种特殊情况下,您可能会注意到该空间稍微宽了一些 - 如果您的浏览器、字体和字符集产生的结果与我的相同。)这是一个“表意空间”、 Unicode U+3000、 HTML  。只是一个随机的例子。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)
许多小细节,但这不是问题的主题。我怀疑您根本不需要临时表...
如果您想改进代码,请尝试一下。有关的: