วิธีจัดเก็บข้อมูลที่สั่งซื้อในฐานข้อมูลเชิงสัมพันธ์


20

ฉันพยายามที่จะเข้าใจวิธีการจัดเก็บข้อมูลที่สั่งซื้ออย่างถูกต้องในฐานข้อมูลเชิงสัมพันธ์

ตัวอย่าง:

สมมติว่าฉันมีเพลย์ลิสต์ประกอบด้วยเพลง ภายในฐานข้อมูลเชิงสัมพันธ์ของฉันฉันมีสารบัญPlaylistsประกอบด้วยข้อมูลเมตาบางส่วน (ชื่อผู้สร้าง ฯลฯ ) ฉันยังมีตารางที่เรียกว่าSongsที่มีข้อมูลplaylist_idรวมถึงข้อมูลเฉพาะเพลง (ชื่อศิลปินระยะเวลา ฯลฯ )

ตามค่าเริ่มต้นเมื่อมีการเพิ่มเพลงใหม่ลงในเพลย์ลิสต์เพลงจะถูกต่อท้าย เมื่อสั่งซื้อบน Song-ID (น้อยไปหามาก) คำสั่งซื้อจะเป็นลำดับของการเพิ่ม แต่ถ้าหากผู้ใช้สามารถสั่งซื้อเพลงในรายการเพลงได้อีกครั้ง

ฉันคิดไอเดียสองสามข้อแต่ละข้อมีข้อดีและข้อเสีย:

  1. คอลัมน์เรียกว่าorderซึ่งเป็นจำนวนเต็ม เมื่อเพลงถูกย้ายลำดับของเพลงทั้งหมดระหว่างตำแหน่งเก่าและตำแหน่งใหม่จะเปลี่ยนไปเพื่อให้สอดคล้องกับการเปลี่ยนแปลง ข้อเสียของเรื่องนี้คือต้องมีการค้นหาจำนวนมากในแต่ละครั้งที่มีการย้ายเพลงและอัลกอริทึมการย้ายนั้นไม่สำคัญกับตัวเลือกอื่น ๆ
  2. คอลัมน์ที่เรียกว่าorderซึ่งเป็นทศนิยม ( NUMERIC) เมื่อเพลงถูกย้ายมันจะถูกกำหนดค่าจุดลอยตัวระหว่างตัวเลขสองตัวที่อยู่ติดกัน ข้อเสียเปรียบ: เขตข้อมูลทศนิยมใช้เนื้อที่มากขึ้นและอาจเป็นไปได้ที่จะใช้ความแม่นยำจนหมดเว้นแต่จะได้รับการดูแลเพื่อกระจายช่วงใหม่หลังจากการเปลี่ยนแปลงทุกครั้ง
  3. อีกวิธีหนึ่งก็คือการมีpreviousและnextฟิลด์ที่อ้างอิงเพลงอื่น ๆ (หรือเป็น NULL ในกรณีของเพลงแรก, resp. เพลงสุดท้ายในเพลย์ลิสต์ในขณะนี้โดยทั่วไปคุณสร้างรายการที่ลิงก์ ) ข้อเสียเปรียบ: ข้อความค้นหาเช่น 'find Xth Song ในรายการ' ไม่ใช่เวลาคงที่อีกต่อไป แต่จะเป็นเวลาเชิงเส้นแทน

ขั้นตอนใดที่ใช้บ่อยที่สุดในการปฏิบัติ? ขั้นตอนใดที่เร็วที่สุดสำหรับฐานข้อมูลขนาดกลางถึงขนาดใหญ่ มีวิธีอื่นอีกไหมในการเก็บเรื่องนี้?

แก้ไข:เพื่อความง่ายในตัวอย่างเพลงเป็นของเพลย์ลิสต์เดียวเท่านั้น (ความสัมพันธ์แบบหนึ่งต่อหนึ่ง) แน่นอนเราสามารถใช้ Junction Table ได้ดังนั้นรายการเพลงเป็นความสัมพันธ์แบบหลายต่อหลายคน (และใช้หนึ่งในกลยุทธ์ด้านบนในตารางนั้น)


1
คุณสามารถใช้ตัวเลือกที่หนึ่ง (สั่งซื้อเป็นจำนวนเต็ม) ด้วย 100 ขั้นตอน จากนั้นคุณไม่จำเป็นต้องสั่งซื้อใหม่หากคุณย้ายเพลงหนึ่งเพลงเพียงแค่ใช้ค่าระหว่าง 100 เพลงคุณอาจจำเป็นต้องมีการกำหนดหมายเลขใหม่เพื่อให้มีช่องว่างระหว่างเพลงอีกครั้ง
knut

