【问题标题】:PostgreSQL function to perform a series of operations with the current record and then return the resultPostgreSQL函数对当前记录执行一系列操作,然后返回结果
【发布时间】:2014-04-27 22:21:59
【问题描述】:

我不熟悉 PostgreSQL 函数和存储过程,但在过去的几天里我已经尽力学习了。在这里问之前我很努力。

基本上,我有一种情况,我不能使用简单的 SQL,而在这种情况下,函数最有帮助。 (那是因为我通过 AJAX 将查询发送到返回 JSONP 的基于 Postgres 的 Web 服务,并且由于查询是基于非预定数量的变量在 JavaScript 中构建的,它可以增长到超过 2000 个左右的 URL 字符MSIE 中允许的限制。)

比如说,我有一个名为 clients 的表:

+-------------+-------------------+-------------+---------------+
|   CLIENT    | MONTHLY_PURCHASES | SALES_VALUE | RETURNS_VALUE |
+-------------+-------------------+-------------+---------------+
| Mercury Ltd | 3                 | 400000      | 30000         |
| Saturn Plc  | 11                | 150000      | 30000         |
| Uranus Ltd  | 4                 | 80000       | 1000          |
+-------------+-------------------+-------------+---------------+

查询应该返回按列中包含的各种标准排名的客户。列数将来可能会增加。

例如,如果我想获得前 10 个最佳客户,从 100(最好)到 0(最差)排名,SQL 查询将是:

WITH var AS (
    --we need the min and max values for each criteria, to calculate the rank later
    SELECT 
      MIN(monthly_purchases) AS min_pur,
      MAX(monthly_purchases) AS max_pur,
      MIN(sales_value) AS min_sales,
      MAX(sales_value) AS max_sales,
      MIN(returns_value) AS min_returns,
      MAX(returns_value) AS max_returns
    FROM clients
),
--standardise values to a 0 to 100 range, so we can compare apples with oranges, and assign weights to each criteria (from 0 to 1)
weights AS (        
    SELECT client,
      --the higher the number of purchases the better. Weight: 0.2 out of 1.
      0.2 * (clients.monthly_purchases - var.min_pur) / (var.max_pur - var.min_pur) * 100 AS rnk_pur,
      --the higher the value of sales, the better. Weight: 0.4 out of 1.
      0.4 * (clients.sales_value - var.min_sales) / (var.max_sales - var.min_sales) * 100 AS rnk_sales,
      --the lower the value of returns the better. Weight: 0.4 out of 1.
      0.4 * (1 - (clients.returns_value - var.min_returns) / (var.max_returns - var.min_returns)) * 100 AS rnk_returns
    FROM clients, var
)
SELECT weights.client, weights.rnk_pur + weights.rnk_sales + weights.rnk_returns as overall_rank FROM weights ORDER BY overall_rank DESC LIMIT 10

一切都好,但实际上列数更大(大约 40 个),用户可以立即使用 1 到 15 之间的任何值进行排名。

因此,SQL 路由是不可行的。我尝试创建一个至少可以对值进行标准化的函数:

--Firstly, a function to find the highest value in an array
DROP FUNCTION IF EXISTS array_max(float[]);

CREATE OR REPLACE FUNCTION array_max(float[])
RETURNS float
AS $$
  select max(x) from unnest($1)x order by 1;
$$
LANGUAGE 'sql';

--Secondly, a function to find the lowest value in an array
DROP FUNCTION IF EXISTS array_min(float[]);

CREATE OR REPLACE FUNCTION array_min(float[])
RETURNS float
AS $$
  select min(x) from unnest($1)x order by 1;
$$
LANGUAGE 'sql';

--Finally, our function
DROP FUNCTION IF EXISTS standardise(float[], float);

CREATE OR REPLACE FUNCTION standardise(myarray float[], val float)
RETURNS float AS
$$

DECLARE
  minimum float;
  maximum float;
  calc_result float;
BEGIN
  minimum = array_min(myarray);
  maximum = array_max(myarray);

  calc_result = (val - minimum) / (maximum - minimum) * 100;

  RETURN calc_result;
