web-dev-qa-db-ja.com

SQLサーバーで順列を生成する最も洗練された方法

次の表があるとします。

Index | Element
---------------
  1   |    A
  2   |    B
  3   |    C
  4   |    D

要素を使用して、可能なすべての順列(繰り返しなし)を生成する必要があります。最終結果(一部の行をスキップ)は次のようになります。

  Results
----------
   ABCD
   ABDC
   ACBD
   ACDB
   ADAC
   ADCA

   ...

   DABC
   DACB
   DBCA
   DBAC
   DCAB
   DCBA

  (24 Rows)

どうしますか?

18
SDReyes

恐らく卑劣なコメントをした後、この問題は一晩中私の頭に残っていました、そして私は最終的に次のセットベースのアプローチを思いつきました。間違いなく「エレガント」と言えると思いますが、それなら「ちょっとばかげている」とも思います。あなたは電話をかけます。

まず、いくつかのテーブルを設定します。

--  For testing purposes
DROP TABLE Source
DROP TABLE Numbers
DROP TABLE Results


--  Add as many rows as need be processed--though note that you get N! (number of rows, factorial) results,
--  and that gets big fast. The Identity column must start at 1, or the algorithm will have to be adjusted.
--  Element could be more than char(1), though the algorithm would have to be adjusted again, and each element
--  must be the same length.
CREATE TABLE Source
 (
   SourceId  int      not null  identity(1,1)
  ,Element   char(1)  not null
 )

INSERT Source (Element) values ('A')
INSERT Source (Element) values ('B')
INSERT Source (Element) values ('C')
INSERT Source (Element) values ('D')
--INSERT Source (Element) values ('E')
--INSERT Source (Element) values ('F')


--  This is a standard Tally table (or "table of numbers")
--  It only needs to be as long as there are elements in table Source
CREATE TABLE Numbers (Number int not null)
INSERT Numbers (Number) values (1)
INSERT Numbers (Number) values (2)
INSERT Numbers (Number) values (3)
INSERT Numbers (Number) values (4)
INSERT Numbers (Number) values (5)
INSERT Numbers (Number) values (6)
INSERT Numbers (Number) values (7)
INSERT Numbers (Number) values (8)
INSERT Numbers (Number) values (9)
INSERT Numbers (Number) values (10)


--  Results are iteratively built here. This could be a temp table. An index on "Length" might make runs
--  faster for large sets.  Combo must be at least as long as there are characters to be permuted.
CREATE TABLE Results
 (
   Combo   varchar(10)  not null
  ,Length  int          not null
 )

ルーチンは次のとおりです。

SET NOCOUNT on

DECLARE
  @Loop     int
 ,@MaxLoop  int


--  How many elements there are to process
SELECT @MaxLoop = max(SourceId)
 from Source


--  Initialize first value
TRUNCATE TABLE Results
INSERT Results (Combo, Length)
 select Element, 1
  from Source
  where SourceId = 1

SET @Loop = 2

--  Iterate to add each element after the first
WHILE @Loop <= @MaxLoop
 BEGIN

    --  See comments below. Note that the "distinct" remove duplicates, if a given value
    --  is to be included more than once
    INSERT Results (Combo, Length)
     select distinct
        left(re.Combo, @Loop - nm.Number)
        + so.Element
        + right(re.Combo, nm.Number - 1)
       ,@Loop
      from Results re
       inner join Numbers nm
        on nm.Number <= @Loop
       inner join Source so
        on so.SourceId = @Loop
      where re.Length = @Loop - 1

    --  For performance, add this in if sets will be large
    --DELETE Results
    -- where Length <> @Loop

    SET @Loop = @Loop + 1
 END

--  Show results
SELECT *
 from Results
 where Length = @MaxLoop
 order by Combo

一般的な考え方は次のとおりです。任意の文字列(たとえば「A」)に新しい要素(たとえば「B」)を追加する場合、すべての順列をキャッチするには、すべての可能な位置(Ba、aB)にBを追加して、新しいセットを作成します。文字列の。次に繰り返します。すべての文字列(Cba、bCa、baC)について、文字列の各位置に新しい要素(C)を追加し(ABはCab、aCb、abCになります)、順列のセットがあります。文字またはリソースがなくなるまで、次の文字で各結果セットを繰り返します。 10要素は360万の順列であり、上記のアルゴリズムでは約48MBであり、14の(一意の)要素は870億の順列と1.163テラバイトに達します。

最終的にはCTEに組み込まれる可能性があると確信していますが、最終的には栄光のループになります。このように論理がより明確になり、CTE実行計画は悪夢になると思わざるを得ません。

11
Philip Kelley
DECLARE @s VARCHAR(5);
SET @s = 'ABCDE';

