sle*_*vin 6 postgresql dynamic-sql plpgsql unaccent accent-insensitive
我使用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;
Run Code Online (Sandbox Code Playgroud)
将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;
Run Code Online (Sandbox Code Playgroud)
我可以证实,unaccent()目前似乎不适用于希腊字母.电话:
SELECT unaccent('
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ? ? ');
Run Code Online (Sandbox Code Playgroud)
...返回所有字母不变,没有按照我们的预期删除变音符号.
(我从希腊变音符号的维基百科页面中提取了这个列表.)
看起来像是unaccent模块的缺点.您可以扩展默认unaccent()字典或创建自己的字典.手册中有说明.我过去创建了几个词典,而且很简单.而你首先不需要这个:
Postgres对希腊字符的非同步规则:
Postgres 9.6的非强制性规则加上希腊字符:
但是,您需要对服务器的文件系统进行写访问 - 包含不相关文件的目录.所以,大多数云服务都不可能......
或者您可能会报告错误并要求包含希腊语变音符号.
你给出的代码片段不容易受到SQL注入.unaccent被连接为文字字符串,并且仅在$1稍后的命令中解析,其中值与该EXECUTE子句安全地一起传递.所以,那里没有不安全的连接.我会这样做,但是:
RETURN QUERY EXECUTE format(
$q$
SELECT id, name
FROM place ...
WHERE lower(unaccent(place.name)) LIKE '%' || lower(unaccent($1)) || '%'
$q$
)
USING placename, event, date;
Run Code Online (Sandbox Code Playgroud)
笔记:
不那么令人困惑 - 你的原创甚至混淆了帕维尔的评论,这是该领域的专业人士.
plpgsql中的赋值稍微贵一些(比其他PL中的赋值更多),因此采用编码风格,分配很少.
将两个USING符号%直接连接到主查询中,为查询规划器提供模式未锚定到开始或结束的信息,这可能有助于更有效的计划.只有用户输入(安全地)作为变量传递.
由于您的LIKE子句引用了表WHERE,因此该place子句无论如何都需要包含此表.因此,您不能独立地连接FROM子句以开始.可能更好地保持一个单一的FROM.
使用美元报价,因此您不必另外转义单引号.
也许只是用format()而不是ILIKE.如果你使用trigram索引(对于这个查询似乎最好):那些也可以使用lower(...) LIKE lower(...):
我假设你知道你可能需要在ILIKE模式中转义具有特殊含义的字符吗?
将完整的函数添加到quersion后...
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;
Run Code Online (Sandbox Code Playgroud)
这是对该功能的完全重写.由于一些改进,应该使功能更快.也是SQLi安全的(就像你的原始版本一样).应该在功能上与原始功能相同 - 除了我加入较少表的情况,这可能不会过滤通过单独连接到表而过滤的行.
主要特点:
LIKE在外层加上使用而不是大量的连接EXISTS().这有助于实现更好的性能.有关:
GROUP BY通常是从用户输入连接SQL的好选择.但是,由于您封装了所有代码元素并且只传递了标志,因此在这种情况下您不需要它.相反,format()是有帮助的.有关:
只连接您实际需要的JOIN.
分配越少,代码越短.
参数的默认值.允许使用缺少参数的简化调用.喜欢:
SELECT __a_search_place('foo', 2, 3, 4);
SELECT __a_search_place('foo');
Run Code Online (Sandbox Code Playgroud)
有关:
关于concat_ws()测试是否有任何值的简短语法ROW():
| 归档时间: |
|
| 查看次数: |
382 次 |
| 最近记录: |