9

Patrząc na:Tworzenie permutacji za pomocą rekurencyjnego CTE na serwerze SQL?

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

mogę utworzyć tabelę permutacji dla wszystkich wartości 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 

ten wykorzystuje moc kartezjańskiej produktu SQL w powiększonej eliminując równe wartości.

http://i.imgur.com/uJUPtVH.png

OK.

Ale czy to możliwe, aby wzbogacić ten rekurencyjną pseudo CTE:

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

SELECT * FROM cte 

enter image description here

tak, że będzie uzyskując ten sam wynik:

enter image description here

NB istnieją inne rozwiązania w języku SO, które używają rekurencyjnego CTE, ale nie jest on rozsyłany do kolumn, ale repr esentation permutations

+0

Tylko z jednym rzędem danych? –

+0

@RaduGheorghiu Tak. to jest nasienie. –

+0

Dla 3 elementów można to zrobić stosunkowo łatwo, ale jest to specyficzne dla 3 elementów. Czy potrzebujesz tylko 3 element jeden lub szukasz rozwiązania, które skaluje się? –

Odpowiedz

2

Próbowałem zrobić dużo w CTE.

Jednak próba "ponownego zdefiniowania" zestawu zestaw wierszy jest nieco trudna. Podczas gdy zadanie jest stosunkowo łatwe przy użyciu dynamicznego SQL, który to robi, nie stwarza problemów.

Chociaż ta odpowiedź może nie być najbardziej wydajna lub prosta, lub nawet poprawna w tym sensie, że nie jest to tylko CTE, może dać innym podstawę do pracy.

Aby lepiej zrozumieć moje podejście, przeczytaj komentarze, ale warto byłoby przyjrzeć się każdemu z wyrażeń CTE po kolei, zmieniając bit kodu poniżej w głównym bloku, z komentarzem poniżej sekcji.

SELECT * FROM <CTE NAME> 

Powodzenia.

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 
+0

Masz mój głos, ale dla pięciu kolumn zajęło to 2:40 min. –

+0

Tak, to nie jest zbyt wydajne, ale nie jest to również właściwe podejście do problemu. Dynamiczny SQL byłby znacznie prostszą i wydajniejszą metodą. Dzięki za głosowanie. :) –

0

Co powiecie na przetłumaczenie 1,2,3 na kolumnę, która będzie wyglądać dokładnie tak, jak na przykładzie, z którego zacząłeś, i używasz tego samego podejścia?

;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 

Jeśli dobrze zrozumiałem żądanie, może to wystarczy.

Aby uruchomić go w większej ilości kolumn, wystarczy dodać do nich definicję początku cet + listę rozwijaną kolumny.

Teraz nie wiem, w jaki sposób przekazać swoje 1 - n wartości, aby był dynamiczny, ale jeśli mi powiesz, mógłbym spróbować edytować skrypt również jako dynamiczny.