我使用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;
答案 0 :(得分:8)
我可以确认,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;
注意:
不那么令人困惑 - 你的原作甚至在评论中混淆了Pavel,这是该领域的专业人士。
plpgsql中的赋值稍微贵一些(比其他PL中的分配更多),因此采用编码风格,分配很少。
直接将%
的两个LIKE
符号连接到主查询中,为查询规划器提供模式未锚定到开始或结束的信息,可以< / em>帮助提高计划效率。只有用户输入(安全地)作为变量传递。
由于您的WHERE
子句引用了表place
,因此FROM
子句无论如何都需要包含此表。因此,您不能独立地连接FROM子句以开始。可能最好将它全部保存在一个format()
中。
使用美元报价,因此您不必另外转义单引号。
或许只使用ILIKE
代替lower(...) LIKE lower(...)
。如果您使用trigram索引(对于此查询似乎最好):那些也与ILIKE
一起使用:
我假设你知道你可能需要在LIKE
模式中转义具有特殊含义的字符吗?
提供完整功能后......
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
的大量连接。这有助于实现更好的性能。相关:
format()
通常是从用户输入连接SQL的不错选择。但是,由于您封装了所有代码元素并且只传递了标记,因此在这种情况下您不需要 。相反,concat_ws()
会有所帮助。相关:
仅连接您实际需要的JOIN。
分配越少,代码越短。
参数的默认值。允许使用缺少参数的简化调用。像:
SELECT __a_search_place('foo', 2, 3, 4);
SELECT __a_search_place('foo');
相关:
关于测试任何值是ROW()
的简短NOT NULL
语法: