0

テーブルをグループに分割する際に興味深い問題があります。私は観光客のグループを持っています - それぞれが単一の言語を話し、および/または家族の一員です. テーブルをグループに分割する必要がありますが、家族や似た言語の話者を一緒に保ちたいと考えています。

観光客を 3 人までのグループに分けたいとしましょう (グループをもっと大きくする必要がある場合は、それで問題ありません)。ソリューションは、すべてのグループを完全に埋めるほどスマートである必要はありませんが、ベスト エフォート型のアプローチをとっています。

入力:

TouristID | LanguageID | FamilyID
---------------------------------
    1     |     1      |    1
    2     |     1      |    1
    3     |     1      |    1
    4     |     2      |    1
    5     |     3      |    2
    6     |     4      |    2
    7     |     5      |    3
    8     |     5      |    4
    9     |     7      |    5

望ましい結果:

TouristID | GroupID
-------------------
    1     |    1
    2     |    1
    3     |    1
    4     |    1
    5     |    2
    6     |    2
    7     |    3
    8     |    3
    9     |    2

グループ 1 は、除外できない 1 人の家族を含む、すべての言語 1 の話者によって形成されます。

グループ 2 は、2 人のファミリー メンバー (5、6) と 1 人のランダム メンバー (9) によって形成され、3 人のグループになります。

グループ 3 は 2 人の同じ言語話者によって形成されます (7, 8)

私がやったこと

INSERT TouristGroup
SELECT
  t.TouristID,
  DENSE_RANK() OVER (ORDER BY GroupID) AS [GroupID]
FROM Tourists t
CROSS APPLY (
  SELECT MIN(TouristID) AS [GroupID]
  FROM Tourists t2
  WHERE
    ( t2.LanguageID = t.LanguageID
    OR t2.FamilyID = t.FamilyID )
) x;

INSERT Groups
SELECT GroupID, COUNT(*)
FROM TouristGroup
GROUP BY GroupID;

declare 
  @matchID int = 0,
  @currentCount int,
  @desiredCount int = 0,
  @candidateGroupID int = null,
  @chunk int = 1

while exists (
  select null
  from Groups g
  left join Matches m
    on m.GroupID = g.GroupID
  where m.GroupID is null
)
begin
  set @currentCount = null
  set @candidateGroupID = null

  select
    @currentCount = isnull(SUM([Count]), 0)
  from Matches m
  join Groups g
    on g.GroupID = m.GroupID
  where m.MatchID = @matchID

  if @CurrentCount is not null
  begin
    set @desiredCount = @chunk - @desiredCount

    select top 1
      @candidateGroupID = g.GroupID
    from Groups g
    left join Matches m
      on m.GroupID = g.GroupID
    where g.[Count] <= @desiredCount
      and m.GroupID is null
    order by [Count] DESC

    if @candidateGroupID is not null
    begin
      insert Matches
      select @matchID, @candidateGroupID
    end
    else begin
      set @matchID = @matchID + 1
    end
  end
  else begin
    set @matchid = @matchID + 1
  end
end         

質問

複数の列に基づいて行をグループ化して、テーブルを分割するためのより良い方法はありますか?

4

1 に答える 1

1

これにより、「ステップ 1」が生成されます。多分それはあなたが今持っているものよりも優れています(ループなし)。

SELECT t.TouristID, DENSE_RANK() OVER (ORDER BY x.GroupNum) as GroupId
FROM Tourists t
CROSS APPLY (SELECT MIN(TouristId) AS GroupNum 
             FROM @Tourist t2 
             WHERE t2.LanguageId = t.LanguageId OR t2.FamilyId = t.FamilyId
            ) x

少なくとも 3 人のグループ メンバーを取得するという他の要件については、可能であれば、現在行っていることと同様のループを実行する必要があります (改善できるかどうかはわかりません。共有しました)。

[更新] 「ステップ 2」に対する私の提案は次のとおりです。

DECLARE @MinGroupSize int = 3, @rc int = 1
WHILE @rc>0
BEGIN
    WITH GroupCount AS (
    SELECT GroupID, COUNT(*) AS GroupCount
    FROM TouristGroup
    GROUP BY GroupID
    ), CandidateGroups AS (
    SELECT TOP 1 gc1.GroupID AS ShortGroupId, singleton.GroupID as SingletonGroupID
    FROM GroupCount gc1
    CROSS APPLY (SELECT TOP 1 GroupID
                 FROM GroupCount AS gc2
                 WHERE gc2.GroupCount = 1 AND gc2.GroupID != gc1.GroupID
                 ORDER BY gc2.GroupID
                 ) AS singleton
    WHERE gc1.GroupCount < @MinGroupSize
    ORDER BY GroupCount DESC, gc1.GroupID ASC
    )
    UPDATE tg
    SET GroupID = cg.ShortGroupID
    FROM TouristGroup tg
    JOIN CandidateGroups cg ON cg.SingletonGroupID = tg.GroupID;
    SET @rc = @@ROWCOUNT;
END
--
-- If you're anal like me and want to eliminate gaps in GroupID values
--
UPDATE tg
SET GroupID = tg2.GroupID
FROM TouristGroup tg
JOIN (SELECT TouristID,  DENSE_RANK() OVER (ORDER BY GroupID) AS [GroupID]
      FROM TouristGroup) AS tg2 ON tg2.TouristID = tg.TouristID
WHERE tg.GroupID != tg2.GroupID;

これにより、必要な最小グループ サイズよりも小さいグループが検出され、シングルトン グループ (1 メンバーのみ) が検出され、シングルトンが他の GroupID で更新され、候補がなくなるまでこれが 1 つずつ実行されます。小さいグループが順番に (GroupCount の降順、次に GroupID の昇順で) 選択されるため、最初に大きいグループが満たされます。自然なグループが分割されないように、シングルトンのみが更新用に選択されます。

于 2013-03-21T21:21:47.167 に答える