架构设计:如何合并“总计”?

时间:2013-04-16 20:36:06

标签: database postgresql database-design schema

我正在为我的物品建立一个得分表。

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

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

  • 新鲜度评分(按过程A计算)
  • 受欢迎程度得分(按过程B计算)
  • 相关性得分(按流程C计算)
  

总计= 0.5 *新鲜度+ 0.25 *人气+ 0.25 *相关性

进程A,B,C将运行几个小时并生成(item_id,score,type),其中type可以是“fresh”,“popular”或“related”。

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

我需要做什么才能执行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 个答案:

答案 0 :(得分: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()为任何缺失的分数类型得分为零。

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

答案 1 :(得分:2)

这是使用虚拟列执行此操作的另一种很酷的方法,如here所述:

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

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;

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

答案 2 :(得分: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 Fiddle]

这将为您提供项目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;  

答案 3 :(得分: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