4
"ข้อเสียเปรียบของเรื่องนี้คือต้องมีการค้นหาจำนวนมากในแต่ละครั้งที่มีการย้ายเพลง"?! - update songorder set order = order - 1 where order >= 12 & order <= 42; update songorder set order = 42 where id = 123;- นั่นคือสองอัปเดต - ไม่สามสิบ สามถ้าคุณต้องการที่จะนำข้อ จำกัด ที่ไม่ซ้ำกันในการสั่งซื้อ

2
ใช้ตัวเลือกที่หนึ่งเว้นแต่คุณจะทราบความจริงว่าคุณต้องการอย่างอื่น โปรแกรมเมอร์ปัญหาหนึ่งที่พบเจอกับฐานข้อมูลใหม่ไม่เข้าใจว่าฐานข้อมูลนั้นดีมากในเรื่องแบบนี้ อย่ากลัวที่จะทำให้ฐานข้อมูลของคุณทำงาน
GrandmasterB

1
Queries like 'find the Xth Song in the list' are no longer constant-timeเป็นจริงสำหรับตัวเลือก 2
Doc Brown

2
@MikeNakis: ดูเหมือนว่าจะมีราคาแพง แต่งานทั้งหมดกำลังทำอยู่บนเซิร์ฟเวอร์ซึ่งมักจะได้รับการปรับให้เหมาะกับงานประเภทนี้ ฉันจะไม่ใช้เทคนิคนี้บนโต๊ะที่มีแถวเป็นล้าน ๆ แถว แต่ฉันจะไม่ลดราคาให้กับตารางที่มีเพียงสองพันเท่านั้น
TMN

คำตอบ:


29

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

พิจารณา:

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

คุณมีสองข้อความที่เตรียมไว้ซึ่งคุณสามารถนำมาใช้ซ้ำได้อย่างมีประสิทธิภาพมาก

สิ่งนี้ให้ประโยชน์ที่สำคัญบางอย่าง - ลำดับของตารางเป็นสิ่งที่คุณสามารถให้เหตุผลได้ เพลงที่สามมีorder3 อย่างเสมอ วิธีเดียวที่จะรับประกันได้ว่าจะใช้จำนวนเต็มต่อเนื่องกันตามลำดับ การใช้รายการที่เชื่อมโยงปลอมหรือตัวเลขทศนิยมหรือจำนวนเต็มกับช่องว่างจะไม่ทำให้คุณรับรองคุณสมบัตินี้ ในกรณีเหล่านี้วิธีเดียวที่จะได้เพลงที่ n คือการจัดเรียงตารางทั้งหมดและรับระเบียนที่ n

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


2
ฉันเริ่มชอบวิธีนี้
Mike Nakis

2
@ MikeNakis ทำงานได้ดี นอกจากนี้ยังมีต้นไม้ไบนารีที่อยู่บนพื้นฐานความคิดที่คล้ายกัน - The ต้นไม้ preorder การแก้ไข ใช้เวลาเพิ่มอีกนิดในการทำให้หัวของคุณเดินไปรอบ ๆ แต่มันจะช่วยให้คุณทำแบบสอบถามที่ดีมากสำหรับข้อมูลลำดับชั้น ฉันไม่เคยมีปัญหาเรื่องประสิทธิภาพกับมันแม้แต่ในต้นไม้ใหญ่ ความสามารถในการให้เหตุผลเกี่ยวกับรหัสเป็นสิ่งที่ฉันให้ความสำคัญเป็นอย่างมากจนกระทั่งมันแสดงให้เห็นว่ารหัสแบบง่าย ๆ ขาดประสิทธิภาพที่จำเป็น

จะมีปัญหาใด ๆ กับการใช้งานorderเนื่องจากorder byเป็นคำสำคัญหรือไม่
kojow7

@ kojow7, ถ้าเขตข้อมูลของคุณมีชื่อที่ขัดแย้งกับคำหลัก, คุณควรใส่เครื่องหมายขีดใน "เครื่องหมาย"
Andri

วิธีการนี้เหมาะสม แต่เป็นวิธีที่ดีที่สุดในการรับorderค่าเมื่อเพิ่มเพลงใหม่ลงในเพลย์ลิสต์ สมมติว่าเป็นเพลงที่ 9 มีวิธีใดที่ดีกว่าในการแทรก 9 ลงในorderเพลงCOUNTก่อนที่จะเพิ่มการบันทึกหรือไม่
delashum

3

ก่อนอื่นมันไม่ชัดเจนจากคำอธิบายของคุณเกี่ยวกับสิ่งที่คุณทำ แต่คุณต้องมีPlaylistSongsตารางที่ประกอบด้วย a PlaylistIdและ a SongIdซึ่งอธิบายว่าเพลงใดเป็นของเพลย์ลิสต์ใด

อยู่ในตารางนี้ซึ่งคุณต้องเพิ่มข้อมูลการสั่งซื้อ

