web-dev-qa-db-ja.com

テーブル内のレコードを任意に並べ替える

データベースを使用する場合の一般的なニーズは、レコードに順番にアクセスすることです。たとえば、ブログがある場合、ブログの投稿を任意の順序で並べ替えることができます。これらのエントリは多くの関係を持っていることが多いため、リレーショナルデータベースは理にかなっているようです。

私が見た一般的な解決策は、整数列orderを追加することです:

CREATE TABLE AS your_table (id, title, sort_order)
AS VALUES
  (0, 'Lorem ipsum',   3),
  (1, 'Dolor sit',     2),
  (2, 'Amet, consect', 0),
  (3, 'Elit fusce',    1);

次に、行をorderでソートして、適切な順序で取得できます。

しかし、これは不器用に思えます:

  • レコード0を最初に移動する場合は、すべてのレコードを並べ替える必要があります
  • 途中に新しいレコードを挿入したい場合は、その後にすべてのレコードを並べ替える必要があります
  • レコードを削除したい場合、その後にすべてのレコードを並べ替える必要があります

次のような状況を想像するのは簡単です。

  • 2つのレコードが同じorder
  • レコード間のorderにギャップがあります

これらは、いくつかの理由でかなり簡単に発生する可能性があります。

これはJoomlaのようなアプリケーションが取るアプローチです:

Example of Joomla's approach to ordering

ここのインターフェースは悪いことであり、人間が数値を直接編集する代わりに、矢印またはドラッグアンドドロップを使用するべきだと主張することができます。おそらくあなたは正しいでしょう。しかし、舞台裏では、同じことが起こっています。

小数を使用して順序を保存することを提案している人もいます。そのため、「2.5」を使用して、2と3の順序のレコードの間にレコードを挿入できます。奇妙な小数(どこで止めますか?2.75?2.875?2.8125?)

テーブルに注文を保存するより良い方法はありますか?

30
Tom Marthenal

レコード0を最初に移動する場合は、すべてのレコードを並べ替える必要があります

いいえ、もっと簡単な方法があります。

update your_table
set order = -1 
where id = 0;

途中に新しいレコードを挿入したい場合は、その後にすべてのレコードを並べ替える必要があります

「between」値をサポートするデータ型を使用しない限り、それは事実です。浮動小数点型と数値型を使用すると、値をたとえば2.5に更新できます。しかし、varchar(n)も機能します。 (「a」、「b」、「c」を考えてください。次に、「ba」、「bb」、「bc」を考えてください。)

レコードを削除したい場合、その後にすべてのレコードを並べ替える必要があります

いいえ、もっと簡単な方法があります。行を削除するだけです。残りの行は引き続き正しくソートされます。

次のような状況を想像するのは簡単です。

2つのレコードの順序が同じです

一意の制約はそれを防ぐことができます。

レコード間の順序にギャップがあります

ギャップは、dbmsが列の値をソートする方法には影響しません。

小数を使用して順序を保存することを提案している人もいます。そのため、「2.5」を使用して、2と3の順序のレコードの間にレコードを挿入できます。奇妙な小数(どこで止めますか?2.75?2.875?2.8125?)

haveになるまで停止しません。 dbmsにはnoの問題があり、小数点以下2、7、または15桁の値のソートに問題があります。

私はあなたのreal問題は整数としてソートされた順序でsee値をしたいことだと思います。出来るよ。

create table your_table (
  id int primary key, 
  title varchar(13), 
  sort_order float
);

insert into your_table values
(0, 'Lorem ipsum', 2.0),
(1, 'Dolor sit', 1.5),
(2, 'Amet, consect', 0.0),
(3, 'Elit fusce', 1.0);

-- This windowing function will "transform" the floats into sorted integers.
select id, title,
       row_number() over (order by sort_order)
from your_table

とてもシンプルです。 「カーディナリティホール」構造が必要です。

2つの列が必要です。

  1. pk = 32ビットinteger
  2. 順序= 64ビットbigintnotdouble

挿入/更新

  1. 最初の新しいレコードを挿入するときに、order = round(max_bigint / 2)を設定します。
  2. テーブルの先頭に挿入する場合は、order = round("order of first record" / 2)を設定します
  3. テーブルの最後に挿入する場合はorder = round("max_bigint - order of last record" / 2)を設定4)途中に挿入する場合はorder = round("order of record before - order of record after" / 2)を設定

このメソッドには、非常に大きなカーディナリティがあります。制約エラーがある場合、またはカーディナリティが小さいと思われる場合は、注文列を再構築(正規化)できます。

(この構造で)正規化を使用した最大の状況では、32ビットで「カーディナリティホール」を持つことができます。

浮動小数点型を使用しないことを忘れないでください-順序は正確な値でなければなりません!

8
user2382679

一般に、順序付けは、レコード、タイトル、ID、またはその特定の状況に適切なもののいくつかの情報に従って行われます。

特別な順序付けが必要な場合、整数列の使用は見かけほど悪くはありません。たとえば、レコードが5位に入る余地を作るには、次のようにします。

update table_1 set place = place + 1 where place > 5

うまくいけば、列をuniqueとして宣言し、多分「アトミック」に再配置するプロシージャを作成できます。詳細はシステムによって異なりますが、それは一般的な考え方です。

4
igelkott

…奇妙な小数になる可能性があるので、間違いなくもっと厄介です(どこで止めますか?2.75?2.875?2.8125?)

