Postgres 在查询中使用函数

Posted

技术标签:

【中文标题】Postgres 在查询中使用函数【英文标题】:Postgres using functions inside queries 【发布时间】:2015-12-11 05:04:22 【问题描述】:

我有一个包含常用词值的表来匹配品牌 - 所以当有人输入“可乐”时,我想匹配与其相关的任何可能的品牌名称以及原始术语。

CREATE TABLE word_association ( commonterm TEXT, assocterm TEXT);

INSERT INTO word_association ('coke', 'coca-cola'), ('coke', 'cocacola'), ('coke', 'coca-cola');

我有一个函数可以在管道分隔符字符串中创建这些值的列表以进行模式匹配:

CREATE OR REPLACE FUNCTION usp_get_search_terms(userterm text)
  RETURNS text AS
$BODY$DECLARE
    returnstr TEXT DEFAULT '';

BEGIN
    SET DATESTYLE TO DMY;

    returnstr := userterm;

    IF EXISTS (SELECT 1 FROM word_association WHERE LOWER(commonterm) = LOWER(userterm)) THEN
        SELECT  returnstr || '|' || string_agg(assocterm, '|') INTO returnstr
        FROM    word_association
        WHERE   commonterm = userterm;

    END IF;

    RETURN returnstr;

END;
$BODY$
  LANGUAGE plpgsql VOLATILE
  COST 100;
ALTER FUNCTION usp_get_search_terms(text)
  OWNER TO customer_role;

如果你调用 SELECT * FROM usp_get_search_terms('coke') 你会得到 ​​p>

coke|coca-cola|cocacola|coca cola

编辑:这个函数运行

我想运行一个插入此文本的查询,例如

SELECT X.article_number, X.online_description
FROM articles X
WHERE LOWER(X.online_description) % usp_get_search_terms ('coke');

这需要大约 56 秒才能针对我的约 500K 记录表运行。

如果我得到原始文本并在查询中使用它,它需要大约 300 毫秒,例如

SELECT X.article_number, X.online_description
FROM articles X
WHERE X.online_description % '(coke|coca-cola|cocacola|coca cola)';

结果集是相同的。

我尝试将函数的输出字符串修改为例如将其括在引号和括号中,但似乎没有什么区别。

有人能告诉我为什么这里有区别吗?是数据类型还是关于在查询中调用函数的东西?谢谢。

【问题讨论】:

【参考方案1】:

您的函数可能需要 100 毫秒,但它不会调用您的函数一次;它调用了 500,000 次。

这是因为你的函数声明为VOLATILE。这告诉 Postgres 该函数在查询中多次调用时返回不同的值(如clock_timestamp()random()),或者它以某种方式改变数据库的状态(例如,通过插入记录)。

如果你的函数只包含SELECTs,没有INSERTs,调用其他VOLATILE函数,或者其他副作用,那么你可以声明它STABLE。这告诉规划器它可以只调用一次函数并重用结果而不影响查询结果。

但是您的函数确实有副作用,因为SET DATESTYLE 语句在会话的其余部分生效。然而,我怀疑这是故意的。您也许可以将其删除,因为日期格式看起来与其中的任何内容都不相关。但如果有必要,正确的做法是使用SET clause of the CREATE FUNCTION statement 仅在函数调用期间更改它:

...
$BODY$
  LANGUAGE plpgsql STABLE
  SET DATESTYLE TO DMY
  COST 100;

慢版本查询的另一个问题是调用LOWER(X.online_description),这将阻止查询使用索引(因为online_description 已编入索引,但LOWER(online_description) 未编入索引)。

通过这些更改,两个查询的性能是相同的;看到这个SQLFiddle。

【讨论】:

这种解释是有道理的——我已经改变了它(完全放弃了 SET DATESTYLE,因为我不需要它,只是出于习惯),它改进了查询,但只下降到 48 秒左右。所以它一定是你所说的重复数千次的函数调用的一个因素。我将不得不更改调用它的 NodeJS 以使用静态字符串。谢谢你的帮助 - 有用 P.S.我什至尝试将其更改为 IMMUTABLE 但将其缩短为 7 秒。在这种情况下显然不是很有用。 好吧,还有几个问题……online_description 上有索引吗?我不认为% 运算符是内置的,它是什么?您的示例查询中只有一个调用LOWER(),这是故意的吗?慢版本和快版本的EXPLAIN 输出是什么? 是的 online_description 字段有 GIN (online_description COLLATE pg_catalog."default" gin_trgm_ops)。 EXPLAIN 版本完全不同 - 函数调用有很多垃圾,而原始版本非常简单。 % 运算符 = 不确定。我来的时候在那儿,所以我要研究一下。它在大多数语言中的“模数”。对文本的影响我必须找出来。【参考方案2】:

所以今天早上黎明时分我得到了答案——CTE 来救援!

特别是因为这是一个非常大的查询的“简单”版本,它有助于将其单独定义一次,然后对其进行匹配。另一种方法(假设我从 NodeJS 平台调用它)是让一个请求检索术语字符串,然后发出另一个请求以将字符串传回。不优雅。

WITH matches AS
    (   SELECT * FROM usp_get_search_terms('coke')  )
, main AS 
    (   SELECT X.article_number, X.online_description
        FROM articles X
        JOIN matches M ON X.online_description % M.usp_get_search_terms )
SELECT * FROM main

执行时间约为 300-500 毫秒,具体取决于搜索的术语和返回的文章。

感谢你们所有的投入 - 我学到了一些关于 PostGres 的东西,我的 MS-SQL 背景不一定让我准备好:)

【讨论】:

【参考方案3】:

您是否尝试过删除 IF EXISTS() 并简单地使用:

SELECT  returnstr || '|' || string_agg(assocterm, '|') INTO returnstr
FROM    word_association
WHERE   LOWER(commonterm) = LOWER(userterm)

【讨论】:

我需要字符串中的原始搜索词。该函数似乎不是问题 - 它在 100 毫秒内执行以产生输出。执行它编写查询是我不明白的。【参考方案4】:

而不是为每一行调用一次函数:

select x.article_number, x.online_description
from
    woolworths.articles x
    cross join
    woolworths.usp_get_search_terms ('coke') c (s)
where lower(x.online_description) % s

【讨论】:

以上是关于Postgres 在查询中使用函数的主要内容,如果未能解决你的问题,请参考以下文章

Postgres - 连接三个表并在查询中对数据使用聚合函数

如何在 jpa 查询中调用 postgres position() 函数

使用窗口函数在 Postgres 上使用 SqlAlchemy 限制查询

在 Postgres 的 jsonb_array_elements_text 函数中使用 SELECT 子查询

带有 jsonb 参数的 Postgres 函数

Postgres 函数不返回任何结果,但是当相同的查询在函数之外返回结果时