Tự ý sắp xếp hồ sơ trong một bảng


28

Một nhu cầu phổ biến khi sử dụng cơ sở dữ liệu là truy cập các bản ghi theo thứ tự. Ví dụ: nếu tôi có một blog, tôi muốn có thể sắp xếp lại các bài đăng trên blog của mình theo thứ tự tùy ý. Các mục này thường có rất nhiều mối quan hệ, vì vậy một cơ sở dữ liệu quan hệ dường như có ý nghĩa.

Giải pháp phổ biến mà tôi đã thấy là thêm một cột số nguyên 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);

Sau đó, chúng ta có thể sắp xếp các hàng bằng cách sắp xếp orderchúng theo thứ tự thích hợp.

Tuy nhiên, điều này có vẻ vụng về:

  • Nếu tôi muốn di chuyển bản ghi 0 về đầu, tôi phải sắp xếp lại mọi bản ghi
  • Nếu tôi muốn chèn một bản ghi mới vào giữa, tôi phải sắp xếp lại mọi bản ghi sau nó
  • Nếu tôi muốn xóa một bản ghi, tôi phải sắp xếp lại mọi bản ghi sau nó

Thật dễ dàng để tưởng tượng một tình huống như:

  • Hai hồ sơ có cùng order
  • Có những khoảng trống ordergiữa các hồ sơ

Những điều này có thể xảy ra khá dễ dàng vì một số lý do.

Đây là cách tiếp cận mà các ứng dụng như Joomla thực hiện:

Ví dụ về cách tiếp cận của Joomla để đặt hàng

Bạn có thể lập luận rằng giao diện ở đây rất tệ và thay vì con người trực tiếp chỉnh sửa số, họ nên sử dụng mũi tên hoặc kéo và thả xuống và bạn có thể đúng. Nhưng đằng sau hậu trường, điều tương tự đang xảy ra.

Một số người đã đề xuất sử dụng số thập phân để lưu trữ đơn hàng, để bạn có thể sử dụng "2.5" để chèn một bản ghi vào giữa các bản ghi ở thứ tự 2 và 3. Và trong khi điều đó giúp ích được một chút, thì thậm chí còn rắc rối hơn vì bạn có thể kết thúc bằng số thập phân kỳ lạ (bạn dừng ở đâu? 2,75? 2,875? 2,8125?)

Có cách nào tốt hơn để lưu trữ thứ tự trong một bảng?


5
Chỉ để bạn biết. . . "Lý do các hệ thống như vậy được gọi là" quan hệ "là vì mối quan hệ thuật ngữ về cơ bản chỉ là một thuật ngữ toán học cho một bảng ." - Giới thiệu về Hệ thống cơ sở dữ liệu , Ngày CJ, lần thứ 7. trang 25
Mike Sherrill 'Nhớ lại mèo'


@ MikeSherrill'CatRecall 'mà tôi không nắm bắt được, tôi đã sửa câu hỏi bằng cũ ordersvà ddl.
Evan Carroll

Câu trả lời:


17

Nếu tôi muốn di chuyển bản ghi 0 về đầu, tôi phải sắp xếp lại mọi bản ghi

Không, có một cách đơn giản hơn.

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

Nếu tôi muốn chèn một bản ghi mới vào giữa, tôi phải sắp xếp lại mọi bản ghi sau nó

Điều đó đúng, trừ khi bạn sử dụng loại dữ liệu hỗ trợ các giá trị "giữa". Kiểu nổi và số cho phép bạn cập nhật giá trị, giả sử, 2.5. Nhưng varchar (n) cũng hoạt động. (Hãy nghĩ 'a', 'b', 'c'; sau đó nghĩ 'ba', 'bb', 'bc'.)

Nếu tôi muốn xóa một bản ghi, tôi phải sắp xếp lại mọi bản ghi sau nó

Không, có một cách đơn giản hơn. Chỉ cần xóa hàng. Các hàng còn lại vẫn sẽ sắp xếp chính xác.

Thật dễ dàng để tưởng tượng một tình huống như:

Hai hồ sơ có cùng thứ tự

