จัดเรียงเร็กคอร์ดในตารางโดยพลการ


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 และในขณะที่ช่วยเล็กน้อยก็อาจจะยุ่งเหยิงเพราะคุณสามารถจบลงด้วย ทศนิยมแปลก ๆ (คุณหยุดที่ไหน 2.75? 2.875? 2.8125?)

มีวิธีที่ดีกว่าในการจัดเก็บคำสั่งซื้อในตารางหรือไม่?


5
เพียงเพื่อให้คุณรู้ว่า . . . "เหตุผลที่ระบบดังกล่าวเรียกว่า" ความสัมพันธ์ "คือความสัมพันธ์ของคำนั้นเป็นเพียงศัพท์ทางคณิตศาสตร์สำหรับตาราง . - คำแนะนำเกี่ยวกับระบบฐานข้อมูลวันที่ CJ วันที่ 7 ed p 25
ไมค์ Sherrill 'Cat Recall'


@ MikeSherrill'CatRecall 'ที่ฉันไม่ได้จับฉันได้แก้ไขคำถามกับเก่า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')

หากฉันต้องการลบบันทึกฉันต้องเรียงลำดับใหม่ทุกระเบียนหลังจากนั้น

ไม่มันมีวิธีที่ง่ายกว่า เพียงลบแถว แถวที่เหลือจะยังคงเรียงลำดับอย่างถูกต้อง

มันง่ายที่จะจินตนาการถึงสถานการณ์เช่น:

สองระเบียนมีลำดับเดียวกัน

ข้อ จำกัด ที่ไม่ซ้ำใครสามารถป้องกันได้

มีช่องว่างตามลำดับระหว่างระเบียน

Gaps ไม่มีผลต่อวิธีที่ dbms เรียงลำดับค่าในคอลัมน์

บางคนเสนอให้ใช้ทศนิยมในการเก็บคำสั่งซื้อเพื่อให้คุณสามารถใช้ "2.5" เพื่อแทรกเรคคอร์ดในระหว่างเรคคอร์ดตามลำดับที่ 2 และ 3 และในขณะที่ช่วยเล็กน้อยก็อาจจะยุ่งเหยิงเพราะคุณสามารถจบลงด้วย ทศนิยมแปลก ๆ (คุณหยุดที่ไหน 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;
Manngo

นี่คือคำใบ้เพิ่มเติม: หากคุณต้องการให้มันสมบูรณ์แบบจริงๆคุณควรตรวจสอบว่าคุณย้ายแถวมากขึ้นหรือไม่แล้วคุณต้องการที่จะไม่แตะต้อง ถ้าเป็นเช่นนั้นให้อัปเดตจำนวนน้อย - "ไม่มีการแตะ" - อัน; D
Ruben Boeck

7

มันง่ายมาก คุณต้องมีโครงสร้าง "cardinality hole":

คุณต้องมี 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)

วิธีนี้มีความสำคัญมาก หากคุณมีข้อผิดพลาดข้อ จำกัด หรือถ้าคุณคิดว่าสิ่งที่คุณมี cardinality ขนาดเล็กคุณสามารถสร้างคอลัมน์คำสั่งซื้อใหม่ (ปกติ)

ในสถานการณ์สูงสุดด้วยการทำให้เป็นมาตรฐาน (ด้วยโครงสร้างนี้) คุณสามารถมี "รูกลมของหัวใจ" ได้ใน 32 บิต

จำไว้ว่าอย่าใช้ประเภทจุดลอย - ลำดับต้องเป็นค่าที่แม่นยำ!


4

โดยทั่วไปการสั่งซื้อจะกระทำตามข้อมูลบางอย่างในบันทึกชื่อเรื่อง ID หรืออะไรก็ตามที่เหมาะสมกับสถานการณ์นั้น

หากคุณต้องการการสั่งซื้อพิเศษการใช้คอลัมน์จำนวนเต็มไม่ได้แย่อย่างที่คิด ตัวอย่างเช่นเพื่อให้มีที่ว่างสำหรับระเบียนเพื่อไปยังอันดับที่ 5 คุณสามารถทำสิ่งต่อไปนี้

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

หวังว่าคุณสามารถประกาศคอลัมน์ที่จะเป็นuniqueและอาจมีขั้นตอนการจัดเรียงใหม่ "atomic" รายละเอียดขึ้นอยู่กับระบบ แต่เป็นแนวคิดทั่วไป


4

…มันอาจจะเป็นเรื่องที่ยุ่งมากขึ้นเพราะคุณสามารถจบลงด้วยทศนิยมแปลก ๆ (คุณหยุดที่ไหน? 2.75? 2.875? 2.8125?)

ใครสน? ตัวเลขเหล่านี้มีไว้เพื่อให้คอมพิวเตอร์จัดการเท่านั้นดังนั้นจึงไม่สำคัญว่าจะมีเศษส่วนจำนวนเท่าใดหรือมีลักษณะที่น่าเกลียดสำหรับเราเพียงใด

การใช้ค่าทศนิยมหมายความว่าการย้ายรายการ F ระหว่างรายการ J และ K ทั้งหมดที่คุณต้องทำคือเลือกค่าการสั่งซื้อสำหรับ J และ K จากนั้นหาค่าเฉลี่ยแล้วอัปเดต F สองคำสั่ง SELECT และหนึ่งคำสั่ง UPDATE (อาจทำได้โดยใช้ deadlocks)

หากคุณต้องการดูจำนวนเต็มแทนที่จะเป็นเศษส่วนในผลลัพธ์ให้คำนวณจำนวนเต็มในแอปพลิเคชันไคลเอนต์หรือใช้ฟังก์ชัน ROW_NUMBER () หรือ RANK () (หาก RDBMS ของคุณรวมไว้)


1

ในโครงการของฉันฉันวางแผนที่จะลองใช้โซลูชันที่คล้ายกับโซลูชันเลขฐานสิบ แต่ใช้ไบต์แทน:

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

แนวคิดก็คือคุณจะไม่มีวันหมดค่าที่เป็นไปได้ระหว่างนี้เพราะและคุณเพียงแค่เพิ่ม a b"\x00"ไปยังระเบียนที่เกี่ยวข้องหากคุณต้องการค่าเพิ่มเติม (intไม่มีขีด จำกัด ใน Python 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"เป็น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']

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 อย่างเสมอ วิธีเดียวที่จะรับประกันได้ว่าจะใช้จำนวนเต็มต่อเนื่องกันตามลำดับ การใช้รายการที่เชื่อมโยงปลอมหรือตัวเลขทศนิยมหรือจำนวนเต็มกับช่องว่างจะไม่ทำให้คุณรับรองคุณสมบัตินี้ ในกรณีเหล่านี้วิธีเดียวที่จะได้เพลงที่ n คือการจัดเรียงตารางทั้งหมดและรับระเบียนที่ n

และนี่คือสิ่งที่ง่ายกว่าที่คุณคิด มันง่ายที่จะเข้าใจว่าคุณต้องการทำอะไรเพื่อสร้างคำแถลงการปรับปรุงสองคำและให้คนอื่นดูคำแถลงการปรับปรุงสองคำเหล่านั้นและรู้ว่ากำลังทำอะไรอยู่

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.