กลไกที่ฉันชอบคือตัวเลขจริง ฉันเพิ่งติดตั้งมันเมื่อไม่นานมานี้และทำงานได้อย่างมีเสน่ห์ เมื่อคุณต้องการย้ายเพลงไปยังตำแหน่งที่ต้องการคุณจะคำนวณOrderingค่าใหม่เป็นค่าเฉลี่ยของOrderingค่าของเพลงก่อนหน้าและเพลงถัดไป หากคุณใช้จำนวนจริง 64- บิตคุณจะหมดความแม่นยำในเวลาเดียวกับที่นรกจะหยุดลง แต่ถ้าคุณเขียนซอฟต์แวร์เพื่อลูกหลานคุณควรพิจารณากำหนดOrderingค่าจำนวนเต็มกลมที่ดีให้กับเพลงทั้งหมดในแต่ละเพลง เพลย์ลิสต์ทุก ๆ ครั้ง

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

ชั้นเป็นParameterTemplate(สิ่งที่ไม่ได้ถาม!) ActivityTemplateวิธีการที่ได้รับรายชื่อของแม่แบบพารามิเตอร์ที่เป็นแม่แบบนี้จากแม่ของมัน (ไม่ว่าจะถามอะไร!) รหัสนี้มีการป้องกันความแม่นยำ ตัวหารใช้สำหรับการทดสอบ: การทดสอบหน่วยใช้ตัวหารขนาดใหญ่เพื่อที่จะหมดความแม่นยำอย่างรวดเร็วและทำให้รหัสการป้องกันความแม่นยำ วิธีที่สองเป็นแบบสาธารณะและ "สำหรับใช้ภายในเท่านั้นอย่าเรียกใช้" เพื่อให้โค้ดทดสอบสามารถเรียกใช้ได้ (มันไม่อาจจะแพคเกจและเอกชนเนื่องจากรหัสการทดสอบของฉันไม่ได้อยู่ในแพคเกจเดียวกับรหัสมันทดสอบ.) ซึ่งเป็นผู้ควบคุมข้อมูลการสั่งซื้อที่เรียกว่าOrdering, เข้าถึงได้ผ่านทางและgetOrdering() setOrdering()คุณไม่เห็น SQL ใด ๆ เพราะฉันใช้การทำแผนที่วัตถุเชิงสัมพันธ์ผ่านทางไฮเบอร์เนต

/**
 * Moves this {@link ParameterTemplate} to the given index in the list of {@link ParameterTemplate}s of the parent {@link ActivityTemplate}.
 *
 * The index must be greater than or equal to zero, and less than or equal to the number of entries in the list.  Specifying an index of zero will move this item to the top of
 * the list. Specifying an index which is equal to the number of entries will move this item to the end of the list.  Any other index will move this item to the position
 * specified, also moving other items in the list as necessary. The given index cannot be equal to the current index of the item, nor can it be equal to the current index plus
 * one.  If the given index is below the current index of the item, then the item will be moved so that its new index will be equal to the given index.  If the given index is
 * above the current index, then the new index of the item will be the given index minus one.
 *
 * NOTE: this method flushes the persistor and refreshes the parent node so as to guarantee that the changes will be immediately visible in the list of {@link
 * ParameterTemplate}s of the parent {@link ActivityTemplate}.
 *
 * @param toIndex the desired new index of this {@link ParameterTemplate} in the list of {@link ParameterTemplate}s of the parent {@link ActivityTemplate}.
 */
public void moveAt( int toIndex )
{
    moveAt( toIndex, 2.0 );
}

/**
 * For internal use only; do not invoke.
 */
public boolean moveAt( int toIndex, double divisor )
{
    MutableList<ParameterTemplate<?>> parameterTemplates = getLogicDomain().getMutableCollections().newArrayList();
    parameterTemplates.addAll( getParentActivityTemplate().getParameterTemplates() );
    assert parameterTemplates.getLength() >= 1; //guaranteed since at the very least, this parameter template must be in the list.
    int fromIndex = parameterTemplates.indexOf( this );
    assert 0 <= toIndex;
    assert toIndex <= parameterTemplates.getLength();
    assert 0 <= fromIndex;
    assert fromIndex < parameterTemplates.getLength();
    assert fromIndex != toIndex;
    assert fromIndex != toIndex - 1;

    double order;
    if( toIndex == 0 )
    {
        order = parameterTemplates.fetchFirstElement().getOrdering() - 1.0;
    }
    else if( toIndex == parameterTemplates.getLength() )
    {
        order = parameterTemplates.fetchLastElement().getOrdering() + 1.0;
    }
    else
    {
        double prevOrder = parameterTemplates.get( toIndex - 1 ).getOrdering();
        parameterTemplates.moveAt( fromIndex, toIndex );
        double nextOrder = parameterTemplates.get( toIndex + (toIndex > fromIndex ? 0 : 1) ).getOrdering();
        assert prevOrder <= nextOrder;
        order = (prevOrder + nextOrder) / divisor;
        if( order <= prevOrder || order >= nextOrder ) //if the accuracy of the double has been exceeded
        {
            parameterTemplates.clear();
            parameterTemplates.addAll( getParentActivityTemplate().getParameterTemplates() );
            for( int i = 0; i < parameterTemplates.getLength(); i++ )
                parameterTemplates.get( i ).setOrdering( i * 1.0 );
            rocs3dDomain.getPersistor().flush();
            rocs3dDomain.getPersistor().refresh( getParentActivityTemplate() );
            moveAt( toIndex );
            return true;
        }
    }
    setOrdering( order );
    rocs3dDomain.getPersistor().flush();
    rocs3dDomain.getPersistor().refresh( getParentActivityTemplate() );
    assert getParentActivityTemplate().getParameterTemplates().indexOf( this ) == (toIndex > fromIndex ? toIndex - 1 : toIndex);
    return false;
}

