从表中有效地选择最具体的结果

时间:2013-07-31 14:40:11

标签: postgresql

我的表大致如下:

CREATE TABLE t_table (
    f_userid       BIGINT NOT NULL
   ,f_groupaid     BIGINT
   ,f_groupbid     BIGINT
   ,f_groupcid     BIGINT
   ,f_itemid       BIGINT
   ,f_value        TEXT
);

这些组是正交的,因此除了表中的每个条目都具有用户ID之外,不能隐含任何层次结构。任何一栏都没有唯一性。

例如,简单的设置可能是:

INSERT INTO t_table VALUES (1, NULL, NULL, NULL, NULL, 'Value for anything by user 1');
INSERT INTO t_table VALUES (1,    5,    2, NULL, NULL, 'Value for anything by user 1 in groupA 5 groupB 2');
INSERT INTO t_table VALUES (1,    4, NULL,    1, NULL, 'Value for anything by user 1  in groupA 5 and groupC 1');
INSERT INTO t_table VALUES (2, NULL, NULL, NULL, NULL, 'Value for anything by user 2');
INSERT INTO t_table VALUES (2,    1, NULL, NULL, NULL, 'Value for anything by user 2 in groupA 1');
INSERT INTO t_table VALUES (2,    1,    3,    4,    5, 'Value for item 5 by user 2 in groupA 1 and groupB 3 and groupC 4');

对于任何给定的user / groupA / groupB / groupC / item集合,我希望能够获取适用的表中最具体的项目。如果任何给定集合为NULL,则它只能匹配表中包含NULL的相关列。例如:

// Exact match
SELECT MostSpecific(1, NULL, NULL, NULL, NULL) => "Value for anything by user 1" 
// Match the second entry because groupC and item were not specified in the table and the other items matched
SELECT MostSpecific(1, 5, 2, 3, NULL) => "Value for anything by user 1 in groupA 5 groupB 2"
// Does not match the second entry because groupA is NULL in the query and set in the table
SELECT MostSpecific(1, NULL, 2, 3, 4) => "Value for anything by user 1"

这里显而易见的方法是让存储过程处理参数并找出哪些是NULL而不是,然后调用相应的SELECT语句。但这似乎效率很低。有更好的方法吗?

3 个答案:

答案 0 :(得分:1)

尝试类似:

select *
from t_table t

where f_userid = $p_userid
  and (t.f_groupaid is not distinct from $p_groupaid or t.f_groupaid is null) --null in f_groupaid matches both null and not null values
  and (t.f_groupbid is not distinct from $p_groupbid or t.f_groupbid is null)
  and (t.f_groupcid is not distinct from $p_groupcid or t.f_groupcid is null)

order by (t.f_groupaid is not distinct from $p_groupaid)::int -- order by count of matches
        +(t.f_groupbid is not distinct from $p_groupbid)::int
        +(t.f_groupcid is not distinct from $p_groupcid)::int desc
limit 1;

它将为您提供最佳的群组匹配。

如果A和B相等或同时A is not distinct from B,则

true填写返回null

::int表示cast ( as int)。将布尔值true转换为int将给出1(不能直接添加布尔值)。

答案 1 :(得分:1)

这应该这样做,只需使用WHERE过滤掉任何不匹配的行,然后根据它们的匹配程度对剩余的行进行排名。如果任何列不匹配,则整个bop表达式将导致NULL,因此我们在外部查询中对其进行过滤,我们也按匹配顺序将结果限制为仅匹配单个最佳匹配。

CREATE FUNCTION MostSpecific(BIGINT, BIGINT, BIGINT, BIGINT, BIGINT) 
RETURNS TABLE(f_userid BIGINT, f_groupaid BIGINT, f_groupbid BIGINT, f_groupcid BIGINT, f_itemid BIGINT, f_value TEXT) AS
'WITH cte AS (
  SELECT *, 
    CASE WHEN f_groupaid IS NULL THEN 0 WHEN f_groupaid = $2 THEN 1 END +
    CASE WHEN f_groupbid IS NULL THEN 0 WHEN f_groupbid = $3 THEN 1 END +
    CASE WHEN f_groupcid IS NULL THEN 0 WHEN f_groupcid = $4 THEN 1 END +
    CASE WHEN f_itemid   IS NULL THEN 0 WHEN f_itemid   = $5 THEN 1 END bop 
  FROM t_table
  WHERE f_userid = $1
    AND (f_groupaid IS NULL OR f_groupaid = $2)
    AND (f_groupbid IS NULL OR f_groupbid = $3)
    AND (f_groupcid IS NULL OR f_groupcid = $4)
    AND (f_itemid IS NULL   OR f_itemid   = $5)
)
SELECT f_userid, f_groupaid, f_groupbid, f_groupcid, f_itemid, f_value FROM cte
WHERE bop IS NOT NULL
ORDER BY bop DESC
LIMIT 1'
LANGUAGE SQL
//

An SQLfiddle to test with

答案 2 :(得分:1)

SQL Fiddle

create or replace function mostSpecific(
    p_userid bigint,
    p_groupaid bigint,
    p_groupbid bigint,
    p_groupcid bigint,
    p_itemid bigint
) returns t_table as $body$

select *
from t_table
order by
    (p_userid is not distinct from f_userid or f_userid is null)::integer
    +
    (p_groupaid is not distinct from f_groupaid or f_userid is null)::integer
    +
    (p_groupbid is not distinct from f_groupbid or f_userid is null)::integer
    +
    (p_groupcid is not distinct from f_groupcid or f_userid is null)::integer
    +
    (p_itemid is not distinct from f_itemid or f_userid is null)::integer
    desc
limit 1
;
$body$ language sql;