テーブル内の任意のレコードの順序付け


28

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

私が見た一般的な解決策は、整数列を追加することです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などのアプリケーションがとるアプローチです。

注文に対するJoomlaのアプローチの例

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

一部の人々は、「2.5」を使用して順序2と3のレコードの間にレコードを挿入できるように、10進数を使用して順序を格納することを提案しています。そして、それは少し助けにはなりますが、奇妙な小数(どこで止まりますか?2.75?2.875?2.8125?)

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


5
ちょうどあなたが知っているので 。。。「このようなシステムが「リレーショナル」と呼ばれる理由は、リレーションという用語は基本的にテーブルの単なる数学用語であるためです。- データベースシステムの概要、CJ日付、第7版。p 25
マイクシェリル 'キャットリコール'


@ MikeSherrill'CatRecall 'キャッチできなかったので、古いものordersとddlの問題を修正しました。
エヴァンキャロル

回答:


17

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

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

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

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

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

レコードを削除する場合は、それ以降のすべてのレコードを並べ替える必要があります

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

次のような状況は簡単に想像できます。

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

ユニークな制約はそれを防ぐことができます。

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

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

一部の人々は、「2.5」を使用して順序2と3のレコードの間にレコードを挿入できるように、10進数を使用して順序を格納することを提案しています。そして、それは少し助けにはなりますが、奇妙な小数(どこで止まりますか?2.75?2.875?2.8125?)

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

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

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

きちんとするために、次のようなもので仕事を終えることができますwith cte as (select *,row_number() over (order by sort_order desc) as row from test) update cte set sort_order=row;
マンゴ

ここに追加のヒントがあります:本当に完璧にしたい場合は、そのまま移動したい場合は、さらに行を移動しているかどうかを確認する必要があります。「そのまま」 - -もしそうなら、それほど数多くのアップデートものを、D
ルーベン・ベック

7

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

2つの列が必要です。

  1. pk = 32ビット integer
  2. order = 64bit bigintnot double

挿入/更新

  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ビットの「カーディナリティホール」を持つことができます。

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


4

通常、順序付けは、レコード、タイトル、ID、またはその特定の状況に適した何らかの情報に従って行われます。

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

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

できれば、列を宣言し、unique再配列を「アトミック」にする手順を用意してください。詳細はシステムによって異なりますが、それは一般的な考え方です。


4

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

誰も気にしない?これらの数値は、コンピューターが処理するためにのみ存在するため、小数の桁数や、見た目がhowいことは関係ありません。

10進値を使用すると、アイテムFをアイテムJとKの間で移動するには、JとKの順序値を選択し、それらを平均してからFを更新するだけです。2つのSELECTステートメントと1つのUPDATEステートメント(おそらく、デッドロック)。

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


1

私自身のプロジェクトでは、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"より多くの値が必要な場合に関係するレコードに単に追加するため、可能な中間の値を使い果たすことはできないということです。(intPython 3では無制限です。そうでない場合、比較するために最後にバイトのスライスを選択する必要があります。2つの隣接する値の間で、差は最後に向かって詰められるという仮定です。)

たとえば、2つのレコードがb"\x00"ありb"\x01"、との間でレコードをやりたいとします。0x00との間に利用可能な値はない0x01ため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']

すべてが辞書式順序になるため、データベースは簡単にソートできます。レコードを削除しても、それは順番どおりです。私のプロジェクトでは、私が作ったb"\x00"b"\xff"などFIRSTLAST記録、しかし、と「を」付加する値/ APPEND新しいレコード「から」仮想としてそれらを使用するために:

>>> 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']

0

私はこの答えをはるかによく見つけました。完全に引用:

データベースは特定のものに対して最適化されています。多くの行をすばやく更新することもその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つの更新ステートメントを見て、何が行われているのかを理解するのは簡単です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.