5

假设我有下表:

id|myId|Name
-------------
1 | 3  |Bob 
2 | 3  |Chet
3 | 3  |Dave
4 | 4  |Jim
5 | 4  |Jose
-------------

是否可以使用递归 CTE 生成以下输出:

3 | Bob, Chet, Date
4 | Jim, Jose

我已经玩了一点,但无法让它工作。使用不同的技术我会做得更好吗?

4

3 回答 3

7

我不推荐这个,但我设法解决了。

桌子:

CREATE TABLE [dbo].[names](
    [id] [int] NULL,
    [myId] [int] NULL,
    [name] [char](25) NULL
) ON [PRIMARY]

数据:

INSERT INTO names values (1,3,'Bob')
INSERT INTO names values 2,3,'Chet')
INSERT INTO names values 3,3,'Dave')
INSERT INTO names values 4,4,'Jim')
INSERT INTO names values 5,4,'Jose')
INSERT INTO names values 6,5,'Nick')

询问:

WITH CTE (id, myId, Name, NameCount)
     AS (SELECT id,
                myId,
                Cast(Name AS VARCHAR(225)) Name,
                1                          NameCount
         FROM   (SELECT Row_number() OVER (PARTITION BY myId ORDER BY myId) AS id,
                        myId,
                        Name
                 FROM   names) e
         WHERE  id = 1
         UNION ALL
         SELECT e1.id,
                e1.myId,
                Cast(Rtrim(CTE.Name) + ',' + e1.Name AS VARCHAR(225)) AS Name,
                CTE.NameCount + 1                                     NameCount
         FROM   CTE
                INNER JOIN (SELECT Row_number() OVER (PARTITION BY myId ORDER BY myId) AS id,
                                   myId,
                                   Name
                            FROM   names) e1
                  ON e1.id = CTE.id + 1
                     AND e1.myId = CTE.myId)
SELECT myID,
       Name
FROM   (SELECT myID,
               Name,
               (Row_number() OVER (PARTITION BY myId ORDER BY namecount DESC)) AS id
        FROM   CTE) AS p
WHERE  id = 1 

根据要求,这是 XML 方法:

SELECT myId,
       STUFF((SELECT ',' + rtrim(convert(char(50),Name))
        FROM   namestable b
        WHERE  a.myId = b.myId
        FOR XML PATH('')),1,1,'') Names
FROM   namestable a
GROUP BY myId
于 2010-06-30T16:46:53.430 回答
2

CTE 只是具有一些额外功能(如递归)的美化派生表。问题是,你可以使用递归来做到这一点吗?可能,但它是用螺丝刀敲钉子。执行 XML 路径的好处(见第一个答案)是将 MyId 列与字符串连接组合在一起。

您将如何使用 CTE 连接字符串列表?我不认为这是它的目的。

于 2010-06-30T17:33:12.103 回答
1

CTE 只是一个临时创建的关系(表和视图都是关系),仅在当前查询的“生命周期”中存在。

我玩过 CTE 名称和字段名称。我真的不喜欢在多个地方重用像id这样的字段名称;我倾向于认为这些会让人感到困惑。由于names.id的唯一用途是作为第一个 ROW_NUMBER() 语句中的 ORDER BY,因此我不会再重复使用它。

WITH namesNumbered as (
    select myId, Name,
        ROW_NUMBER() OVER (
            PARTITION BY myId 
            ORDER BY id
        ) as nameNum
    FROM names
)
, namesJoined(myId, Name, nameCount) as (
    SELECT myId,
        Cast(Name AS VARCHAR(225)),
        1
    FROM namesNumbered nn1
    WHERE nameNum = 1
    UNION ALL
    SELECT nn2.myId,
        Cast(
            Rtrim(nc.Name) + ',' + nn2.Name
            AS VARCHAR(225)
        ),
        nn.nameNum
    FROM namesJoined nj
    INNER JOIN namesNumbered nn2 ON nn2.myId = nj.myId
        and nn2.nameNum = nj.nameCount + 1
)
SELECT myId, Name
FROM (
    SELECT myID, Name,
        ROW_NUMBER() OVER (
            PARTITION BY myId
            ORDER BY nameCount DESC
        ) AS finalSort
    FROM namesJoined
) AS tmp
WHERE finalSort = 1

第一个 CTE,namesNumbered,返回我们关心的两个字段和一个排序值;我们不能只为此使用names.id,因为对于每个myId值,我们需要具有 1、2、...的值。对于myId = 1 , names.id将具有 1、2 ...对于后续的myId值具有更高的起始值。

第二个 CTE namesJoined必须在 CTE 签名中指定字段名称,因为它是递归的。基本情况(UNION ALL 之前的部分)为我们提供了nameNum = 1 的记录。我们必须 CAST() Name字段,因为它会随着后续传递而增长;我们需要确保我们 CAST() 它足够大以处理任何输出;如果需要,我们以后可以随时 TRIM() 它。我们不必为字段指定别名,因为 CTE 签名提供了这些别名。递归情况(在 UNION ALL 之后)将当前 CTE 与前一个 CTE 连接起来,确保后续传递使用更高的nameNum值。我们需要 TRIM() 之前的Name迭代,然后添加逗号和新的名称。结果将隐式地 CAST()ed 到更大的字段。

最终查询仅获取我们关心的字段(myIdName),并且在子查询中,有针对性地重新排序记录,以便最高的namesJoined.nameCount值将获得 1 作为finalSort值。然后,我们告诉 WHERE 子句只给我们这一条记录(对于每个myId值)。

是的,我将子查询别名为tmp,这与您所能获得的一样通用。大多数 SQL 引擎都要求您给子查询一个别名,即使它是当时唯一可见的关系。

于 2017-07-12T15:57:30.107 回答