SQL Server循环行以形成组

时间:2018-09-20 16:51:21

标签: sql sql-server database

我使用的是SQL Server 2008 R2 / 2014。我希望找到一个可以执行以下操作的SQL查询:

规则:

  1. 每个[Group]必须具有[Number] 1至6才能成为完整的组。
  2. 每个[Group]中的[Name]必须唯一。
  3. 每行只能使用1次。

排序前的表格是...

Name   Number    Group  
----   ------    -----
A        1  
B        6  
A      123  
C        3  
B        4  
C       23  
D       45  
D        4  
C       56  
A       12  
D       56  

排序后,我想要的结果低于或相似。...

Name  Number  Group  
----  ------  ----- 
A       1       1  
C      23       1  
D      45       1  
B       6       1  

A     123       2  
D       4       2  
C      56       2  

A      12       3  
C       3       3  
B       4       3  
D      56       3

我之前尝试过的是使用下面的连接方法找到具有[Number]由1-6组成的子组...

SELECT *
FROM [Table1] ST2
WHERE 
    SUBSTRING((SELECT ST1.[Number] AS [text()]
               FROM [Table1] ST1
               -- WHERE ST1.[Group] = ST2.[Group]
               ORDER BY LEFT(ST1.[Number],1)           
               FOR XML PATH ('')), 1, 1000) = '123456' 

2 个答案:

答案 0 :(得分:0)

也许您应该检查ROW_NUMBER函数。

select Name
     , Number
     , ROW_NUMBER () OVER(PARTITION BY Name ORDER BY Number) as Group 
from [Table1]

如果您具有6个以上具有相同NAME值的行,则它将返回更多组。您可以过滤掉其他组,因为您只对具有NAME列的唯一值的6个组感兴趣。

答案 1 :(得分:0)

我不确定是否可以更简单地完成此操作,但这是我的努力... 高级警告,这需要一些拆分字符串的方法。由于您不在2016年,因此我在脚本开头添加了一个函数。

大部分工作是递归CTE,它将Name和Number列构建为逗号分隔的组。然后,我们将工作集简化为仅数字可创建 123456 的组,将这些组拆分并使用ROW_NUMBER() OVER...进行标识,然后根据新数据进行选择。

演示:http://rextester.com/NEXG53500

CREATE FUNCTION [dbo].[SplitStrings]
(
   @List       NVARCHAR(MAX),
   @Delimiter  NVARCHAR(255)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
   RETURN 
   (  
      SELECT Item = y.i.value('(./text())[1]', 'nvarchar(4000)')
      FROM 
      ( 
        SELECT x = CONVERT(XML, '<i>' 
          + REPLACE(@List, @Delimiter, '</i><i>') 
          + '</i>').query('.')
      ) AS a CROSS APPLY x.nodes('i') AS y(i)
   );

GO


CREATE TABLE #temp
(
name VARCHAR(MAX),
number INT
)

INSERT INTO #temp
VALUES
('a',1),
('b',6),
('a',123),
('c',3),
('b',4),
('c',23),
('d',45),
('d',4),
('c',56),
('a',12),
('d',56);

/*** Recursively build groups based on information from #temp ***/
WITH groupFinder AS
(
    SELECT CAST(name AS VARCHAR(MAX)) AS [groupNames], CAST(number AS VARCHAR(max)) AS [groupNumbers] FROM #temp
    UNION ALL
    SELECT
        cast(CONCAT(t.[Name],',',g.[groupNames]) as VARCHAR(MAX)), 
        CAST(CONCAT(CAST(t.[Number] AS VARCHAR(max)),',',CAST(g.[groupNumbers] AS VARCHAR(max))) AS VARCHAR(max))
    FROM #temp t
        JOIN groupFinder g
    ON 
        g.groupNames NOT LIKE '%' + t.name+'%'
        AND g.[groupNumbers] NOT LIKE '%' + CAST(t.number/100 AS VARCHAR(10)) +'%'
        AND g.[groupNumbers] NOT LIKE '%' + CAST(t.number/10 AS VARCHAR(10)) +'%'
        AND g.[groupNumbers] NOT LIKE '%' + CAST(t.number%10 AS VARCHAR(10)) +'%'
)
/*** only get groups where the numbers form 123456 ***/
, groupPruner AS
( 
    SELECT *, ROW_NUMBER() OVER (ORDER BY [groupNames]) AS [rn] FROM groupFinder WHERE REPLACE([groupNumbers],',','') = '123456'
)
/*** split the name group and give it identifiers ***/
, nameIdentifier AS
(
    SELECT g.*, c1.[item] AS [Name], ROW_NUMBER() OVER (PARTITION BY [rn] ORDER BY (SELECT NULL)) AS [rn1]
    FROM groupPruner g
    CROSS APPLY splitstrings(g.groupnames,',') c1
)
/*** split the number group and give it identifiers ***/
, numberIdentifier AS
(
    SELECT g.*, c1.[item] AS [Number], ROW_NUMBER() OVER (PARTITION BY [rn], [rn1] ORDER BY (SELECT NULL)) AS [rn2]
    FROM nameIdentifier g
    CROSS APPLY splitstrings(g.groupNumbers,',') c1
)
SELECT [Name], [Number], [rn] AS [Group]
    --,groupnames, groupNumbers /*uncomment this line to see the groups that were built*/
FROM numberIdentifier
    WHERE rn1 = rn2 
ORDER BY rn, rn1


DROP TABLE #temp