ฉันจะใช้การเรียงจำนวนเต็มและถ้าฉันรู้สึกว่าการเรียงลำดับใหม่มีราคาแพงเกินไปฉันจะลดจำนวนการเรียงลำดับใหม่โดยการกระโดดแต่ละครั้งด้วย X โดยที่ X คือจำนวนที่ฉันต้องลดการเรียงลำดับใหม่โดยบอกว่า 20 ซึ่ง ควรจะดีในฐานะผู้เริ่มต้น
Warren P

1
@WarrenP ใช่ฉันรู้ว่ามันสามารถทำได้ด้วยวิธีนี้นั่นเป็นเหตุผลว่าทำไมฉันจึงเรียกวิธีนี้ว่า "วิธีที่ฉันโปรดปราน" แทนที่จะเป็น "วิธีที่ดีที่สุด" หรือ "หนึ่ง"
Mike Nakis

0

สิ่งที่ได้ผลสำหรับฉันสำหรับรายการเล็ก ๆ ในลำดับ 100 รายการคือการใช้วิธีไฮบริด:

  1. คอลัมน์เรียงลำดับทศนิยม แต่มีความแม่นยำเพียงพอที่จะเก็บความแตกต่าง 0.5 (เช่นทศนิยม (8,2) หรือบางอย่าง)
  2. เมื่อเรียงลำดับให้คว้า PKs ของแถวด้านบนและด้านล่างที่แถวปัจจุบันเพิ่งถูกย้ายไปหากมีอยู่ (คุณไม่มีแถวด้านบนหากคุณย้ายรายการไปยังตำแหน่งแรกเช่น)
  3. โพสต์ PKs ของแถวปัจจุบันก่อนหน้าและถัดไปไปยังเซิร์ฟเวอร์เพื่อดำเนินการเรียงลำดับ
  4. หากคุณมีแถวก่อนหน้าให้ตั้งตำแหน่งของแถวปัจจุบันเป็น prev + 0.5 หากคุณมีเพียงชุดถัดไปให้กำหนดตำแหน่งของแถวปัจจุบันเป็นถัดไป - 0.5
  5. ต่อไปฉันมี Stored proc ที่อัพเดตตำแหน่งทั้งหมดโดยใช้ฟังก์ชัน SQL Server Row_Number เรียงลำดับโดยเรียงลำดับใหม่ สิ่งนี้จะแปลงการเรียงลำดับจาก 1,1.5,2,3,4,6 เป็น 1,2,3,4,5,6 เนื่องจากฟังก์ชัน row_number จะให้เลขจำนวนเต็ม

ดังนั้นคุณจะได้คำสั่งจำนวนเต็มโดยไม่มีช่องว่างเก็บไว้ในคอลัมน์ทศนิยม มันค่อนข้างสะอาดฉันรู้สึก แต่มันอาจไม่ขยายขนาดได้ดีมากเมื่อคุณมีหลายแสนแถวที่คุณจำเป็นต้องอัปเดตทั้งหมดในครั้งเดียว แต่ถ้าคุณทำทำไมคุณถึงใช้การเรียงลำดับที่ผู้ใช้กำหนดตั้งแต่แรก (หมายเหตุ: หากคุณมีตารางขนาดใหญ่ที่มีผู้ใช้หลายล้านคน แต่ผู้ใช้แต่ละคนมีเพียงไม่กี่ร้อยรายการที่จะเรียงลำดับคุณสามารถใช้วิธีการด้านบนได้เนื่องจากคุณจะใช้ส่วนคำสั่งต่อไปเพื่อ จำกัด การเปลี่ยนแปลงเพียงผู้ใช้เพียงคนเดียว )

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