8

データベースの設計を決定する必要があります。要件は、1つのデータベーステーブルにidと呼ばれるAUTO_INCREMENTPRIMARYKEYフィールドがあることです。デフォルトでは、各行は(Webで)ユーザーに表示され、IDの昇順で並べ替えられます。たとえば、テーブルに4つのレコードがある場合。UIには、0、1、2、3の順序で行が表示されます。

現在、ユーザーがUIで行をドラッグアンドドロップして、順序を変更できる必要があります。たとえば、ユーザーがrom 3をドラッグし、 0の下にドロップすると、表示シーケンスは3、0、1、2になります。このシーケンスはデータベースに永続的である必要があります。

これを永続的かつスケーラブルにするためにデータベーステーブルを設計する方法を考えています。私の最初の考えは、各行には表示シーケンスを示す「シーケンス」フィールドがあるということです。デフォルトでは、値はidと同じである必要があります。表示するデータベースからデータを選択すると、行はidではなく昇順で並べ替えられます

シーケンスが変更されると、新しい値に更新されます。その結果、他の行に多くの変更が含まれる可能性があります。上記の例をとると、元々テーブルは次のようになります。

|id   | sequence |
|0    | 0        |
|1    | 1        |
|2    | 2        |
|3    | 3        |

ここで、ID3の行を最初にドラッグした後。そのシーケンスは0に更新されます。同時に、IDが0、1、2の行も更新する必要があります。

|id   | sequence |
|0    | 1        |
|1    | 2        |
|2    | 3        |
|3    | 0        |

このアプローチでは、再シーケンスのコストが多くのリソースになり、スケーラブルではなくなるのではないかと心配しています。したがって、idにK(たとえば、10)を掛けることで、シーケンスを初期化できると思います。これにより、挿入のシーケンス値の間にギャップが残ります。ただし、K + 1行をこのギャップに移動すると、ギャップが消費される可能性があります。

|id   | sequence |
|0    | 0        |
|1    | 10       |
|2    | 20       |
|3    | 30       |

これは、データベース設計に共通の問題のようです。誰かがこれを達成するためのより良いアイデアを持っていますか?

4

8 に答える 8

11

私への明白な答えは、あなたが言及した最後の解決策を使用することですが、小数(浮動小数点数)を使用することです。

したがって、次のように始めます{0.1, 0.2, 0.3, 0.4, 0.5}。最後の項目を間に移動する0.2と、0.3になり0.25ます。上に移動すると になり0.05ます。毎回、両側の 2 つの数値の中間点を取ります。つまり、前後のアイテムの平均です。

別の同様の解決策は、文字を使用してから、文字列でアルファベット順に並べ替えることです。から始めて{1, 2, 3, 4, 5}、2 と 3 の間で 5 を移動すると、25 を使用します。リストの文字列ソートを行うと、正しい順序が維持されます{1, 2, 25, 3, 4}

これらの方法で私が考えることができる唯一の問題は、最終的に浮動小数点の精度の限界に達すること0.0078125です0.0078124。これを解決するいくつかの方法:

  • すべてのアイテムを実行し、それらを に並べ替えるスクリプトを頻繁に実行します{0.1, 0.2, 0.3, ...}
  • 小数点以下 1 桁を使用できる場合は、小数点以下 2 桁を使用しないでください。0.2との間で、 計算された の代わりに0.25使用できます。0.230.225
  • グローバルではなく、ローカルで再シーケンスします。{0.2, 0.3, 0.6}の後に挿入したい場合は0.2、2 番目の項目を に設定し0.4、新しい項目を に挿入できます0.3
于 2009-10-22T16:49:02.157 に答える
3

IDとSequence/SortOrderは別個のものであり、相互に依存してはなりません。

上への移動/下への移動機能の場合:Sequence/SortOrder値を交換できます

また

ドラッグアンドドロップ機能の場合:

1)選択したレコードの新しいシーケンス/注文番号を確立します。

2)選択したレコードの現在のシーケンスを取得し、選択したレコードを新しい番号で更新します。

3)a)新しいシーケンス番号が現在のシーケンス番号よりも小さい場合は、シーケンス番号> =新しいシーケンス番号(選択したものを除く)を持つレコードのすべてのシーケンス番号をインクリメントします。

b)新しいシーケンス番号が現在のシーケンス番号よりも大きい場合は、新しく選択したシーケンス番号より下で現在のシーケンス番号より上にあるすべてのシーケンス番号をデクリメントします。

これが理にかなっていることを願っています、そして私はそれを正しい方法で持っています(以下は実際の実装です)。

私はこれを、純粋主義者向けではなく、少量のロジックを持つ単一のSQLステートメントに実装しましたが、うまく機能します。

次に例を示します(OP:GUID IDをINTに変更する必要があります):

CREATE PROCEDURE [proc_UpdateCountryRowOrder]
    @ID UNIQUEIDENTIFIER,
    @NewPosition INT
