【问题标题】:Random row selection with weighted filters in SQL/PostgreSQL在 SQL/PostgreSQL 中使用加权过滤器进行随机行选择
【发布时间】:2018-12-14 08:22:33
【问题描述】:

我有一个问题表,我需要准备 X 个问题来准备测试。问题需要根据多个标准(学科、机构、领域等)进行过滤,每个标准具有不同的权重。

过滤器权重在查询之外动态设置和规范化。例如:

  1. 主题 1 — 0.4
  2. 主题 2 — 0.1
  3. 主题 3 — 0.5
  4. 机构 1 — 0.2
  5. 机构 2 — 0.04
  6. 机构 3 — 0.76
  7. 区域 1 — 1

其他几点:

  • 今天,我有10个不同的过滤器(学科、机构、地区等),但用户可以以多种和混合的方式选择(例如:10个学科、5个机构、30个地区等),比如在上面的示例中。
  • 问题表有大约 50 万行;
  • 过滤器是 N - N 个问题;
  • 过滤后,我想限制返回的行数;
  • 如果某个过滤器无法提供更多问题,则必须考虑其他过滤器(请记住:我想准备一个测试 -- 如果我还有问题,则必须使用它们)
  • 我非常关心这个查询的性能。

为了说明,如果我不想加权过滤器,我会这样做:

SELECT
    *
FROM
    public.questions q
    INNER JOIN public.subjects_questions sq ON q.id = sq.question_id
    INNER JOIN public.subjects s ON s.id = sq.subject_id
    INNER JOIN public.institutions_questions iq ON iq.question_id = q.id
    INNER JOIN public.institutions i ON i.id = iq.institution_id
    INNER JOIN public.areas_questions aq ON aq.question_id = q.id
    INNER JOIN public.areas a ON a.id = aq.area_id
WHERE
    s.id IN :subjects
    AND a.id IN :areas
    AND i.id IN :institutions
ORDER BY
    random() limit 200

期望的输出:

Question — Subject — Institution — Area

我的想法是这样的:

  1. 使用过滤器返回的问题创建 CTE;必须考虑到同一个问题可以由多个过滤器返回——我是否需要分开评估每个过滤器并使用 UNION ALL 来解决这个问题?还必须指定问题来自哪个过滤器;
  2. 创建另一个具有权重和相关过滤器的 CTE;
  3. 加入 CTE,但此时必须对问题进行分组,并对权重求和;
  4. 应用窗口函数并返回结果,限制为 X 行 (LIMIT X)。

你会如何编写这样的查询/解决这个问题?

【问题讨论】:

  • 样本数据和期望的结果真的很有帮助。如果您从现有样本中提取记录,则不一定能满足不同维度的此类约束。
  • 澄清一下,在上面的例子中,您希望 Subject = 1 的记录有 40% 的机会被选中,Subject = 2 10%, ...Institution = 3 76% 等。 .. 这些参数已经计算出来并存储在一个可以查询的表中?
  • @Error_2646 是的,但它们没有存储在任何表中(这就是我建议在步骤 2 中创建第二个 CTE 的原因)。就像我说的,它们是在查询之外动态设置的。
  • @GordonLinoff,我不明白你为什么需要样本数据。您可以认为示例查询中提到的所有表都只有一个自动递增的 ID 列。不会有任何区别。
  • @delta 如果解决方案查询过滤器表并忽略实现,您就可以进行修改,对吧?看起来这很容易解决。

标签: sql postgresql random probability window-functions


【解决方案1】:

这样的事情怎么样。这只是为了演示这个想法,我将把细节留给你。如果您不熟悉这种随机选择方法,如果您随机生成一个介于 0 和 1 之间的数字,则它有 40% 的机会低于 0.4。所以 rand()

假设您拥有或可以创建一个看起来有点像这样的“过滤器”实体

CREATE TABLE Filters
  ( FieldName VARCHAR(100), 
    FieldValue VARCHAR(100),
    Prob Float -- probability of selection based on Name and Value
  );

SELECT DISTINCT TMP.* -- The fields you want. Distinct needed to get rid of 
                      -- records which pass multiple conditions.
  FROM (SELECT YRSWF.*,
               RAND() AS rnd
          FROM YourResultSetWithoutFilters YRSWF -- You can code the details
       ) TMP  
 INNER
  JOIN Filters F
    ON (
       TMP.Subject = F.FieldValue
   AND F.FieldName = 'Subject'
   AND TMP.rnd <= F.prob
       )
    OR (
       TMP.Institution = F.FieldValue
   AND F.FieldName = 'Institution'
   AND TMP.rnd <= F.prob
       )
    OR ( 
       TMP.Area = F.FieldValue
   AND F.FieldName = 'Area'
   AND TMP.rnd <= F.prob
       );