Một ràng buộc độc đáo có thể ngăn chặn điều đó.

Có những khoảng trống theo thứ tự giữa các hồ sơ

Các khoảng trống không ảnh hưởng đến cách dbms sắp xếp các giá trị trong một cột.

Một số người đã đề xuất sử dụng số thập phân để lưu trữ đơn hàng, để bạn có thể sử dụng "2.5" để chèn một bản ghi vào giữa các bản ghi ở thứ tự 2 và 3. Và trong khi điều đó giúp ích được một chút, thì thậm chí còn rắc rối hơn vì bạn có thể kết thúc bằng số thập phân kỳ lạ (bạn dừng ở đâu? 2,75? 2,875? 2,8125?)

Bạn không dừng lại cho đến khi bạn phải . Các dbms không có vấn đề sắp xếp các giá trị có 2, 7 hoặc 15 vị trí sau dấu thập phân.

Tôi nghĩ vấn đề thực sự của bạn là bạn muốn xem các giá trị theo thứ tự được sắp xếp dưới dạng số nguyên. Bạn có thể làm điều đó.

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

Để gọn gàng, bạn có thể hoàn thành công việc với một cái gì đó nhưwith cte as (select *,row_number() over (order by sort_order desc) as row from test) update cte set sort_order=row;
Manngo

Dưới đây là một gợi ý bổ sung: Nếu bạn muốn nó thực sự hoàn hảo, bạn nên kiểm tra xem bạn có di chuyển nhiều hàng hơn không thì bạn muốn giữ nguyên. Nếu vậy, thì hãy cập nhật số lượng ít hơn - "không bị ảnh hưởng" - những cái; D
Ruben Boeck

7

Nó rất đơn giản. Bạn cần phải có cấu trúc "lỗ cardinality":

Bạn cần có 2 cột:

  1. pk = 32 bit integer
  2. đặt hàng = 64 bit bigint( không double )

Chèn / cập nhật

  1. Khi chèn bản ghi mới đầu tiên, thiết lập order = round(max_bigint / 2).
  2. Khi chèn vào đầu bảng, đặt order = round("order of first record" / 2)
  3. Khi chèn vào cuối bảng, đặt order = round("max_bigint - order of last record" / 2) 4) Khi chèn vào giữa, đặtorder = round("order of record before - order of record after" / 2)

Phương pháp này có số lượng tim rất lớn. Nếu bạn có lỗi ràng buộc hoặc nếu bạn nghĩ những gì bạn có số lượng thẻ nhỏ, bạn có thể xây dựng lại cột thứ tự (bình thường hóa).

Trong tình huống tối đa với chuẩn hóa (với cấu trúc này), bạn có thể có "lỗ cardinality" trong 32 bit.

Hãy nhớ không sử dụng các loại dấu phẩy động - thứ tự phải là một giá trị chính xác!


4

Nói chung, việc đặt hàng được thực hiện theo một số thông tin trong hồ sơ, tiêu đề, ID hoặc bất cứ điều gì phù hợp với tình huống cụ thể đó.

Nếu bạn cần một thứ tự đặc biệt, sử dụng một cột số nguyên không tệ như nó có vẻ. Ví dụ: để nhường chỗ cho một bản ghi đi vào vị trí thứ 5, bạn có thể làm một cái gì đó như:

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

Hy vọng rằng bạn có thể tuyên bố cột là uniquevà có thể có một quy trình để sắp xếp lại "nguyên tử". Các chi tiết phụ thuộc vào hệ thống nhưng đó là ý tưởng chung.


4

Có thể nói nó thậm chí còn lộn xộn hơn bởi vì bạn có thể kết thúc với số thập phân kỳ lạ (bạn dừng ở đâu? 2,75? 2,875? 2,8125?)

Ai quan tâm? Những con số này chỉ có ở đó để máy tính xử lý nên không quan trọng chúng có bao nhiêu chữ số phân số hay chúng xấu xí như thế nào đối với chúng ta.

