【问题标题】:Creating permutation via recursive CTE in SQL server?在 SQL Server 中通过递归 CTE 创建排列?
【发布时间】:2015-07-22 06:36:16
【问题描述】:

看着:

;WITH cte AS(
    SELECT 1 AS x UNION
    SELECT 2 AS x UNION     
    SELECT 3 AS x   
)

我可以为所有 3 个值创建排列表:

SELECT T1.x , y=T2.x , z=t3.x
FROM cte T1
JOIN cte T2
ON T1.x != T2.x
JOIN cte T3
ON T2.x != T3.x AND T1.x != T3.x

这利用了 SQL 的笛卡尔积以及消除相等值的强大功能。

好的。

但是是否有可能增强这种递归伪 CTE:

;WITH cte AS(
    SELECT 1 AS x ,  2 AS y , 3 AS z   
    UNION ALL 
    ...
)

SELECT * FROM cte

这样它会产生与以下相同的结果:

注意 SO 中还有其他使用递归 CTE 的解决方案,但它不会传播到列,而是排列的字符串表示

【问题讨论】:

  • 只有一行数据?
  • @RaduGheorghiu 是的。这是种子。
  • 对于 3 个元素,它可以相对容易地完成,但它特定于 3 个元素。您是只需要 3 元素,还是正在寻找可扩展的解决方案?
  • @Damien_The_Unbeliever 是的。很抱歉没有这样做。我希望能够声明;WITH cte AS( SELECT 1 AS x , SELECT 2 AS y ,... SELECT n as N.. ) - 它是动态的。如果我提供容器列 - CTE 应该能够填充它们
  • 也许创建一个 CLR 函数来调用 wolframalpha.com/input/… 然后解析 HTML 输出?如果您想在递归 CTE 中使用它,请调用它两次并仔细检查您的结果:)

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


【解决方案1】:

我尝试在 CTE 中做很多事情。

但是,尝试动态“重新定义”行集有点棘手。虽然使用动态 SQL 执行此任务相对容易,但不会产生一些问题。

虽然这个答案可能不是最有效或最直接的,甚至在它不全是 CTE 的意义上是正确的,但它可能会为其他人提供工作的基础。

为了更好地理解我的方法,请阅读 cmets,但可能值得通过更改主块中下面的代码位来依次查看每个 CTE 表达式,并注释掉它下面的部分。

SELECT * FROM <CTE NAME>

祝你好运。

IF OBJECT_ID('tempdb..#cteSchema') IS NOT NULL
    DROP Table #cteSchema
GO