誰も気にしない?これらの数値は、コンピューターが処理するためだけに存在するため、小数桁数がいくつあっても、醜いように見えてもかまいません。

10進数の値を使用すると、アイテムFをアイテムJとKの間で移動するには、JとKの順序値を選択し、それらを平均してからFを更新するだけです。デッドロック)。

出力に分数ではなく整数を表示する場合は、クライアントアプリケーションで整数を計算するか、ROW_NUMBER()またはRANK()関数を使用します(RDBMSに整数が含まれている場合)。

4

私自身のプロジェクトでは、10進数のソリューションと同様のソリューションを試す予定ですが、代わりにバイト配列を使用します。

def pad(x, x_len, length):
    if x_len >= length:
        return x
    else:
        for _ in range(length - x_len):
            x += b"\x00"
        return x

def order_index(_from, _to, count, length=None):
    assert _from != _to
    assert _from < _to

    if not length:
        from_len = len(_from)
        to_len = len(_to)
        length = max(from_len, to_len)

        _from = pad(_from, from_len, length)
        _to = pad(_to, to_len, length)

    from_int = int.from_bytes(_from, "big")
    to_int = int.from_bytes(_to, "big")
    inc = (to_int - from_int)//(count + 1)
    if not inc:
        length += 1
        _from += b"\x00"
        _to += b"\x00"
        return order_index(_from, _to, count, length)

    return (int.to_bytes(from_int + ((x+1)*inc), length, "big") for x in range(count))
>>> index = order_index(b"A", b"Z", 24)
>>> [x for x in index]
[b'B', b'C', b'D', b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y']
>>> 
>>> index = order_index(b"A", b"Z", 25)
>>> [x for x in index]
[b'A\xf6', b'B\xec', b'C\xe2', b'D\xd8', b'E\xce', b'F\xc4', b'G\xba', b'H\xb0', b'I\xa6', b'J\x9c', b'K\x92', b'L\x88', b'M~', b'Nt', b'Oj', b'P`', b'QV', b'RL', b'SB', b'T8', b'U.', b'V$', b'W\x1a', b'X\x10', b'Y\x06']

考えられるのは、可能な中間値が不足することは決してないということです。さらに値が必要な場合は、関係するレコードにb"\x00"を追加するだけです。 (intはPython 3で制限されていません。そうでない場合は、最後に比較するバイトのスライスを選択する必要があります。2つの隣接する値の間で、違いは終わりに向かって詰め込まれます。)

たとえば、b"\x00"b"\x01"の2つのレコードがあり、それらの間のレコードが必要だとします。 0x000x01の間には使用可能な値がないため、両方にb"\x00"を追加すると、それらの間に新しい値を挿入するために使用できる値の束ができます。

>>> records = [b"\x00", b"\x01", b"\x02"]
>>> values = [x for x in order_index(records[0], records[1], 3)]
>>> records = records + values
>>> records.sort()
>>> records
[b'\x00', b'\x00@', b'\x00\x80', b'\x00\xc0', b'\x01', b'\x02']

すべてが辞書式順序になるため、データベースはデータベースを簡単にソートできます。レコードを削除しても、まだ正常です。私のプロジェクトでは、仮想の「from」および「to」として使用するために、b"\x00"およびb"\xff"FIRSTおよびLASTレコードとして作成しました。 "新しいレコードを追加/追加する値:

>>> records = []
>>> value = next(order_index(FIRST, LAST, 1))
>>> value
b'\x7f'
>>> records.append(value)
>>> value = next(order_index(records[0], LAST, 1))
>>> value
b'\xbf'
>>> records.append(value)
>>> records.sort()
>>> records
[b'\x7f', b'\xbf']
>>> value = next(order_index(FIRST, records[0], 1))
>>> value
b'?'
>>> records.append(value)
>>> records.sort()
>>> records
[b'?', b'\x7f', b'\xbf']
1
tjb1982

私は この答え を発見しました。完全に引用すると:

データベースは特定の目的のために最適化されています。多くの行をすばやく更新することもその1つです。これは、データベースに機能させる場合に特に当てはまります。

検討してください:

order song
1     Happy Birthday
2     Beat It
3     Never Gonna Give You Up
4     Safety Dance
5     Imperial March

そして、あなたは移動したいBeat It最後に、2つのクエリを作成します。

update table 
  set order = order - 1
  where order >= 2 and order <= 5;

update table
  set order = 5
  where song = 'Beat It'

以上です。これは非常に大きな数で非常によくスケールアップします。データベースの架空のプレイリストに数千曲を入れて、曲をある場所から別の場所に移動するのにかかる時間を確認してください。これらは非常に標準化された形式を持っているので:

update table 
  set order = order - 1
  where order >= ? and order <= ?;

update table
  set order = ?
  where song = ?

非常に効率的に再利用できる2つの準備済みステートメントがあります。

これにはいくつかの重要な利点があります-テーブルの順序はあなたが考えることができるものです。 3番目の曲のorderは常に3です。これを保証する唯一の方法は、順序として連続した整数を使用することです。疑似リンクリスト、10進数、またはギャップのある整数を使用しても、このプロパティは保証されません。これらの場合、n番目の曲を取得する唯一の方法は、テーブル全体をソートしてn番目のレコードを取得することです。

そして、本当に、これはあなたが思っているよりもずっと簡単です。 2つの更新ステートメントを生成し、他の人がこれら2つの更新ステートメントを見て、何が行われているのかを理解するために、何をしたいかを理解するのは簡単です。

1
vedant