Database 模式设计:你是如何整合的;总数;?

Database 模式设计:你是如何整合的;总数;?,database,postgresql,database-design,schema,Database,Postgresql,Database Design,Schema,我目前正在为我的项目建立评分表 每个项目都有一个分数,因此数据库(postgres)可以按分数对项目进行排序并将其返回给用户 目前,该产品的总分由以下公式确定: 新鲜度分数(由过程A计算) 人气得分(流程B计算) 相关性得分(由过程C计算) 总计=0.5*新鲜度+0.25*受欢迎度+0.25*相关性 流程A、B、C将运行几个小时,并生成(项目id、分数、类型),其中类型可以是“新鲜”、“流行”或“相关” 请注意,我必须保留这些值,因为它们是由不同的进程生成的 我需要做什么才能执行SELECT

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

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

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

  • 新鲜度分数(由过程A计算)
  • 人气得分(流程B计算)
  • 相关性得分(由过程C计算)
总计=0.5*新鲜度+0.25*受欢迎度+0.25*相关性

流程A、B、C将运行几个小时,并生成(项目id、分数、类型),其中类型可以是“新鲜”、“流行”或“相关”

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

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

编辑

一个明显的答案是让另一个流程为所有项目生成
type=total
。这是可行的,但它是一个痛苦的驴,因为任何一个分数的每一个变化都需要更新总数。此外,它几乎可以将我的数据存储从25%增加到100%。我不认为这是一个最佳的解决方案,因为需要有相当多的保持。p> 更新

这是我的分数表:

    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 |

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

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()
对任何缺少的分数类型给予零分


您可能认为这会导致性能问题,但我对此表示怀疑。试一试,看看它是如何执行的,在考虑存储总数的时候,这只是出于性能的原因,因此是一个“优化早期”的例子——一个要避免的反模式。< /P> < P >由总分的表达式表示,单独加入每个分数行,以便所有的分数类型都可以用于计算。< /P>
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()
对任何缺少的分数类型给予零分


您可能认为这会导致性能问题,但我对此表示怀疑。尝试它,看看它如何执行之前,您考虑存储总额,这将是出于性能的原因,因此,一个“优化早期”-一个反模式,以避免。< / P > < P>这是另一个很酷的方式使用虚拟列,如所描述:

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

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;

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

下面是另一种使用虚拟列来完成此操作的很酷的方法,如下所述:

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

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;
这是一个巧妙的技巧,提供了一种直接的方法来获取总数。

给你

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和相关总分(按从高到低排序)

如有必要,您可以轻松地将其与
表连接起来,限制在前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;  
给你

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和相关总分(按从高到低排序)

如有必要,您可以轻松地将其与
表连接起来,限制在前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;  

不需要多个连接。在加入之前先聚合

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

不需要多个连接。在加入之前先聚合

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

@波希米亚人:请注意,您的意思是“=”而不是“=”…在切换语言时常见的打字错误@妈的!这是我注意到的关于查询的第一件事,但我总是复制粘贴它。现在修好了。谢谢@波希米亚人如果结果来自分数表本身,你怎么能说
orderby0.5*新鲜度…
?现在你已经发布了模式,这就更清楚了。请参阅编辑后的答案,了解如何计算所有分数类型in@Bohemian:请注意,您的意思是“=”而不是“=”…切换语言时常见的打字错误@妈的!这是我注意到的关于查询的第一件事,但我总是复制粘贴它。现在修好了。谢谢@波希米亚人如果结果来自分数表本身,你怎么能说
orderby0.5*新鲜度…
?现在你已经发布了模式,这就更清楚了。请参阅编辑后的答案,了解如何将所有分数类型计算在内。请参阅问题更新,显示分数表中没有这些列(顺便说一句,我已通过答案更正,以适应新的信息)。a)看看我提到的帖子
s.total
是调用total()并传递行的一种简捷方法。b) 谢谢!我没有看到变化……威尔edit@mu:在博客里有很好的解释。我最初的回答假设新鲜度、受欢迎度和相关性是分数表中的列。如果采用这种结构,你可以索引
s.total
——我使用了这种技术来快速访问排名数据。别忘了选择coalesce(0.5*$1.freshness,0)+coalesce(0.25*$1.popularity,0)+coalesce(0.25*$1.relationship,0)@discoseared:很好,谢谢!我更改了视图,因此如果给定类型没有记录,它将返回0。尽管有点多余,我还是按照建议在total函数中添加了
COALESCE