我正在为我的物品建立一个得分表。
每个项目都有一个分数,因此数据库(postgres)可以按分数对项目进行排序并将其返回给用户。
目前,产品的总分由以下公式确定:
总计= 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 |
答案 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;
这将为您提供项目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