0

我目前正在为我的项目建立一个评分表。

每个项目都有一个分数,因此数据库(postgres)可以按分数对项目进行排序并将它们返回给用户。

目前,该产品的总分由以下公式确定:

  • 新鲜度分数(由流程A计算)
  • 人气分数(由流程B计算)
  • 相关性分数(由流程 C 计算)

总计 = 0.5 * 新鲜度 + 0.25 * 流行度 + 0.25 * 相关度

流程 A、B、C 将运行几个小时并生成 (item_id, score, type),其中 type 可以是“新鲜”、“流行”或“相关”。

请注意,我必须保留这些值,因为它们是由不同的过程产生的。

我需要做什么才能执行SELECT * FROM items JOIN scores ON items.id == scores.item_id ORDER BY <total_score ??> DESC LIMIT 10 OFFSET 0;

编辑

type = total一个明显的答案是为所有项目生成另一个进程。这行得通,但这很麻烦,因为其中任何一个分数的每一次变化都需要更新总分。此外,它几乎可以将我的数据存储量从 25% 增加到 100%。我不认为这是一个最佳解决方案,因为整合它需要相当多的维护。

更新

这是我的分数表:

    Column     |            Type             |                         Modifiers                         | Storage  | Description
---------------+-----------------------------+-----------------------------------------------------------+----------+-------------
 created_at    | timestamp without time zone |                                                           | plain    |
 updated_at    | timestamp without time zone |                                                           | plain    |
 id            | integer                     | not null default                             | plain    |
 score         | double precision            | not null                                                  | plain    |
 type          | character varying           | not null                                                  | extended |
4

4 回答 4

2

按总分的表达式排序,分别连接到每个分数行,以便可以在计算中使用所有分数类型。

SELECT * FROM items
LEFT JOIN scores f ON items.id = f.item_id and type = 'freshness'
LEFT JOIN scores p ON items.id = p.item_id and type = 'popularity'
LEFT JOIN scores r ON items.id = r.item_id and type = 'relevance'
ORDER BY 
    0.5 * COALESCE(f.score, 0) +
    0.25 * COALESCE((p.score, 0) +
    0.25 * COALESCE(r.score) DESC
LIMIT 10 OFFSET 0

无需存储总数。

注意使用LEFT JOIN,这意味着仍然会返回没有特定分数类型的项目。对于任何缺失的分数类型,我曾经COALESCE()给零分。

您可能认为这会导致性能问题,但我对此表示怀疑。在考虑存储总数之前尝试一下,看看它的性能如何,这只是出于性能原因,因此是“早期优化”的情况 - 要避免的反模式。

于 2013-04-16T20:49:02.777 回答
2

这是使用虚拟列的另一种很酷的方法,如下所述

首先,创建一个视图来汇总每个项目的分数:

CREATE OR REPLACE VIEW vw_scores_rollup AS
SELECT id,
  SUM(CASE WHEN type = 'freshness' THEN score ELSE 0 END) AS freshness,
  SUM(CASE WHEN type = 'popularity' THEN score ELSE 0 END) AS popularity,
  SUM(CASE WHEN type = 'relevance' THEN score ELSE 0 END) AS relevance
FROM scores
GROUP BY id;

接下来,此函数将源表/视图作为参数。

CREATE OR REPLACE FUNCTION total(vw_scores_rollup) RETURNS numeric AS
$BODY$
  SELECT 0.5 * COALESCE($1.freshness, 0) + 0.25 * COALESCE($1.popularity, 0) + 0.25 * COALESCE($1.relevance, 0);
$BODY$
  LANGUAGE sql;

访问:

SELECT *, s.total
FROM items i
JOIN vw_scores_rollup s USING (id)
ORDER BY s.total DESC
LIMIT 10 OFFSET 0;

这是一个巧妙的技巧,并提供了一种直接访问总数的方法。

于 2013-04-16T21:09:28.250 回答
0

干得好...

SELECT item_id, SUM(S) TOTAL
FROM (
  SELECT item_id, 0.5 * score S
      FROM scores
      WHERE type = 'freshness'
  UNION ALL
  SELECT item_id, 0.25 * score
      FROM scores
      WHERE type IN ('popularity', 'relevance')
) Q1
GROUP BY item_id
ORDER BY TOTAL DESC;

[SQL 小提琴]

这将为您提供项目 ID 和相关的总分(按从高到低排序)。

items如有必要,您可以轻松地将其加入表格,限制到前 10 名等...


另一种可能...

SELECT
    item_id,
    SUM (
        CASE type
            WHEN 'freshness' THEN 0.5
            WHEN 'popularity' THEN 0.25
            WHEN 'relevance' THEN 0.25
        END
        * score
    ) TOTAL
FROM scores
GROUP BY item_id
ORDER BY TOTAL DESC;  
于 2013-04-17T00:40:49.303 回答
0

无需多次连接。加入前只需聚合。

select i.*, s.total
from
    items i
    inner join
    (
        select
            id,
                coalesce(sum((type = 'fresh')::integer * score * 0.5), 0)
                + coalesce(sum((type = 'popularity')::integer * score * 0.25), 0)
                + coalesce(sum((type = 'relevance')::integer * score * 0.25), 0)
            total
        from scores
        group by id
    ) s on i.id = s.id
order by s.total desc
limit 10
于 2013-04-17T12:58:21.533 回答