簡體   English   中英

在SQL / PostgreSQL中使用加權過濾器隨機選擇行

[英]Random row selection with weighted filters in SQL/PostgreSQL

我有一個問題表,需要准備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))。

您將如何編寫此類查詢/解決此問題?

那這樣的事呢 這只是為了演示這個想法,我將詳細信息留給您。 如果您不熟悉這種隨機選擇方法,則如果您隨機生成一個介於0和1之間的數字,則它有40%的可能性低於.4。 因此rand()<= .4將在40%的時間內返回true。

假設您擁有或可以創建一個看起來像這樣的“過濾器”實體

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
       );

好。 設法解決它。 基本上,使用問題中已經概述的策略以及從此處獲得的一些幫助-我以前已經看過這篇文章,但是我(並且現在)正試圖以一種更優雅的方式解決問題-類似這樣,但是對於多個行-無需手動創建“界限”。

讓我們逐步嘗試:

由於具有權重的過濾器來自架構外部,因此我們創建一個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
)

產生界限,就像暴露在這里一樣

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) ),因為權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, '(]')

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM