TSQL - 将两组类别连接在一起

时间:2017-09-27 19:02:36

标签: sql-server tsql grouping

我想根据两列将我的日期重组为群组。下面的数据显示了我的数据。我不能给你发一张漂亮的照片,因为我是新来的,但简历应该足够清楚了。

+-----+-----+--------+
| X1  | X2  | Result |
+-----+-----+--------+
|   4 |   1 | A      |
|   4 |   2 | A      |
|   1 |   3 | B      |
|   2 |   3 | B      |
|   3 |   4 | C      |
|   3 |   5 | C      |
|   1 |   6 | B      |
|   2 |   6 | B      |
+-----+-----+--------+

我有两列表示我的数据所属的类别。我想将它们重新组合成一个组,在这组中他们都属于一个组别或另一个组。我想找到一个有效的查询。

目前,我已经考虑过建立一个连接,将哪个元素链接到哪个元素:

SELECT * FROM [Table] T1
JOIN [Table] T2 ON T1.Gr1 = T2.Gr1 OR T1.Gr2 = T2.Gr2

有了这个,我可以构建一个以第一个元素开头的递归查询,如果它以某种方式链接到另一个组,则将另一个添加到另一个组中,如果没有,则将其添加到新的等级。有了大量的数据,它看起来并不优雅也没有效率。

任何人都有解决方案吗?

EDIT。我意识到我的问题不明确。每行有两个分类变量X1和X2。我想将所有共同观察中的所有观察结果分开。在例子中,B组保持所有具有X1 = 1或2或X2 = 3或6的观测值.C组保持观察结果x1 = 3且x2 = 4或5.我希望这使事情更清楚。

1 个答案:

答案 0 :(得分:0)

我想我明白你想要达到的目标。

为了澄清,我认为您想要动态创建新组,因为某种指针会通过X1 / X2对列表。如果已经看到X1 OR X2,请将其添加到现有组,否则将其添加到新组。

这对于数据库端计算来说是相当不寻常的,因为它不属于RDBMS设计的常规基于集合的理论。例如:如果一个项目匹配位于不同组中的前两个行,那么该项目会进入哪个组?

这可以用光标完成,但我讨厌游标,所以我不会去那里(至少还没有)。我认为有一种方法可以通过window functions实现这一目标。

这是一个相当粗略的例子,说明如何实现它 - 请记住,这种逻辑的细微差别可能不符合您的预期,正如我上面的重复行注释。这假设输入数据具有某种有序/唯一键,通过行继续并根据需要创建新组,如果找到重复匹配,则它只匹配第一个实例。

在大型桌子上这可能非常慢(...所以用户要小心并且所有这些东西):

declare @t table (
     i int identity, -- here's our ordered key
     X1 int,
     X2 int
);

-- Here's our test data (a few extra rows added on for good measure
insert into @t 
values (4, 1),(4, 2),(1, 3),(2, 3),(3, 4),(3, 5),(1, 6),
       (2, 6),(2, 1),(2, 2),(2, 5),(7, 7),(4, 1),(1, 2),
       (9, 9),(9, 1)

; with cte as ( 
-- keep track of the newest group number (by summing the "new group" flags):
select i, X1, X2, sum(newgrp) over (order by i) newestGroupNo 
    from (
        -- This subquery flags whenever there is a row that doesn't 
        -- match any previous row, and a new group should be created
        select X1, X2, i, 
            case when 
                (count(*) over (partition by X1 order by i)) = 1 and -- unique over X1
                (count(*) over (partition by X2 order by i)) = 1 -- unique over X2
            then 1 -- New group
            else 0 -- Existing group
            end newgrp
        from @t
    ) b
)
-- Now do the uniqueness check again, but either return the new group no, 
-- or the group no of the first group that matches prior to this row
select i, X1, X2, 
        case when 
            (count(*) over (partition by X1 order by i)) = 1 and -- unique over X1
            (count(*) over (partition by X2 order by i)) = 1 -- unique over X2
        then newestGroupNo -- New group, return the group number
        else (select top 1 newestGroupNo 
              from cte 
              where (cte.X1 = c.X1 or cte.X2 = c.X2) and cte.i <= c.i) -- Existing group
        end grp
from cte c order by i

结果:

i   X1  X2  grp
1   4   1   1     new group
2   4   2   1     match on X1 where i=1
3   1   3   2     no match
4   2   3   2     match on X2 where i=3
5   3   4   3     no match
6   3   5   3     match on X1 where i=5
7   1   6   2     match on X1 where i=3
8   2   6   2     match on X1 where i=4 (not i=7)
9   2   1   1     match on X2 where i=1 (not i=4 or i=8)
10  2   2   1     match on X2 where i=2 (... etc)
11  2   5   2     match on X1 where i=4
12  7   7   4     no match
13  4   1   1     match on X1 where i=1
14  1   2   1     match on X2 where i=2
15  9   9   5     no match
16  9   1   1     match on X2 where i=1

这是否符合您的期望?如果要处理大型表,请确保对此进行测试,在存储过程中几乎肯定有更快的方法可以实现此目的,但这会为您提供单个查询,如果需要,可以将其放入视图中。

欢迎对上述脚本进行改进。