限制 2 元组中元素的出现次数

Posted

技术标签:

【中文标题】限制 2 元组中元素的出现次数【英文标题】:limiting the number of occurrences of elements in 2-tuples 【发布时间】:2016-05-05 13:15:03 【问题描述】:

我正在尝试在 SQL(或 postgresql 9.4)中为以下问题找到一个基于集合的查询解决方案:

我有一组有限的唯一 2 元组 (x ∈ N, y ∈ N),它们分配了一个等级。

现在我想删除所有元组,使剩余的元组满足以下条件:

    每个数字在元组左侧最多出现 n 次,并且 每个数字在右侧最多出现 m 次。

使用迭代有序元组并计算每个元素出现次数的过程很容易做到这一点。但是,我想知道是否有使用单个(postgre)SQL 查询的解决方案?

为了更具体,请考虑以下 n=2,m=2 的简单示例:

╔═══╦═══╦══════╗
║ x ║ y ║ rank ║
╠═══╬═══╬══════╣
║ 1 ║ 4 ║    1 ║
║ 2 ║ 4 ║    2 ║
║ 3 ║ 4 ║    3 ║
║ 3 ║ 5 ║    4 ║
║ 3 ║ 6 ║    5 ║
║ 3 ║ 7 ║    6 ║
╚═══╩═══╩══════╝

现在我们正在寻找一个返回以下元组作为结果的查询:(1,4), (2,4), (3,5), (3,6)

表和值的 SQL 小提琴:

   create table tab (
     x bigint,
     y bigint,
     rank bigint);

  insert into tab values (1,4,1);
  insert into tab values (2,4,2);
  insert into tab values (3,4,3);
  insert into tab values (3,5,4);
  insert into tab values (3,6,5);
  insert into tab values (3,7,6);

我尝试了一种使用 postgres 窗口函数的方法,它解决了上面的示例,但我不确定它是否可以找到与其他示例中基于光标的方法一样多的对。

    SELECT x, y FROM (
      SELECT x, y, ROW_NUMBER() OVER (PARTITION BY x ORDER BY rank) AS rx FROM (
        SELECT x, y, rank, ROW_NUMBER() OVER (PARTITION BY y ORDER BY rank) AS ry FROM tab) AS limitY
      WHERE limitY.ry < 3) AS limitX
    WHERE limitX.rx < 3

【问题讨论】:

当您说“声明性”时,您是指基于单个集合的查询吗? SQL 上下文中的“声明性”通常意味着声明性 RI,这将是一个非常不同的东西。 是的,我的意思是一个基于集合的查询。我正在寻找一种遵循声明式编程方法的解决方案,而不是使用存储过程的命令式解决方案。 x 或 y 中是否有元素乱序(按等级排序时)?如果不是,是否可以相信排名是完全连续的? 您可以假设 x 和 y 总是按等级升序排序(它们的值是为了保持示例简单,这里只是巧合),但是我在这个问题上做了更多的工作并且需要在初始 2 中添加另一个条件(最大化返回的对数量),这会显着改变范围。我应该只编辑我的原始帖子还是创建一个后续问题? 【参考方案1】:

这是使用单个窗口函数传递的变体(可能更快):

select x, y, rank
from (
  select *, count(*) over (partition by x order by rank) as cx,
            count(*) over (partition by y order by rank) as cy
  from tab
  order by rank
  ) t
where cx < 3 and cy < 3;

还有递归 CTE 方法:

-- use tab directly instead of tabr CTE (and replace all ocurrences of r column with rank)
-- if rank is trusted to be sequential uninterrupted starting with 1
with recursive
  r (r, x, y, rank, cx, cy) as (
    select *, 1 as cx, 1 as cy
    from tabr where r = 1
    union all
    select t.*, case when r.x = t.x then r.cx + 1 else 1 end as cx, case when r.y = t.y then r.cy + 1 else 1 end as cy
    from r, tabr t
    where t.r = r.r + 1
    ),
  tabr as (
    select row_number() over () as r, *
    from tab
    order by rank
    )
