我使用PostgreSQL 10并成功运行CREATE EXTENSION unaccent;
。我有一个plgsql函数,其中包含以下whereText := 'lower(unaccent(place.name)) LIKE lower(unaccent($1))';
之后,根据用户选择的内容,可以在whereText
中添加更多条款。
whereText
最终用于查询:
placewithkeys := '%'||placename||'%';
RETURN QUERY EXECUTE format('SELECT id, name FROM '||fromText||' WHERE '||whereText)
USING placewithkeys , event, date;
即使我删除了whereText := 'LOWER(unaccent(place.name)) LIKE LOWER(unaccent($1))';
部分,LOWER
也不起作用。
我做select __my_function('Τζι');
,我什么也得不回来,即使我应该取回结果,因为在数据库中有名称Τζίμα
如果我删除unaccent
并离开LOWER
它有效,但不适用于口音:τζ
带回Τζίμα
应该。似乎unaccent
造成了问题。
我错过了什么?我怎样才能解决这个问题?
由于有关于语法和可能的SQLi的评论,我提供了整个函数定义,现在更改为在希腊语中对重音不敏感和不区分大小写的工作:
CREATE FUNCTION __a_search_place
(placename text, eventtype integer, eventdate integer, eventcentury integer, constructiondate integer, constructioncentury integer, arstyle integer, artype integer)
RETURNS TABLE
(place_id bigint, place_name text, place_geom geometry)
AS $$
DECLARE
selectText text;
fromText text;
whereText text;
usingText text;
placewithkeys text;
BEGIN
fromText := '
place
JOIN cep ON place.id = cep.place_id
JOIN event ON cep.event_id = event.id
';
whereText := 'unaccent(place.name) iLIKE unaccent($1)';
placewithkeys := '%'||placename||'%';
IF constructiondate IS NOT NULL OR constructioncentury IS NOT NULL OR arstyle IS NOT NULL OR artype IS NOT NULL THEN
fromText := fromText || '
JOIN construction ON cep.construction_id = construction.id
JOIN construction_atype ON construction.id = construction_atype.construction_id
JOIN construction_astyle ON construction.id = construction_astyle.construction_id
JOIN atype ON atype.id = construction_atype.atype_id
JOIN astyle ON astyle.id = construction_astyle.astyle_id
';
END IF;
IF eventtype IS NOT NULL THEN
whereText := whereText || 'AND event.type = $2 ';
END IF;
IF eventdate IS NOT NULL THEN
whereText := whereText || 'AND event.date = $3 ';
END IF;
IF eventcentury IS NOT NULL THEN
whereText := whereText || 'AND event.century = $4 ';
END IF;
IF constructiondate IS NOT NULL THEN
whereText := whereText || 'AND construction.date = $5 ';
END IF;
IF constructioncentury IS NOT NULL THEN
whereText := whereText || 'AND construction.century = $6 ';
END IF;
IF arstyle IS NOT NULL THEN
whereText := whereText || 'AND astyle.id = $7 ';
END IF;
IF artype IS NOT NULL THEN
whereText := whereText || 'AND atype.id = $8 ';
END IF;
whereText := whereText || '
GROUP BY place.id, place.geom, place.name
';
RETURN QUERY EXECUTE format('SELECT place.id, place.name, place.geom FROM '||fromText||' WHERE '||whereText)
USING placewithkeys, eventtype, eventdate, eventcentury, constructiondate, constructioncentury, arstyle, artype ;
END;
$$
LANGUAGE plpgsql;
我可以证实,unaccent()
目前似乎不适用于希腊字母。电话:
SELECT unaccent('
ἀ ἁ ἂ ἃ ἄ ἅ ἆ ἇ Ἀ Ἁ Ἂ Ἃ Ἄ Ἅ Ἆ Ἇ
ἐ ἑ ἒ ἓ ἔ ἕ Ἐ Ἑ Ἒ Ἓ Ἔ Ἕ
ἠ ἡ ἢ ἣ ἤ ἥ ἦ ἧ Ἠ Ἡ Ἢ Ἣ Ἤ Ἥ Ἦ Ἧ
ἰ ἱ ἲ ἳ ἴ ἵ ἶ ἷ Ἰ Ἱ Ἲ Ἳ Ἴ Ἵ Ἶ Ἷ
ὀ ὁ ὂ ὃ ὄ ὅ Ὀ Ὁ Ὂ Ὃ Ὄ Ὅ
ὐ ὑ ὒ ὓ ὔ ὕ ὖ ὗ Ὑ Ὓ Ὕ Ὗ
ὠ ὡ ὢ ὣ ὤ ὥ ὦ ὧ Ὠ Ὡ Ὢ Ὣ Ὤ Ὥ Ὦ Ὧ
ὰ ά ὲ έ ὴ ή ὶ ί ὸ ό ὺ ύ ὼ ώ
ᾀ ᾁ ᾂ ᾃ ᾄ ᾅ ᾆ ᾇ ᾈ ᾉ ᾊ ᾋ ᾌ ᾍ ᾎ ᾏ
ᾐ ᾑ ᾒ ᾓ ᾔ ᾕ ᾖ ᾗ ᾘ ᾙ ᾚ ᾛ ᾜ ᾝ ᾞ ᾟ
ᾠ ᾡ ᾢ ᾣ ᾤ ᾥ ᾦ ᾧ ᾨ ᾩ ᾪ ᾫ ᾬ ᾭ ᾮ ᾯ
ᾰ ᾱ ᾲ ᾳ ᾴ ᾶ ᾷ Ᾰ Ᾱ Ὰ Ά ᾼ ᾽ ι ᾿
῀ ῁ ῂ ῃ ῄ ῆ ῇ Ὲ Έ Ὴ Ή ῌ ῍ ῎ ῏
ῐ ῑ ῒ ΐ ῖ ῗ Ῐ Ῑ Ὶ Ί ῝ ῞ ῟
ῠ ῡ ῢ ΰ ῤ ῥ ῦ ῧ Ῠ Ῡ Ὺ Ύ Ῥ ῭ ΅ `
ῲ ῳ ῴ ῶ ῷ Ὸ Ό Ὼ Ώ ῼ ´ ῾ ');
...返回所有字母不变,没有按照我们的预期删除变音符号。 (我从Wikipedia page on Greek diacritics中提取了这个列表。)
看起来像unaccent module的缺点。您可以扩展默认的unaccent
字典或创建自己的字典。手册中有说明。我过去创建了几个词典,而且很简单。而你首先不需要这个:
Postgres对希腊字符的非同步规则:
Postgres 9.6的非强制性规则加上希腊字符:
但是,您需要对服务器的文件系统进行写访问 - 包含不相关文件的目录。所以,大多数云服务都不可能......
或者你可能report a bug并要求包括希腊语变音符号。
您提供的代码片段不易受SQL注入攻击。 $1
被连接为文字字符串,后来只在EXECUTE
命令中解析,其中值与USING
子句安全地传递。所以,那里没有不安全的连接。我会这样做,但是:
RETURN QUERY EXECUTE format(
$q$
SELECT id, name
FROM place ...
WHERE lower(unaccent(place.name)) LIKE '%' || lower(unaccent($1)) || '%'
$q$
)
USING placename, event, date;
笔记:
%
的两个LIKE
符号直接连接到主查询中,为查询规划器提供模式未锚定到开始或结束的信息,这可能有助于更有效的计划。只有用户输入(安全地)作为变量传递。WHERE
子句引用了表place
,所以FROM
子句无论如何都需要包含这个表。因此,您不能独立地连接FROM子句以开始。可能更好地将它全部保存在单个format()
中。ILIKE
而不是lower(...) LIKE lower(...)
。如果你使用trigram索引(对于这个查询似乎最好):那些也适用于ILIKE
:
LOWER LIKE vs iLIKELIKE
模式中转义具有特殊含义的字符?
How to escape string while matching pattern in PostgreSQL
Escape function for regular expression or LIKE patterns在您提供完整的功能之后......
CREATE OR REPLACE FUNCTION __a_search_place(
placename text
, eventtype int = NULL
, eventdate int = NULL
, eventcentury int = NULL
, constructiondate int = NULL
, constructioncentury int = NULL
, arstyle int = NULL
, artype int = NULL)
RETURNS TABLE(place_id bigint, place_name text, place_geom geometry) AS
$func$
BEGIN
-- RAISE NOTICE '%', concat_ws(E'\n' -- to debug
RETURN QUERY EXECUTE concat_ws(E'\n'
,'SELECT p.id, p.name, p.geom
FROM place p
WHERE unaccent(p.name) ILIKE (''%'' || unaccent($1) || ''%'')' -- no $-quotes
-- any input besides placename ($1)
, CASE WHEN NOT ($2,$3,$4,$5,$6,$7,$8) IS NULL THEN
'AND EXISTS (
SELECT
FROM cep
JOIN event e ON e.id = cep.event_id' END
-- constructiondate, constructioncentury, arstyle, artype
, CASE WHEN NOT ($5,$6,$7,$8) IS NULL THEN
'JOIN construction con ON cep.construction_id = con.id
JOIN construction_atype ON con.id = construction_atype.construction_id
JOIN construction_astyle ON con.id = construction_astyle.construction_id' END
-- arstyle, artype
, CASE WHEN NOT ($7,$8) IS NULL THEN
'JOIN atype ON atype.id = construction_atype.atype_id
JOIN astyle ON astyle.id = construction_astyle.astyle_id' END
, CASE WHEN NOT ($2,$3,$4,$5,$6,$7,$8) IS NULL THEN
'WHERE cep.place_id = p.id' END
, CASE WHEN eventtype IS NOT NULL THEN 'AND e.type = $2' END
, CASE WHEN eventdate IS NOT NULL THEN 'AND e.date = $3' END
, CASE WHEN eventcentury IS NOT NULL THEN 'AND e.century = $4' END
, CASE WHEN constructiondate IS NOT NULL THEN 'AND con.date = $5' END
, CASE WHEN constructioncentury IS NOT NULL THEN 'AND con.century = $6' END
, CASE WHEN arstyle IS NOT NULL THEN 'AND astyle.id = $7' END
, CASE WHEN artype IS NOT NULL THEN 'AND atype.id = $8' END
, CASE WHEN NOT ($2,$3,$4,$5,$6,$7,$8) IS NULL THEN
')' END
);
USING placename
, eventtype
, eventdate
, eventcentury
, constructiondate
, constructioncentury
, arstyle
, artype;
END
$func$ LANGUAGE plpgsql;
这是一个完全重写,有一些改进。应该使功能显着。也是SQLi安全的(就像你的原始版本)。在功能上应该是相同的,除了我加入较少的表的情况,这些表可能不会过滤通过单独连接到表而过滤的行。
主要特点:
EXISTS()
而不是在外层加上GROUP BY
的大量连接。这有助于实现更好的性能。有关:
Search a JSON array for an object containing a value matching a patternformat()
通常是从用户输入连接SQL的不错选择。但是,由于您封装了所有代码元素并且只传递了标志,因此在这种情况下您不需要它。相反,concat_ws()
是有帮助的。有关:
How to concatenate columns in a Postgres SELECT?SELECT __a_search_place('foo', 2, 3, 4);
SELECT __a_search_place('foo');
有关:
Optional argument in PL/pgSQL functionROW()
的短NOT NULL
语法:
Why is IS NOT NULL false when checking a row type?