테이블에서 레코드를 임의로 주문


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을 시작으로 이동하려면 모든 레코드를 다시 정렬해야합니다
  • 중간에 새 레코드를 삽입하려면 그 후에 모든 레코드를 다시 정렬해야합니다
  • 레코드를 제거하려면 레코드마다 모든 레코드를 다시 정렬해야합니다

다음과 같은 상황을 상상하기 쉽습니다.

  • 두 개의 레코드가 동일 order
  • order기록 사이에 차이 가 있습니다

여러 가지 이유로 상당히 쉽게 발생할 수 있습니다.

Joomla와 같은 응용 프로그램은 다음과 같은 접근 방식을 취합니다.

주문에 대한 Joomla의 접근 예

여기의 인터페이스가 나쁘고 사람이 직접 숫자를 편집하는 대신 화살표 나 드래그 앤 드롭을 사용해야한다고 주장 할 수 있습니다. 그러나 무대 뒤에서 같은 일이 일어나고 있습니다.

어떤 사람들은 순서를 저장하기 위해 십진수를 사용하도록 제안했기 때문에 "2.5"를 사용하여 순서 2와 3의 레코드 사이에 레코드를 삽입 할 수 있습니다. 기묘한 십진수

테이블에 주문을 저장하는 더 좋은 방법이 있습니까?


5
알다시피 . . 관계형 "이러한 시스템이 호출되는 이유는" "용어이다 관계는 단지 수학 용어에 대한 기본적 테이블 ..." - 데이터베이스 시스템 소개 , CJ Date, 7th ed. p 25
Mike Sherrill 'Cat


내가 잡지 않은 @ MikeSherrill'CatRecall ', 나는 old orders와 ddl로 질문을 고쳤다 .
Evan Carroll

답변:


17

레코드 0을 시작으로 이동하려면 모든 레코드를 다시 정렬해야합니다

더 간단한 방법이 있습니다.

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

중간에 새 레코드를 삽입하려면 그 후에 모든 레코드를 다시 정렬해야합니다

"사이"값을 지원하는 데이터 유형을 사용하지 않는 한 사실입니다. 부동 소수점 및 숫자 유형을 사용하면 값을 2.5로 업데이트 할 수 있습니다. 그러나 varchar (n)도 작동합니다. ( 'a', 'b', 'c'를 생각한 다음 'ba', 'bb', 'bc'를 생각하십시오.)

레코드를 제거하려면 레코드마다 모든 레코드를 다시 정렬해야합니다

더 간단한 방법이 있습니다. 행을 삭제하십시오. 나머지 행은 여전히 ​​올바르게 정렬됩니다.

다음과 같은 상황을 상상하기 쉽습니다.

두 레코드의 순서가 동일합니다

독특한 제약은 그것을 막을 수 있습니다.

레코드간에 순서에 차이가 있습니다

간격은 dbms가 열의 값을 정렬하는 방법에 영향을 미치지 않습니다.

어떤 사람들은 순서를 저장하기 위해 십진수를 사용하도록 제안했기 때문에 "2.5"를 사용하여 순서 2와 3의 레코드 사이에 레코드를 삽입 할 수 있습니다. 기묘한 십진수

당신이 때까지 멈추지 않는 에. 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. 순서 = 64 비트 bigint( 아님 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?).

누가 신경 쓰나요? 이 숫자는 컴퓨터가 처리 할 수있는 숫자이므로 소수의 소수 자릿수 나 우리에게 얼마나 못 생겼는지는 중요하지 않습니다.

십진수 값을 사용한다는 것은 항목 J와 K 사이에서 항목 F를 이동하려면 J와 K의 순서 값을 선택한 다음 평균을 계산 한 다음 F를 업데이트하기 만하면됩니다. 두 개의 SELECT 문과 하나의 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"을 모두 사용할 수 없으며 더 많은 값이 필요한 경우 관련된 레코드에 a 만 추가하기 때문 입니다. ( int파이썬 3에서는 제한이 없으며, 그렇지 않으면 비교하기 위해 끝에 바이트 조각을 선택해야합니다. 두 개의 인접한 값 사이에 차이가 끝을 향한다고 가정합니다.)

예를 들어, 두 개의 레코드가 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

나는 이 대답이 훨씬 더 낫다는 것을 알았다 . 그것을 완전히 인용 :

데이터베이스는 특정 사안에 최적화되어 있습니다. 많은 행을 빠르게 업데이트하는 것이 그 중 하나입니다. 데이터베이스에서 작업을 수행 할 때 특히 그렇습니다.

치다:

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

그리고 당신 Beat It은 끝 으로 이동하고 싶습니다 , 당신은 두 가지 쿼리를해야합니다

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 = ?

매우 효율적으로 재사용 할 수있는 준비된 진술이 두 개 있습니다.

이것은 몇 가지 중요한 이점을 제공합니다. 테이블의 순서는 추론 할 수있는 것입니다. 세 번째 노래는 order항상 3입니다. 이를 보장하는 유일한 방법은 연속 정수를 순서대로 사용하는 것입니다. 의사 연결 목록이나 10 진수 또는 공백이있는 정수를 사용한다고해서이 속성을 보장 할 수는 없습니다. 이 경우 n 번째 노래를 얻는 유일한 방법은 전체 테이블을 정렬하고 n 번째 레코드를 얻는 것입니다.

실제로 이것은 생각보다 훨씬 쉽습니다. 수행하려는 작업을 파악하고 두 개의 업데이트 문을 생성하고 다른 사람들이이 두 개의 업데이트 문을보고 수행중인 작업을 인식하는 것은 간단합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.