Sử dụng các giá trị thập phân có nghĩa là để di chuyển mục F giữa các mục J và K, tất cả những gì bạn cần làm là chọn các giá trị thứ tự cho J và K sau đó trung bình chúng sau đó cập nhật F. Hai câu lệnh CHỌN và một câu lệnh CẬP NHẬT (có thể được thực hiện bằng cách sử dụng cách ly nối tiếp để tránh bế tắc).

Nếu bạn muốn xem các số nguyên thay vì phân số trong đầu ra thì hãy tính các số nguyên trong ứng dụng khách hoặc sử dụng các hàm ROW_NUMBER () hoặc RANK () (nếu RDBMS của bạn bao gồm chúng).


1

Trong dự án của riêng tôi, tôi dự định thử một giải pháp tương tự như giải pháp số thập phân, nhưng sử dụng mảng byte thay thế:

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

Ý tưởng là bạn không bao giờ có thể hết các giá trị ở giữa có thể bởi vì và bạn chỉ cần thêm một b"\x00"bản ghi vào các bản ghi liên quan nếu bạn cần thêm giá trị. ( intkhông bị ràng buộc trong Python 3, nếu không, bạn phải chọn một lát byte ở cuối để so sánh, giả định rằng, giữa hai giá trị liền kề, sự khác biệt sẽ được đóng gói vào cuối.)

Ví dụ, giả sử bạn có hai bản ghi b"\x00"b"\x01", và bạn muốn một bản ghi đi giữa chúng. Không có bất kỳ giá trị khả dụng nào giữa 0x000x01, vì vậy bạn nối b"\x00"vào cả hai và bây giờ bạn có một loạt các giá trị giữa chúng, bạn có thể sử dụng để chèn các giá trị mới.

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

Cơ sở dữ liệu có thể dễ dàng sắp xếp nó bởi vì mọi thứ kết thúc theo thứ tự từ điển. Nếu bạn xóa một bản ghi, nó vẫn theo thứ tự. Trong dự án của tôi, tôi đã thực hiện b"\x00"b"\xff"như FIRSTLASThồ sơ, tuy nhiên, để sử dụng những người như ảo "từ" và "thành" giá trị để thêm vào trước / append kỷ lục mới:

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

Tôi thấy câu trả lời này tốt hơn nhiều. Trích dẫn hoàn toàn:

Cơ sở dữ liệu được tối ưu hóa cho những thứ nhất định. Cập nhật nhiều hàng một cách nhanh chóng là một trong số đó. Điều này trở nên đặc biệt đúng khi bạn để cơ sở dữ liệu thực hiện công việc của mình.

Xem xét:

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

Và bạn muốn chuyển Beat Itđến cuối, bạn sẽ có hai truy vấn:

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

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

Và đó là nó. Điều này quy mô lên rất tốt với số lượng rất lớn. Hãy thử đặt một vài nghìn bài hát vào danh sách phát giả định trong cơ sở dữ liệu của bạn và xem mất bao lâu để di chuyển một bài hát từ vị trí này sang vị trí khác. Vì chúng có các hình thức rất chuẩn:

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

update table
  set order = ?
  where song = ?

Bạn có hai tuyên bố chuẩn bị mà bạn có thể sử dụng lại rất hiệu quả.

Điều này cung cấp một số lợi thế đáng kể - thứ tự của bảng là thứ mà bạn có thể suy luận. Bài hát thứ ba có order3, luôn luôn. Cách duy nhất để đảm bảo điều này là sử dụng các số nguyên liên tiếp làm đơn đặt hàng. Sử dụng danh sách giả liên kết hoặc số thập phân hoặc số nguyên với các khoảng trống sẽ không cho phép bạn đảm bảo tính chất này; trong những trường hợp này, cách duy nhất để có được bài hát thứ n là sắp xếp toàn bộ bảng và lấy bản ghi thứ n.

Và thực sự, điều này dễ dàng hơn nhiều so với bạn nghĩ. Thật đơn giản để tìm ra những gì bạn muốn làm, để tạo ra hai báo cáo cập nhật và cho người khác xem xét hai tuyên bố cập nhật đó và nhận ra những gì đang được thực hiện.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.