select x, y, rank
from r
where cx <= 2 and cy <= 2
order by r;

【讨论】:

您的第一个解决方案忽略了 (3, 6) 将有效的事实,因为 (3,4) 由于 y 已经被排除在外,因此 x=3 的计数发生了变化。不知道第二个解决方案有没有同样的错误。【参考方案2】:

这需要一段时间,但我能够在 MS SQL Server 中提出一个解决方案,我认为应该将其转换为 PostGreSQL。 SQL Server 对递归 CTE 中的内容有一些限制,我不完全知道 PostGreSQL 有什么限制。也就是说,希望这对您有用或为您指明正确的方向。

棘手的部分是被排除的行会根据已经被排除的行发生变化,因此不能简单地计算它们,因为它们同时依赖于 x y,递归 CTE 不能'不只是按顺序构建,因为它只能引用自己一次。那时我想出了将计数嵌入字符串的想法。这根本不能很好地扩展 - 例如,如果在排除一行之前规则更改为 3 或 4 个实例,则 CASE 语句开始爆炸。

WITH CTE_Excludes AS
(
    SELECT
        x,
        y,
        [rank],
        CAST('|' + CAST(x AS VARCHAR(4)) + '-1|' AS VARCHAR(1000)) AS x_counts,
        CAST('|' + CAST(y AS VARCHAR(4)) + '-1|' AS VARCHAR(1000)) AS y_counts,
        0 AS excluded
    FROM
        tab
    WHERE
        [rank] = 1
    UNION ALL
    SELECT
        T.x,
        T.y,
        T.[rank],
        CAST(CASE
            WHEN X.x_counts LIKE '%|' + CAST(T.x AS VARCHAR(4)) + '-2|%' OR X.y_counts LIKE '%|' + CAST(T.y AS VARCHAR(4)) + '-2|%' THEN X.x_counts
            WHEN X.x_counts LIKE '%|' + CAST(T.x AS VARCHAR(4)) + '-1|%' THEN REPLACE(X.x_counts, '|' + CAST(T.x AS VARCHAR(4)) + '-1|', '|' + CAST(T.x AS VARCHAR(4)) + '-2|')
            ELSE X.x_counts + '|' + CAST(T.x AS VARCHAR(4)) + '-1|'
        END AS VARCHAR(1000)) AS x_counts,
        CAST(CASE
            WHEN X.x_counts LIKE '%|' + CAST(T.x AS VARCHAR(4)) + '-2|%' OR X.y_counts LIKE '%|' + CAST(T.y AS VARCHAR(4)) + '-2|%' THEN X.y_counts
            WHEN X.y_counts LIKE '%|' + CAST(T.y AS VARCHAR(4)) + '-1|%' THEN REPLACE(X.y_counts, '|' + CAST(T.y AS VARCHAR(4)) + '-1|', '|' + CAST(T.y AS VARCHAR(4)) + '-2|')
            ELSE X.y_counts + '|' + CAST(T.y AS VARCHAR(4)) + '-1|'
        END AS VARCHAR(1000)) AS y_counts,
        CASE
            WHEN X.x_counts LIKE '%|' + CAST(T.x AS VARCHAR(4)) + '-2|%' OR X.y_counts LIKE '%|' + CAST(T.y AS VARCHAR(4)) + '-2|%' THEN 1
            ELSE 0
        END AS excluded
    FROM
        CTE_Excludes X
    INNER JOIN tab T ON T.[rank] = X.[rank] + 1
)
SELECT
    x, y
FROM
    CTE_Excludes
WHERE
    excluded = 0

【讨论】:

以上是关于限制 2 元组中元素的出现次数的主要内容,如果未能解决你的问题,请参考以下文章

Python基础--元组(tuple)

Python中的元组(Tuple)

Python基础-04元组

Python基础-04元组

元组常用操作

关于Python中元组的问题