END;
$$
LANGUAGE 'plpgsql' IMMUTABLE;

毫不奇怪,这个功能很慢。如果这样使用:

SELECT 0.5 *standardise((SELECT array(SELECT sales_value FROM clients)), clients.sales_value) AS rnk_sales 来自客户

...这是可以接受的。任何涉及订购的东西都会让它慢下来。即:

SELECT 0.5 *standardise((SELECT array(SELECT sales_value FROM clients)), clients.sales_value) AS rnk_sales 从客户订购 rnk_sales 限制 10

有什么方法可以提高上述功能的速度。或者,也许是完全不同的方法?任何帮助将非常感激。谢谢!

更新:

我使用最后一个查询运行了 EXPLAIN ANALYZE。为此,我只从整个表中选择了一个样本,因为它花费的时间太长了。我等了10分钟后取消了查询。这是一张有 1000 个客户的桌子:

解释分析选择 0.5 * 标准化((选择数组(选择 sales_value FROM clients_sample)),clients_sample.sales_value)作为 rnk_sales FROM clients_sample 按 rnk_sales LIMIT 10 订购

结果:

限制(成本=78.82..78.83 行=10 宽度=8)(实际时间=357.806..357.822 行=10 循环=1) InitPlan 2(返回 1 美元) -> 结果(成本=12.00..12.00 行=1 宽度=0)(实际时间=1.267..1.268 行=1 循环=1) 初始计划 1(返回 0 美元) -> clients_sample clients_sample_1 上的 Seq Scan(成本=0.00..12.00 行=1000 宽度=8)(实际时间=0.002..0.666 行=1000 循环=1) -> 排序(成本=66.82..67.32 行=1000 宽度=8)(实际时间=357.805..357.809 行=10 循环=1) 排序键:((0.5::double precision *standardise($1, clients_sample.sales_value))) 排序方法:top-N heapsort 内存:25kB -> clients_sample 上的 Seq Scan(成本=0.00..62.50 行=1000 宽度=8)(实际时间=1.870..356.742 行=1000 循环=1) 总运行时间:357.850 毫秒

【问题讨论】:

  • 注意:在纯 SQL 查询中,varweights CTE 永远不会被引用。 (请添加一些相关名称以使事情变得清晰)
  • 评论与 CTE“权重”中的代码不匹配。
  • @wildplasser:做到了。应该从实际查询中复制粘贴而不是直接编写。
  • @MikeSherrill'CatRecall' 谢谢!这也帮助我找到了另一个错误。我希望它现在很好
  • 您可以运行explain analyze your-last-query-with-order-by-and-limit,并将结果粘贴到问题的末尾吗?

标签: postgresql stored-procedures plpgsql sql-function postgresql-9.0


【解决方案1】:

净化的辅助函数

CREATE OR REPLACE FUNCTION array_max(float[])
  RETURNS float AS
'SELECT max(x) from unnest($1) x'
LANGUAGE sql;

ORDER BY 1 将毫无用处,因为max(x) 无论如何都会返回一行。
array_min(float[]) 也一样;

但是,不要使用这些功能。在一个电话中获得min()max() 更便宜。

主要功能:

改用简单的 SQL 函数:

CREATE OR REPLACE FUNCTION standardise(_arr float[], _val float)
  RETURNS float AS
$func$
SELECT ((_val - min_x) * 100) / (max_x - min_x)
FROM (
   SELECT min(x) AS min_x, max(x) AS max_x
   FROM   unnest($1) x
   ) sub
$func$
LANGUAGE sql IMMUTABLE;
  • 使用子查询同时获取两个聚合。
  • 通常先乘以更高的精度。
  • 不要引用语言名称。

【讨论】:

  • 谢谢!该解决方案工作并返回结果不到以前所需时间的一半。大表仍然很痛苦,但我想那是我应该相应地添加索引的地方
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-06-17
  • 2011-09-30
  • 1970-01-01
  • 2019-02-14
  • 2013-12-23
  • 2020-02-16
  • 2021-06-25
相关资源
最近更新 更多