【讨论】:

  • 考虑到子查询中没有应用过滤器,有可能得到主题1和区域7的问题。过滤器类型相同时为“或”,但两者之间为“与”每种类型(这在我的示例查询中说明)。这也可以通过在子查询中应用过滤器来解决。我的问题仅在限制行时才有意义。如果我想返回所有行,我不需要加权任何东西。当你考虑到这一点时,这个解决方案就行不通了。
  • 此解决方案可以返回可变数量的行,即使您还有满足过滤条件的问题。例如:您有主题 1 (0.5) 和主题 2 (0.5),各有 10 个问题;我想限制 15 行;根据您的查询,我不会得到 15 行。我想如果我说我想拿X个问题来做一个测试,那就更清楚了。我编辑了这个问题以反映这一点。 RAND() 应该是 RANDOM()。我想要 PostgreSQL SQL。
  • 对不起,我把 dbms 弄混了。但是你之前问过 Gordon 为什么样本数据很重要,这就是原因。等我回到电脑前,我会更新解决方案。
  • 在这种情况下,样本数据与它无关。就像我对他说的那样,您可以认为所有表都只有一个自动递增的 ID 列或 300 列;没有区别。
  • @delta 您可能想在此评论和其他评论中重新考虑您的语气。人们出于利他主义和对他们以前获得的帮助的补偿而花时间在这个网站上提供帮助。样本数据和预期结果使事情变得更容易。时期。 Gordon 是这个网站上最好的 SQL 人(也许是世界上最好的)。他不是为了好玩而要求提供示例数据。
【解决方案2】:

好的。设法解决它。基本上,使用了问题中已经概述的策略和here 的一点帮助——我之前已经看过这篇文章,但我(现在仍然)试图以更优雅的方式解决——比如@987654322 @ 但对于多行 - 不需要手动创建“边界”。

让我们一步一步地尝试:

由于带有权重的过滤器来自架构外部,让我们创建一个 CTE:

WITH filters (type, id, weight) AS (
    SELECT 'subject', '148232e0-dece-40d9-81e0-0fa675f040e5'::uuid, 0.5
    UNION SELECT 'subject', '854431bb-18ee-4efb-803f-185757d25235'::uuid, 0.4
    UNION SELECT 'area', 'e12863fb-afb7-45cf-9198-f9f58ebc80cf'::uuid, 1
    UNION SELECT 'institution', '7f56c89f-705e-45c7-98fb-fee470550edf'::uuid, 0.5
    UNION SELECT 'institution', '0066257b-b2e3-4ee8-8075-517a2aa1379e'::uuid, 0.5
)

现在,让我们过滤行,忽略权重(暂时),所以稍后我们不需要处理整个表:

WITH filtered_questions AS (
    SELECT
        q.id,
        s.id subject_id,
        a.id area_id,
        i.id institution_id
    FROM
        public.questions q
        INNER JOIN public.subjects_questions sq ON q.id = sq.question_id
        INNER JOIN public.subjects s ON s.id = sq.subject_id
        INNER JOIN public.institutions_questions iq ON iq.question_id = q.id
        INNER JOIN public.institutions i ON i.id = iq.institution_id
        INNER JOIN public.areas_questions aq ON aq.question_id = q.id
        INNER JOIN public.areas a ON a.id = aq.area_id
    WHERE
        subject_id IN (SELECT id from filters where type = 'subject')
        and institution_id IN (SELECT id from filters where type = 'institution')
        and area_id IN (SELECT id from filters where type = 'area')
)

同一个问题可以被多个过滤器选择,增加它被选中的机会。我们必须更新权重来解决这个问题。

WITH filtered_questions_weights_sum AS (
    SELECT
        q.id,
        SUM(filters.weight) weight_sum
    FROM filtered_questions q
    INNER JOIN filters
    ON (filters.type = 'subject' AND q.subject_id IN(filters.id))
    OR (filters.type = 'area' AND q.area_id IN(filters.id))
    OR (filters.type = 'institution' AND q.institution_id IN(filters.id))
    GROUP BY q.id
)

生成边界,例如暴露的here

WITH cumulative_prob AS (
    SELECT
        id,
        SUM(weight_sum) OVER (ORDER BY id) AS cum_prob
    FROM filtered_questions_weights_sum
),
cumulative_bounds AS (
    SELECT
        id,
        COALESCE( lag(cum_prob) OVER (ORDER BY cum_prob, id), 0 ) AS lower_cum_bound,
        cum_prob AS upper_cum_bound
    FROM cumulative_prob
)