WITH Subsets AS (
SELECT CAST(SUBSTRING(@s, Number, 1) AS VARCHAR(5)) AS Token,
CAST('.'+CAST(Number AS CHAR(1))+'.' AS VARCHAR(11)) AS Permutation,
CAST(1 AS INT) AS Iteration
FROM dbo.Numbers WHERE Number BETWEEN 1 AND 5
UNION ALL
SELECT CAST(Token+SUBSTRING(@s, Number, 1) AS VARCHAR(5)) AS Token,
CAST(Permutation+CAST(Number AS CHAR(1))+'.' AS VARCHAR(11)) AS
Permutation,
s.Iteration + 1 AS Iteration
FROM Subsets s JOIN dbo.Numbers n ON s.Permutation NOT LIKE
'%.'+CAST(Number AS CHAR(1))+'.%' AND s.Iteration < 5 AND Number
BETWEEN 1 AND 5
--AND s.Iteration = (SELECT MAX(Iteration) FROM Subsets)
)
SELECT * FROM Subsets
WHERE Iteration = 5
ORDER BY Permutation

Token Permutation Iteration
----- ----------- -----------
ABCDE .1.2.3.4.5. 5
ABCED .1.2.3.5.4. 5
ABDCE .1.2.4.3.5. 5
(snip)
EDBCA .5.4.2.3.1. 5
EDCAB .5.4.3.1.2. 5
EDCBA .5.4.3.2.1. 5

少し前に最初に投稿されました ここ

ただし、C#やC++などのより優れた言語で行う方がよいでしょう。

7
A-K

SQLを使用するだけで、コードなしで、テーブルに別の列をクローバーすることができれば、それを実行できます。明らかに、並べ替える値ごとに1つの結合テーブルが必要です。

with llb as (
  select 'A' as col,1 as cnt union 
  select 'B' as col,3 as cnt union 
  select 'C' as col,9 as cnt union 
  select 'D' as col,27 as cnt
) 
select a1.col,a2.col,a3.col,a4.col
from llb a1
cross join llb a2
cross join llb a3
cross join llb a4
where a1.cnt + a2.cnt + a3.cnt + a4.cnt = 40
7
Dave

デカルト積nx n x n x nを作成し、不要なものを除外したことを正しく理解していますか?別の方法は、nまでのすべての数を生成することです!次に、 階乗法 を使用して、要素エンコーディングを介してそれらをマップします。

2
Tegiri Nenashi

このメソッドは、バイナリマスクを使用して正しい行を選択します。

;with src(t,n,p) as (
select element, index, power(2,index-1)
from table
)
select s1.t+s2.t+s3.t+s4.t
from src s1, src s2, src s3, src s4
where s1.p+s2.p+s3.p+s4.p=power(2,4)-1

私の元の投稿:

declare @t varchar(4) = 'ABCD'

;with src(t,n,p) as (
select substring(@t,1,1),1,power(2,0)
union all
select substring(@t,n+1,1),n+1,power(2,n)
from src
where n < len(@t)
)
select s1.t+s2.t+s3.t+s4.t
from src s1, src s2, src s3, src s4
where s1.p+s2.p+s3.p+s4.p=power(2,len(@t))-1

これはあなたを悩ませているそれらの問題の1つです。私は元の答えの単純さが好きでしたが、私がまだすべての可能な解決策を構築し、次に正しいものを選択しているというこの問題がありました。正しいソリューションを構築するだけでこのプロセスをより効率的にするためのもう1つの試みは、この答えをもたらしました。文字列に文字が存在しない場合にのみ、文字列に文字を追加します。 Patindexは、CTEソリューションの完璧なコンパニオンのように見えました。ここにあります。

declare @t varchar(10) = 'ABCDEFGHIJ'

;with s(t,n) as (
select substring(@t,1,1),1
union all
select substring(@t,n+1,1),n+1
from s where n<len(@t)
)
,j(t) as (
select cast(t as varchar(10)) from s
union all
select cast(j.t+s.t as varchar(10))
from j,s where patindex('%'+s.t+'%',j.t)=0
)
select t from j where len(t)=len(@t)

3分2秒で360万のソリューションすべてを構築することができました。うまくいけば、このソリューションが最初ではないという理由だけで見逃されることはありません。

1
Keith Gresham

再帰CTEよりも単純:

declare @Number Table( Element varchar(MAX), Id varchar(MAX) )
Insert Into @Number Values ( 'A', '01')
Insert Into @Number Values ( 'B', '02')
Insert Into @Number Values ( 'C', '03')
Insert Into @Number Values ( 'D', '04')

select a.Element, b.Element, c.Element, d.Element
from @Number a
join @Number b on b.Element not in (a.Element)
join @Number c on c.Element not in (a.Element, b.Element)
join @Number d on d.Element not in (a.Element, b.Element, c.Element)
order by 1, 2, 3, 4

任意の数の要素について、スクリプトを作成します。

if object_id('tempdb..#number') is not null drop table #number
create table #number (Element char(1), Id int, Alias as '_'+convert(varchar,Id))
insert #number values ('A', 1)
insert #number values ('B', 2)
insert #number values ('C', 3)
insert #number values ('D', 4)
insert #number values ('E', 5)

declare @sql nvarchar(max)
set @sql = '
select '+stuff((
  select char(13)+char(10)+'+'+Alias+'.Element'
  from #number order by Id for xml path (''), type
  ).value('.','NVARCHAR(MAX)'),3,1,' ')

