是否可以使用CTE将列值连接成字符串?

时间:2010-06-30 16:36:43

标签: sql sql-server sql-server-2008 common-table-expression

说我有下表:

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

我已经玩过一些但却无法让它发挥作用。我会用更好的技术做得更好吗?

3 个答案:

答案 0 :(得分:6)

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

表:

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

答案 1 :(得分:2)

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

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

答案 2 :(得分: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,.... 的名称.id 对于 myId = 1会有1,2 ...但后续的 myId 值会有更高的起始值。

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

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

是的,我将子查询别名为 tmp ,这与您可以得到的一样通用。大多数SQL引擎都要求您为子查询提供别名,即使它是此时唯一可见的关系。