生成随机序列。必须重新规范化(random() * (SELECT SUM(weight_sum)),因为权重在上一步中已更新。 10 是我们想要返回的行数。

WITH random_series AS (
    SELECT generate_series (1,10),random() * (SELECT SUM(weight_sum) FROM filtered_questions_weights_sum) AS R
)

最后:

SELECT
      id, lower_cum_bound, upper_cum_bound, R
FROM random_series
JOIN cumulative_bounds
ON R::NUMERIC <@ numrange(lower_cum_bound::NUMERIC, upper_cum_bound::NUMERIC, '(]')

我们得到以下分布:

id                                   lower_cum_bound upper_cum_bound r                   
------------------------------------ --------------- --------------- ------------------- 
380f46e9-f373-4b89-a863-05f484e6b3b6 0               2.0             0.41090718149207534 
42bcb088-fc19-4272-8c49-e77999edd01c 2.0             3.9             3.4483200465794654  
46a97f1d-789f-46e7-9d3b-bd881a22a32e 3.9             5.9             5.159445870062337   
46a97f1d-789f-46e7-9d3b-bd881a22a32e 3.9             5.9             5.524481557868421   
972d0296-acc3-4b44-b67d-928049d5e9c2 5.9             7.8             6.842470594821498   
bdcc26f7-ccaf-4f8f-9e0b-81b9a6d29cdb 11.6            13.5            12.207371663767844  
bdcc26f7-ccaf-4f8f-9e0b-81b9a6d29cdb 11.6            13.5            12.674184153741226  
c935e3de-f1b6-4399-b5eb-ed3a9194eb7b 15.5            17.5            17.16804686235264   
e5061aeb-53b7-4247-8404-87508c5ac723 21.4            23.4            22.622627633158118  
f8c37700-0c3a-457e-8882-7c65269482ea 25.4            27.3            26.841821723571048  

把它们放在一起:

WITH filters (type, id, weight) AS (
        SELECT 'subject', '148232e0-dece-40d9-81e0-0fa675f040e5'::uuid, 0.5
        UNION SELECT 'subject', '854431bb-18ee-4efb-803f-185757d25235'::uuid, 0.4
        UNION SELECT 'area', 'e12863fb-afb7-45cf-9198-f9f58ebc80cf'::uuid, 1
        UNION SELECT 'institution', '7f56c89f-705e-45c7-98fb-fee470550edf'::uuid, 0.5
        UNION SELECT 'institution', '0066257b-b2e3-4ee8-8075-517a2aa1379e'::uuid, 0.5
        )
    ,
    filtered_questions AS
    (
        SELECT
            q.id,
            SUM(filters.weight) weight_sum
        FROM
        public.questions q
        INNER JOIN public.subjects_questions sq ON q.id = sq.question_id
        INNER JOIN public.subjects s ON s.id = sq.subject_id
        INNER JOIN public.institutions_questions iq ON iq.question_id = q.id
        INNER JOIN public.institutions i ON i.id = iq.institution_id
        INNER JOIN public.activity_areas_questions aq ON aq.question_id = q.id
        INNER JOIN public.activity_areas a ON a.id = aq.activity_area_id
        INNER JOIN filters
            ON (filters.type = 'subject' AND s.id IN(filters.id))
            OR (filters.type = 'area' AND a.id IN(filters.id))
            OR (filters.type = 'institution' AND i.id IN(filters.id))
        WHERE
            s.id IN (SELECT id from filters where type = 'subject')
            and i.id IN (SELECT id from filters where type = 'institution')
            and a.id IN (SELECT id from filters where type = 'area')
        GROUP BY q.id
    )
    ,
    cumulative_prob AS (
        SELECT
            id,
            SUM(weight_sum) OVER (ORDER BY id) AS cum_prob
        FROM filtered_questions
    )
    ,
    cumulative_bounds AS (
        SELECT
            id,
            COALESCE( lag(cum_prob) OVER (ORDER BY cum_prob, id), 0 ) AS lower_cum_bound,
            cum_prob AS upper_cum_bound
        FROM cumulative_prob
    )
    ,
    random_series AS
    (
        SELECT generate_series (1,14),random() * (SELECT SUM(weight_sum) FROM filtered_questions) AS R
    )
SELECT id, lower_cum_bound, upper_cum_bound, R
FROM random_series
JOIN cumulative_bounds
ON R::NUMERIC <@ numrange(lower_cum_bound::NUMERIC, upper_cum_bound::NUMERIC, '(]')

【讨论】:

    猜你喜欢
    • 2021-10-18
    • 1970-01-01
    • 2012-10-13
    • 2013-01-06
    • 2016-03-15
    • 2010-09-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多