-- BASE CTE
;WITH cte AS( SELECT 1 AS x, 2 AS y, 3 AS z),

    -- So we know what columns we have from the CTE we extract it to XML
    Xml_Schema AS ( SELECT CONVERT(XML,(SELECT * FROM cte FOR XML PATH(''))) AS MySchema ),

    -- Next we need to get a list of the columns from the CTE, by querying the XML, getting the values and assigning a num to the column
    MyColumns AS (SELECT D.ROWS.value('fn:local-name(.)','SYSNAME') AS ColumnName,
                        D.ROWS.value('.','SYSNAME') as Value,
                        ROW_NUMBER() OVER (ORDER BY D.ROWS.value('fn:local-name(.)','SYSNAME')) AS Num
                    FROM Xml_Schema
                        CROSS APPLY Xml_Schema.MySchema.nodes('/*') AS D(ROWS) ),
    -- How many columns we have in the CTE, used a coupld of times below
    ColumnStats AS (SELECT MAX(NUM) AS ColumnCount FROM MyColumns),

    -- create a cartesian product of the column names and values, so now we get each column with it's possible values,
    -- so {x=1, x =2, x=3, y=1, y=2, y=3, z=1, z=2, z=3} -- you get the idea.
    PossibleValues AS (SELECT MyC.ColumnName, MyC.Num AS ColumnNum, MyColumns.Value, MyColumns.Num, 
                ROW_NUMBER() OVER (ORDER BY MyC.ColumnName, MyColumns.Value, MyColumns.Num ) AS ID
                FROM MyColumns 
                    CROSS APPLY MyColumns MyC
                ),

    -- Now we have the possibly values of each "column" we now have to concat the values together using this recursive CTE.
    AllRawXmlRows AS (SELECT CONVERT(VARCHAR(MAX),'<'+ISNULL((SELECT ColumnName FROM MyColumns WHERE MyColumns.Num = 1),'')+'>'+Value) as ConcatedValue, Value,ID, Counterer = 1  FROM PossibleValues
                UNION ALL
            SELECT CONVERT(VARCHAR(MAX),CONVERT(VARCHAR(MAX), AllRawXmlRows.ConcatedValue)+'</'+(SELECT ColumnName FROM MyColumns WHERE MyColumns.Num = Counterer)+'><'+(SELECT ColumnName FROM MyColumns WHERE MyColumns.Num = Counterer+1)+'>'+CONVERT(VARCHAR(MAX),PossibleValues.Value))  AS ConcatedValue, PossibleValues.Value, PossibleValues.ID,
            Counterer = Counterer+1
            FROM AllRawXmlRows
                INNER JOIN PossibleValues  ON AllRawXmlRows.ConcatedValue NOT LIKE '%'+PossibleValues.Value+'%' -- I hate this, there has to be a better way of making sure we don't duplicate values.... 
                AND AllRawXmlRows.ID <> PossibleValues.ID
                AND Counterer < (SELECT ColumnStats.ColumnCount FROM ColumnStats)
                ),

    -- The above made a list but was missing the final closing XML element. so we add it.
    -- we also restict the list to the items that contain all columns, the section above builds it up over many columns
    XmlRows AS (SELECT DISTINCT 
                ConcatedValue +'</'+(SELECT ColumnName FROM MyColumns WHERE MyColumns.Num = Counterer)+'>' 
                AS ConcatedValue
            FROM AllRawXmlRows WHERE Counterer = (SELECT ColumnStats.ColumnCount FROM ColumnStats)
                    ),              
    -- Wrap the output in row and table tags to create the final XML
    FinalXML AS (SELECT (SELECT CONVERT(XML,(SELECT CONVERT(XML,ConcatedValue) FROM XmlRows FOR XML PATH('row'))) FOR XML PATH('table') )as XMLData),

    -- Prepare a CTE that represents the structure of the original CTE with 
    DataTable AS (SELECT cte.*, XmlData
                        FROM FinalXML, cte)
--SELECT * FROM <CTE NAME>
    -- GETS destination columns with XML data.
SELECT * 
    INTO #cteSchema
FROM DataTable


DECLARE @XML VARCHAR(MAX) ='';
SELECT @Xml = XMLData FROM #cteSchema --Extract XML Data from the

ALTER TABLE #cteSchema DROP Column XMLData -- Removes the superflous column
DECLARE @h INT
EXECUTE sp_xml_preparedocument @h OUTPUT, @XML
    SELECT * 
        FROM OPENXML(@h, '/table/row', 2) 
            WITH #cteSchema -- just use the #cteSchema to define the structure of the xml that has been constructed

EXECUTE sp_xml_removedocument @h

【讨论】:

  • 你得到了我的投票,但五列需要 2:40 分钟。
  • 是的,它不是很有效,但也不是解决问题的正确方法。动态 SQL 将是一种更简单有效的方法。不过感谢您的投票。 :)
【解决方案2】:

如何将 1,2,3 翻译成一列,看起来与您开始的示例完全相同,并使用相同的方法?

;WITH origin (x,y,z) AS (
    SELECT 1,2,3
), translated (x) AS ( 
    SELECT  col
    FROM    origin
            UNPIVOT ( col FOR cols IN (x,y,z)) AS up
)
SELECT T1.x , y=T2.x , z=t3.x
FROM    translated T1
        JOIN translated T2
            ON T1.x != T2.x
        JOIN translated T3
            ON T2.x != T3.x AND T1.x != T3.x
ORDER BY 1,2,3

如果我正确理解了请求,这可能就可以解决问题。

要在更多列上运行它,只需将它们添加到原始 cte 定义 + 取消透视列列表。

现在,我不知道你如何传递你的 1 - n 值使其成为动态的,但如果你告诉我,我也可以尝试将脚本编辑为动态的。

【讨论】:

    猜你喜欢
    • 2020-06-08
    • 1970-01-01
    • 2012-04-23
    • 2014-05-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-25
    相关资源
    最近更新 更多