AS

SET NOCOUNT ON

DECLARE @CurrentPosition INT
DECLARE @MaximumPosition INT

IF (@NewPosition < 1) SET @NewPosition = 1

SELECT @CurrentPosition = [Countries].[Order]
FROM [Countries]
WHERE [Countries].[ID] = @ID

SELECT @MaximumPosition = MAX([Countries].[Order])
FROM [Countries]

IF (@NewPosition > @MaximumPosition) SET @NewPosition = @MaximumPosition

IF (@NewPosition <> @CurrentPosition)
BEGIN
    IF (@NewPosition < @CurrentPosition)
    BEGIN
        BEGIN TRAN

        UPDATE [Countries]
        SET [Countries].[Order] = [Countries].[Order] + 1
        WHERE [Countries].[Order] >= @NewPosition
        AND [Countries].[Order] < @CurrentPosition

        UPDATE [Countries]
        SET [Countries].[Order] = @NewPosition
        WHERE ID = @ID

        COMMIT TRAN
    END
    ELSE
    BEGIN
        BEGIN TRAN

        UPDATE [Countries]
        SET [Countries].[Order] = [Countries].[Order] - 1
        WHERE [Countries].[Order] <= @NewPosition
        AND [Countries].[Order] > @CurrentPosition

        UPDATE [Countries]
        SET [Countries].[Order] = @NewPosition
        WHERE ID = @ID

        COMMIT TRAN
    END
END
GO
于 2009-10-17T10:01:05.077 に答える
2

リンクリストはどうですか?:-)

CREATE TABLE item(
    id INT PRIMARY KEY,
    prev INT,
    next INT
);

WITH RECURSIVE sequence AS (
    SELECT item.id, item.prev, item.next FROM item
    WHERE item.prev IS NULL
  UNION
    SELECT item.id, item.prev, item.next FROM sequence
    INNER JOIN item ON sequence.next = item.id
)
SELECT * FROM sequence;

実際、これが実際に機能するかどうかをテストするためのPostgreSQLは手元にありません(そしてMySQLはSQL-99をサポートしていませんWITH RECURSIVE)。また、私もそれを真剣に推奨していません。

于 2009-10-17T05:36:50.270 に答える
1

私はここで同様の質問に答えました:大きなデータセットを視覚的に注文する

多くのアイテムを移動する場合は、各アイテムをループして移動し、オーバーフローもチェックする必要があります。しかし、全体として、基本的なロジックは、定期的に再初期化できるギャップのある並べ替え列を用意することです。

于 2009-10-22T16:53:16.240 に答える
0

これを行うには、ユーザーが選択した順序で ID (db キー) の CSV 文字列をサーバーに返します。私のデータベースには、csv文字列を2つのフィールド(IDとシーケンス(実際にはIDを持つint))を持つテーブルに変換する関数があります。この一時テーブルのシーケンス フィールドの値は、CSV 文字列内の項目の順序を反映しています。次に、ID と一致する新しいシーケンス フィールド値でデータ テーブルを更新します。

編集:バウンティポイントはとても美味しそうに見えたので、回答の詳細を提供したいと思いました. コードは次のとおりです。

declare @id_array varchar(1000)
set @id_array = '47,32,176,12,482'

declare @id_list_table table ([id] int, [sequence] int)

insert @id_list_table ([id], [sequence])
  select [id], [sequence]
  from get_id_table_from_list (@id_array)

update date_table
  set [sequence] = id_list.[sequence]
  from date_table
    inner join @id_list_table as id_list
      on (id_list.[id] = date_table.[id])

テスト用の変数として設定し@id_arrayました。通常、UI は変更された順序で id 値を取得し、それをパラメータとしてストアド プロシージャに渡します。このget_id_table_from_list関数は、csv 文字列を [id] と [sequence] の 2 つの「int」列を持つテーブルに解析します。[sequence] 列は ID です。私のテストデータで機能するその関数の結果は次のようになります。

    ID シーケンス
    47 1  
    32 2  
    176 3  
    12 4  
    482 5  

csv を解析する関数が必要になります (興味があれば投稿できますし、あちこちに投稿されている他の人も見てきました)。私のコードは、SQL サーバーを使用していることを前提としています - シーケンスは ID フィールドに依存し、更新クエリは T-SQL 拡張 ('from' 句) を使用します - 他のデータベースを使用している場合は、簡単な変更を加えます。

于 2009-10-17T19:07:20.487 に答える
0

この修正は、思ったより簡単です。1 つの一時テーブルと 1 つの更新クエリで完了です。


CREATE TABLE #TempData
(
NewSequence bigint identity(1,1),
[Id] BigInt
)

INSERT INTO #TempData ([Id])
SELECT [Id] 
FROM TableNameGoesHere
ORDER BY Sequence

