SQL,选择至少在N列上匹配的行对

时间:2017-05-19 08:06:10

标签: sql postgresql query-optimization

我自己在寻找SQL的一个不寻常的问题。

让我们说我有一个带有N列的表T1,为简单起见,名为A ... Z。

我需要的是找到至少在N个属性上匹配的所有行对。

让我们看一个非常简单的例子:

ID | A | B | C | D |
---+---+---+---+---+-
-1-| 1 | 2 | 3 | 4 |
---+---+---+---+---+-    
-2-| 2 | 3 | 4 | 1 |
---+---+---+---+---+-    
-3-| 1 | 2 | 2 | 1 |

在这种情况下,N = 2

  • 第1行应与第3行匹配(它们在A列和B列匹配)
  • 第2行没有匹配
  • 第3行与row1匹配,因为关系是对称的。

你对如何处理这个有什么想法吗?

2 个答案:

答案 0 :(得分:1)

我建议这样的事情。当然可以根据请求在案例列表中添加字段数:

select id1, id2, matches from
(
select
     tab1.id id1, tab2.id id2,
     (case when tab1.a=tab2.a then 1 else 0 end)+
     (case when tab2.b=tab2.b then 1 else 0 end) matches
from
           t1 tab1
cross join t1 tab2
)
where matches>1;

您将获得匹配行的ID以及匹配字段的数量。

答案 1 :(得分:1)

在表上使用自联接,您可以找到所有行对的匹配值的数量:

with my_table(id, a, b, c, d) as (
values
    (1, 1, 2, 3, 4),
    (2, 2, 3, 4, 1),
    (3, 1, 2, 2, 1)
)

select 
    t1.id, t2.id, 
    (t1.a = t2.a)::int+ (t1.b = t2.b)::int+ (t1.c = t2.c)::int+ (t1.d = t2.d)::int as matches
from my_table t1
join my_table t2 on t1.id < t2.id

 id | id | matches 
----+----+---------
  1 |  2 |       0
  1 |  3 |       2
  2 |  3 |       1
(3 rows)    

如果列数未知,您可以将表转换为获取数组而不是单列:

with my_table(id, a, b, c, d) as (
values
    (1, 1, 2, 3, 4),
    (2, 2, 3, 4, 1),
    (3, 1, 2, 2, 1)
),
my_table_transformed (id, cols) as (
    select id, array_agg(value::int)
    from my_table,
    to_jsonb(my_table) j,
    jsonb_each_text(j)
    where key <> 'id'
    group by 1
)
select *
from my_table_transformed t1

 id |   cols    
----+-----------
  1 | {1,2,3,4}
  2 | {2,3,4,1}
  3 | {1,2,2,1}
(3 rows)

现在你需要一个函数来在两个数组中获得许多匹配值:

create or replace function find_matches(a1 int[], a2 int[])
returns int language sql as $$
    select sum(m)::int
    from (
        select (c1 = c2)::int as m
        from unnest(a1, a2) u(c1, c2)
    ) s
$$;

并使用转换表的函数:

with my_table(id, a, b, c, d) as (
values
    (1, 1, 2, 3, 4),
    (2, 2, 3, 4, 1),
    (3, 1, 2, 2, 1)
),
my_table_transformed (id, cols) as (
    select id, array_agg(value::int)
    from my_table,
    to_jsonb(my_table) j,
    jsonb_each_text(j)
    where key <> 'id'
    group by 1
)
select t1.id, t2.id, find_matches(t1.cols, t2.cols)
from my_table_transformed t1
join my_table_transformed t2 on t1.id < t2.id;

 id | id | find_matches 
----+----+--------------
  1 |  2 |            0
  1 |  3 |            2
  2 |  3 |            1
(3 rows)    

最后一个查询适用于具有不同列数的表。