我正在尝试调试一个不是我自己创建的函数 ( dms2dd )。我制作了自己的测试函数(见下文)并将我的问题归结为特定的行/值。
如果我运行以下查询:
SELECT "Lat", "Long", test_dolf("Lat"), test_dolf("Long") FROM pawikan WHERE "Lat" IS NOT NULL AND "Long" IS NOT NULL ORDER BY index LIMIT 1 OFFSET 29130
我得到以下输出:
'N6° 6' 9.4824"';'E118° 26' 49.1172'' ';'9.4824';'49.1172'
这正是我所期望的。 但是使用以下查询:
SELECT "Lat", "Long", CAST(test_dolf("Lat") as numeric), test_dolf("Long") FROM pawikan WHERE "Lat" IS NOT NULL AND "Long" IS NOT NULL ORDER BY index LIMIT 1 OFFSET 29130
我得到了错误
ERROR: invalid input syntax for type numeric: ""
SQL state: 22P02
错误表明我试图转换为数字的 varchar 值是空的,但正如您从前面的查询中看到的那样,它不是。它只是一个有效的数字 varchar。实际上,如果我复制粘贴该值并运行:
SELECT CAST('9.4824' AS numeric);
它完全有效,查询实际上产生了一个有效的数字。 更重要的是,如果我将第一个查询的结果存储在一个中间表中:
SELECT "Lat", "Long", test_dolf("Lat") as lat_sec, test_dolf("Long") as long_sec INTO dms2dd_test FROM pawikan WHERE "Lat" IS NOT NULL AND "Long" IS NOT NULL ORDER BY index LIMIT 11 OFFSET 29120
然后发出一个
SELECT CAST(long_sec as numeric), CAST(lat_sec AS numeric) FROM dms2dd_test;
它完全有效。 即使这样也能正常工作:
SELECT test_dolf(E'N6° 6\' 9.4824"')::numeric as lat_sec
那么这里出了什么问题呢?看起来在我转换为数字的第二个查询中,一个不同的值被传递到我的函数,但我测试了排序列(索引)并且它只包含唯一的 bigints。
这是 test_dolf 函数的代码:
CREATE OR REPLACE FUNCTION public.test_dolf(strdegminsec character varying)
RETURNS varchar AS
$BODY$
DECLARE
i numeric;
intDmsLen numeric; -- Length of original string
strCompassPoint Char(1);
strNorm varchar(16) = ''; -- Will contain normalized string
strDegMinSecB varchar(100);
blnGotSeparator integer; -- Keeps track of separator sequences
arrDegMinSec varchar[]; -- TYPE stringarray is table of varchar(2048) ;
strChr Char(1);
BEGIN
strDegMinSec := regexp_replace(replace(strdegminsec,E'\'\'','"'),' "([0-9]+)',E' \\1"');
-- Remove leading and trailing spaces
strDegMinSecB := REPLACE(strDegMinSec,' ','');
intDmsLen := Length(strDegMinSecB);
blnGotSeparator := 0; -- Not in separator sequence right now
-- Loop over string, replacing anything that is not a digit or a
-- decimal separator with
-- a single blank
FOR i in 1..intDmsLen LOOP
-- Get current character
strChr := SubStr(strDegMinSecB, i, 1);
-- either add character to normalized string or replace
-- separator sequence with single blank
If strpos('0123456789,.', strChr) > 0 Then
-- add character but replace comma with point
If (strChr <> ',') Then
strNorm := strNorm || strChr;
Else
strNorm := strNorm || '.';
End If;
blnGotSeparator := 0;
ElsIf strpos('neswNESW',strChr) > 0 Then -- Extract Compass Point if present
strCompassPoint := strChr;
Else
-- ensure only one separator is replaced with a blank -
-- suppress the rest
If blnGotSeparator = 0 Then
strNorm := strNorm || ' ';
blnGotSeparator := 0;
End If;
End If;
End Loop;
-- Split normalized string into array of max 3 components
arrDegMinSec := string_to_array(strNorm, ' ');
return arrDegMinSec[3];
End
$BODY$
LANGUAGE plpgsql IMMUTABLE
COST 100;
最佳答案
我想出了问题所在。它看起来像 postgresql,即使我执行了 LIMIT 和 OFFSET,仍然会为该框架外的其他行调用 select 中的函数。
我通过将引发异常的代码放入我的函数中并捕获由此产生的错误,并在该异常发生时引发 NOTICE 错误来解决这个问题(参见下面的函数,特别是函数末尾的 BEGIN EXCEPTION END block ).该通知显示为警告,但不会导致代码执行停止。突然间,我发现该函数不仅针对我期望它被调用的行调用,而且还针对一大堆其他行调用。这完全不是我所期望的,对我来说有点反直觉,但我想这就是 postgresql 应该如何工作的。
因为在 postgresql 中捕获异常是相当昂贵的,我想我需要添加一个测试来首先防止异常(我可以测试 arrDegMinSec
的长度和项目 1- 的值该数组的 3 并在无效值的情况下返回 NULL。
CREATE OR REPLACE FUNCTION public.test_dolf(strdegminsec character varying)
RETURNS numeric AS
$BODY$
DECLARE
i numeric;
intDmsLen numeric; -- Length of original string
strCompassPoint Char(1);
strNorm varchar(16) = ''; -- Will contain normalized string
strDegMinSecB varchar(100);
blnGotSeparator integer; -- Keeps track of separator sequences
arrDegMinSec varchar[]; -- TYPE stringarray is table of varchar(2048) ;
strChr Char(1);
retval numeric;
BEGIN
strDegMinSec := regexp_replace(replace(strdegminsec,E'\'\'','"'),' "([0-9]+)',E' \\1"');
-- Remove leading and trailing spaces
strDegMinSecB := REPLACE(strDegMinSec,' ','');
intDmsLen := Length(strDegMinSecB);
blnGotSeparator := 0; -- Not in separator sequence right now
-- Loop over string, replacing anything that is not a digit or a
-- decimal separator with
-- a single blank
FOR i in 1..intDmsLen LOOP
-- Get current character
strChr := SubStr(strDegMinSecB, i, 1);
-- either add character to normalized string or replace
-- separator sequence with single blank
If strpos('0123456789,.', strChr) > 0 Then
-- add character but replace comma with point
If (strChr <> ',') Then
strNorm := strNorm || strChr;
Else
strNorm := strNorm || '.';
End If;
blnGotSeparator := 0;
ElsIf strpos('neswNESW',strChr) > 0 Then -- Extract Compass Point if present
strCompassPoint := strChr;
Else
-- ensure only one separator is replaced with a blank -
-- suppress the rest
If blnGotSeparator = 0 Then
strNorm := strNorm || ' ';
blnGotSeparator := 0;
End If;
End If;
End Loop;
-- Split normalized string into array of max 3 components
arrDegMinSec := string_to_array(strNorm, ' ');
BEGIN
retval := arrDegMinSec[3]::numeric;
return retval;
EXCEPTION
WHEN SQLSTATE '22P02' THEN
RAISE NOTICE 'Incorrect value %', strDegMinSec;
RETURN NULL;
END;
End
$BODY$
LANGUAGE plpgsql IMMUTABLE
COST 100;
编辑
@michel.milezzi 提供的另一种不需要修改函数的解决方案是将查询中的函数调用更改为
CAST(NULLIF(test_dolf("Lat"), '') as numeric)
确实正如@abelisto 所建议的那样,我也可以将查询放在子查询中,并且只在主查询中将其转换为数字,如下所示:
SELECT "Lat", "Long", CAST(test_dolf("Lat") as numeric), test_dolf("Long") FROM (SELECT * FROM pawikan WHERE "Lat" IS NOT NULL AND "Long" IS NOT NULL ORDER BY index LIMIT 1 OFFSET 29130) as t
这确实可以避免问题的发生,从而简化调试过程。
话虽这么说,但无论如何我都打算修改该函数(以使其对脏数据更加健壮),所以在这种情况下对我来说这是最好的解决方案。
关于数字 : "" while value is not an empty varchar 类型的 postgresql 奇怪的无效输入语法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44732534/