Postgres使用查询中的函数

时间:2015-12-11 05:04:23

标签: postgresql postgresql-9.4

我有一张常用字词值表可以与品牌匹配 - 所以当有人输入"焦炭"我希望匹配与之相关的任何可能的品牌名称以及原始术语。

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|coca-cola|cocacola|coca cola

编辑:此函数运行<100毫秒,因此工作正常。

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

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)';

结果集完全相同。

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

有人可以告诉我这里有什么区别吗?它是关于在查询中调用函数的数据类型吗?感谢。

4 个答案:

答案 0 :(得分:4)

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

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

如果您的功能仅包含SELECT s,没有INSERT s,调用其他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

答案 1 :(得分:1)

所以今天早上我的答案就出现了 - 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背景并不一定能帮助我:)

答案 2 :(得分:0)

您是否尝试删除IF EXISTS()并使用:

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

答案 3 :(得分:0)

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

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