UPDATE TableNameGoesHere
SET Sequence = t2.NewSequence
FROM TableNameGoesHere t1
INNER JOIN #TempData t2
ON t1.[Id] = t2.[Id]

DROP TABLE #TempData
于 2009-10-25T02:11:30.777 に答える
0

これは3年前の質問であることは知っていますが、コメントは同様の問題を解決するのに役立ちました.

@DisgruntledGoat から提供されたコード サンプル (ありがとうございます) に基づいて、Entity Framework 4.1 コード ファースト用に特別に記述された次のコードを作成しました。基本的には、リポジトリ オブジェクト、エンティティ オブジェクトの ID、およびエンティティの DisplayOrder をディスプレイ内で上下に移動する必要があるかどうかを示すブール値の 3 つのパラメータを取ります。エンティティは Entity から継承する必要があります。つまり、Id 値が必要であり、IOrderedEntity を実装する必要があります。つまり、DisplayOrder float プロパティが必要です。

MoveDisplayOrder メソッドは、隣接する 2 つのエントリ (移動方向に応じて、現在の表示順序よりも小さいか大きいか) を見つけ、それらの値を平均します (したがって、整数値ではなく float が必要です)。次に、そのエンティティのリポジトリを更新します。次に、クリーンアップの目的で、結果の新しい表示順序の小数点以下の桁数が 5 を超える場合、EF を使用してデータベース内のすべての値を更新し、1、2、3 などの値で再シードします。

このコードは私にとって完璧に機能しており、クリーンアップまたはリファクタリングが必要な場合はフィードバックを歓迎します.

  public abstract class Entity : IIdentifiableEntity
  {
    public int Id { get; set; }
  }

  public interface IOrderedEntity
  {
    float DisplayOrder { get; set; }
  }

  public interface IRepository<TEntity>
  {
    TEntity FindById(int id);
    bool InsertOrUpdate(TEntity entity);
    // More repository methods here...
  }

public static class RepositoryExtenstions
{
  public static void MoveDisplayOrder<T>(IRepository<T> repository, int id, bool moveUp) where T : Entity, IOrderedEntity
  {
    var currentStatus = repository.FindById(id);
    IQueryable<IOrderedEntity> adjacentStatuses;
    if (moveUp)
      adjacentStatuses = repository.All().OrderByDescending(ms => ms.DisplayOrder).Where(ms => ms.DisplayOrder < currentStatus.DisplayOrder);
    else
      adjacentStatuses = repository.All().OrderBy(ms => ms.DisplayOrder).Where(ms => ms.DisplayOrder > currentStatus.DisplayOrder);

    var adjacentTwoDisplayOrders = adjacentStatuses.Select(ms => ms.DisplayOrder).Take(2).ToList();
    float averageOfPreviousTwoDisplayOrders;
    switch (adjacentTwoDisplayOrders.Count)
    {
      case 0:
        // It's already at the top or bottom, so don't move it
        averageOfPreviousTwoDisplayOrders = currentStatus.DisplayOrder;
        break;
      case 1:
        // It's one away, so just add or subtract 0.5 to the adjacent value
        if (moveUp)
          averageOfPreviousTwoDisplayOrders = adjacentTwoDisplayOrders[0] - 0.5F;
        else
          averageOfPreviousTwoDisplayOrders = adjacentTwoDisplayOrders[0] + 0.5F;
        break;
      default: // 2
        // Otherwise, just average the adjacent two values
        averageOfPreviousTwoDisplayOrders = adjacentTwoDisplayOrders.Average();
        break;
    }

    currentStatus.DisplayOrder = averageOfPreviousTwoDisplayOrders;
    repository.InsertOrUpdate(currentStatus);
    var floatPrecision = currentStatus.DisplayOrder.ToString().Substring(currentStatus.DisplayOrder.ToString().IndexOf('.') + 1).Length;
    if(floatPrecision > 5)
      ReorganizeDisplayOrder(repository);
  }

  public static void ReorganizeDisplayOrder<T>(IRepository<T> repository) where T : Entity, IOrderedEntity
  {
    var entities = repository.All().OrderBy(ms => ms.DisplayOrder).ToList();
    float counter = 1F;
    foreach (var entity in entities)
    {
      entity.DisplayOrder = counter;
      repository.InsertOrUpdate(entity);
      counter++;
    }
  }
}
于 2012-01-08T17:19:41.587 に答える
-1

-編集:この投稿を読んでいる他の人のために、私はこのトピックに関する一般的な不正確さではなく、いくつかの奇妙な個人的な恨みから反対票を投じました。:)

- 年:

これを行う一般的な方法は、「シーケンス」番号を使用することです(私はこれを「SortOrder」と呼びます)。

とても簡単です。

「スケーラブル」は含まれていません。作業中のすべてのノードのリストがすでにあるので(あなたが言うようにドラッグアンドドロップ)、実際に行っているのはそれらの番号を交換することだけです。

些細なことです。

于 2009-10-17T05:42:46.053 に答える