set @sql += '
from #number '+(select top 1 Alias from #number order by Id)

set @sql += (
  select char(13)+char(10)+'join #number '+Alias+' on '+Alias+'.Id not in ('
    +stuff((
      select ', '+Alias+'.Id'
      from #number b where a.Id > b.Id
      order by Id for xml path ('')
      ),1,2,'')
    + ')'
  from #number a where Id > (select min(Id) from #number)
  order by Element for xml path (''), type
  ).value('.','NVARCHAR(MAX)')

set @sql += '
order by 1'

print @sql
exec (@sql)

これを生成するには:

select 
 _1.Element
+_2.Element
+_3.Element
+_4.Element
+_5.Element
from #number _1
join #number _2 on _2.Id not in (_1.Id)
join #number _3 on _3.Id not in (_1.Id, _2.Id)
join #number _4 on _4.Id not in (_1.Id, _2.Id, _3.Id)
join #number _5 on _5.Id not in (_1.Id, _2.Id, _3.Id, _4.Id)
order by 1
1
Peter Radocchia

テーブルの名前がElementsで、4行あるとすると、これは次のように簡単です。

select e1.Element + e2.Element + e3.Element + e4.Element
from Elements e1
    join Elements e2 on e2.Element != e1.Element 
    join Elements e3 on e3.Element != e2.Element AND e3.Element != e1.Element 
    join Elements e4 on e4.Element != e3.Element AND e4.Element != e2.Element AND e4.Element != e1.Element
0
brettlyman

再帰CTEを使用した現在のソリューション。

-- The base elements
Declare @Number Table( Element varchar(MAX), Id varchar(MAX) )
Insert Into @Number Values ( 'A', '01')
Insert Into @Number Values ( 'B', '02')
Insert Into @Number Values ( 'C', '03')
Insert Into @Number Values ( 'D', '04')

-- Number of elements
Declare @ElementsNumber int
Select  @ElementsNumber = COUNT(*)
From    @Number;



-- Permute!
With Permutations(   Permutation,   -- The permutation generated
                     Ids,            -- Which elements where used in the permutation
                     Depth )         -- The permutation length
As
(
    Select  Element,
            Id + ';',
            Depth = 1
    From    @Number
    Union All
    Select  Permutation + ' ' + Element,
            Ids + Id + ';',
            Depth = Depth + 1
    From    Permutations,
            @Number
    Where   Depth < @ElementsNumber And -- Generate only the required permutation number
            Ids Not like '%' + Id + ';%' -- Do not repeat elements in the permutation (this is the reason why we need the 'Ids' column) 
)
Select  Permutation
From    Permutations
Where   Depth = @ElementsNumber
0
SDReyes

-うまくいけば、これは迅速な解決策であり、#Xに入る値を変更するだけです

IF OBJECT_ID('tempdb.dbo.#X', 'U') IS NOT NULL  DROP TABLE #X; CREATE table #X([Opt] [nvarchar](10) NOT NULL)
Insert into #X values('a'),('b'),('c'),('d')
declare @pSQL NVarChar(max)='select * from #X X1 ', @pN int =(select count(*) from #X), @pC int = 0;
while @pC<@pN begin
if @pC>0 set  @pSQL = concat(@pSQL,' cross join #X X', @pC+1);
set @pC = @pC +1;
end
execute(@pSQL)

-または単一列の結果として

IF OBJECT_ID('tempdb.dbo.#X', 'U') IS NOT NULL  DROP TABLE #X; CREATE table #X([Opt] [nvarchar](10) NOT NULL)
Insert into #X values('a'),('b'),('c'),('d')
declare @pSQL NVarChar(max)=' as R from #X X1 ',@pSelect NVarChar(Max)=' ',@pJoin NVarChar(Max)='', @pN int =(select count(*) from #X), @pC int = 0;
while @pC<@pN begin
if @pC>0 set  @pJoin = concat(@pJoin ,' cross join #X X', @pC+1) set @pSelect =  concat(@pSelect ,'+ X', @pC+1,'.Opt ')
set @pC = @pC +1;
end
set @pSQL = concat ('select X1.Opt', @pSelect,@pSQL ,@pJoin)
exec(@pSQL)
0
Paul Bamford

SQLスキルが多すぎるRustですが、同様の問題に対して別の方法を取り、共有する価値があると思いました。

Table1 - X strings in a single field Uno
Table2 - Y strings in a single field Dos

(SELECT Uno, Dos
FROM Table1
CROSS JOIN Table2 ON 1=1)
    UNION
(SELECT  Dos, Uno
FROM Table1
CROSS JOIN Table2 ON 1=1)

CROSSJOINが追加された3つのテーブルの同じ原則

(SELECT  Tres, Uno, Dos
FROM Table1
CROSS JOIN Table2 ON 1=1
    CROSS JOIN Table3 ON 1=1)

ユニオンでは6つのクロス結合セットが必要ですが。

0
Matthew Dunn