T-SQL“动态”加入

时间:2013-09-17 19:55:22

标签: sql sql-server tsql permutation common-table-expression

给定以下带有单个char(1)列的SQL Server表:

Value
------
'1'
'2'
'3'

如何在T-SQL中获得以下结果?

Result
------
'1+2+3'
'1+3+2'
'2+1+3'
'2+3+1'
'3+2+1'
'3+1+2'

这也需要是动态的,所以如果我的表只保留行'1'和'2',我期望:

Result
------
'1+2'
'2+1'

似乎我应该能够使用CROSS JOIN来做到这一点,但由于我不知道会提前有多少行,所以我不确定CROSS JOIN会多少次回到我自己身上。 ?

SELECT a.Value + '+' + b.Value
FROM MyTable a
CROSS JOIN MyTable b
WHERE a.Value <> b.Value

在任何给定时间总会有少于10行(实际上更像是1-3行)。我可以在SQL Server中即时执行此操作吗?

编辑:理想情况下,我希望在单个存储过程中发生这种情况,但如果我必须使用另一个proc或一些用户定义的函数来解决这个问题,我就可以了。

2 个答案:

答案 0 :(得分:12)

此SQL将计算排列而不重复:

WITH recurse(Result, Depth) AS
(
    SELECT CAST(Value AS VarChar(100)), 1
    FROM MyTable

    UNION ALL

    SELECT CAST(r.Result + '+' + a.Value AS VarChar(100)), r.Depth + 1
    FROM MyTable a
    INNER JOIN recurse r
    ON CHARINDEX(a.Value, r.Result) = 0
)

SELECT Result
FROM recurse
WHERE Depth = (SELECT COUNT(*) FROM MyTable)
ORDER BY Result

如果MyTable包含9行,则需要一些时间来计算,但它会返回362,880行。

更新说明:

WITH语句用于定义recursive common table expression。实际上,WITH语句多次循环执行UNION,直到递归完成。

SQL的第一部分设置起始记录。假设在MyTable中有3行名为“A”,“B”和“C”,这将生成以下行:

    Result     Depth
    ------     -----
    A          1
    B          1
    C          1

然后下一个SQL块执行第一级递归:

    SELECT CAST(r.Result + '+' + a.Value AS VarChar(100)), r.Depth + 1
    FROM MyTable a
    INNER JOIN recurse r
    ON CHARINDEX(a.Value, r.Result) = 0

这将获取到目前为止生成的所有记录(将在recurse表中)并再次将它们连接到MyTable中的所有记录。 ON子句过滤MyTable中的记录列表,仅返回此行排列中不存在的记录。这将导致这些行:

    Result     Depth
    ------     -----
    A          1
    B          1
    C          1
    A+B        2
    A+C        2
    B+A        2
    B+C        2
    C+A        2
    C+B        2

然后递归循环再次给出这些行:

    Result     Depth
    ------     -----
    A          1
    B          1
    C          1
    A+B        2
    A+C        2
    B+A        2
    B+C        2
    C+A        2
    C+B        2
    A+B+C      3
    A+C+B      3
    B+A+C      3
    B+C+A      3
    C+A+B      3
    C+B+A      3

此时,递归停止,因为UNION不再创建任何行,因为CHARINDEX始终为0

最后一个SQL过滤所有生成的行,其中计算的Depth列与MyTable中的记录数匹配。抛出除最后一次递归深度生成的行之外的所有行。所以最终结果将是这些行:

    Result
    ------
    A+B+C
    A+C+B
    B+A+C
    B+C+A
    C+A+B
    C+B+A

答案 1 :(得分:2)

您可以使用递归CTE执行此操作:

with t as (
      select 'a' as value union all
      select 'b' union all
      select 'c'
     ),
     const as (select count(*) as cnt from t),
     cte as (
      select cast(value as varchar(max)) as value, 1 as level
      from t
      union all
      select cte.value + '+' + t.value, 1 + level
      from cte join
           t 
           on '+'+cte.value+'+' not like '%+'+t.value+'+%' cross join
           const
      where level <= const.cnt
     )
select cte.value
from cte cross join
     const
where level = const.cnt;