Cách lưu trữ thông tin theo thứ tự trong Cơ sở dữ liệu quan hệ


20

Tôi đang cố gắng hiểu làm thế nào để lưu trữ đúng thông tin theo thứ tự trong cơ sở dữ liệu quan hệ.

Một ví dụ:

Nói rằng tôi có một Playlist, bao gồm các bài hát. Trong Cơ sở dữ liệu quan hệ của tôi, tôi có một bảng Playlists, chứa một số siêu dữ liệu (tên, người tạo, v.v.). Tôi cũng có một bảng được gọi Songs, chứa playlist_idthông tin cụ thể về bài hát (tên, nghệ sĩ, thời lượng, v.v.).

Theo mặc định, khi một Bài hát mới được thêm vào Danh sách phát, nó sẽ được thêm vào cuối. Khi đặt hàng trên Song-ID (tăng dần), thứ tự sẽ là thứ tự bổ sung. Nhưng nếu người dùng có thể đặt hàng lại các bài hát trong danh sách nhạc thì sao?

Tôi đã đưa ra một vài ý tưởng, mỗi ý tưởng đều có ưu điểm và nhược điểm:

  1. Một cột được gọi order, đó là một số nguyên . Khi một bài hát được di chuyển, thứ tự của tất cả các bài hát giữa vị trí cũ và mới được thay đổi, để phản ánh sự thay đổi. Hạn chế của điều này là rất nhiều truy vấn cần được thực hiện mỗi khi bài hát được di chuyển và thuật toán di chuyển không tầm thường như với các tùy chọn khác.
  2. Một cột được gọi order, đó là một số thập phân ( NUMERIC). Khi một bài hát được di chuyển, nó được gán giá trị dấu phẩy động giữa hai số liền kề. Nhược điểm: Các trường thập phân chiếm nhiều không gian hơn và có thể hết độ chính xác, trừ khi được chăm sóc để phân phối lại phạm vi sau mỗi vài thay đổi.
  3. Một cách khác là có một previousvà một nextlĩnh vực tham khảo các Bài hát khác. (hoặc là NULL trong trường hợp bài hát đầu tiên, tương ứng trong danh sách phát ngay bây giờ; Về cơ bản, bạn tạo một danh sách liên kết ). Nhược điểm: Các truy vấn như 'tìm bài hát thứ X trong danh sách' không còn là thời gian không đổi, mà thay vào đó là thời gian tuyến tính.

Những thủ tục nào thường được sử dụng nhất trong thực tế? Những thủ tục nào là nhanh nhất trên cơ sở dữ liệu trung bình đến lớn? Có cách nào khác để lưu trữ điều này?

EDIT: Để đơn giản, trong ví dụ, Bài hát chỉ thuộc về một Playlist (mối quan hệ nhiều-một). Tất nhiên, người ta cũng có thể sử dụng Bảng kết nối để danh sách bài hát là mối quan hệ nhiều-nhiều (và áp dụng một trong các chiến lược trên trên bảng đó).


1
Bạn có thể sử dụng tùy chọn một (đặt hàng dưới dạng Số nguyên) với 100 bước. Sau đó, bạn không cần phải đặt hàng lại nếu bạn di chuyển một bài hát, chỉ cần lấy một giá trị trong khoảng 100. Thỉnh thoảng bạn có thể cần phải đánh số lại mới để có được khoảng cách giữa các bài hát.
knut 8/12/2015

4
"Hạn chế của điều này là rất nhiều truy vấn cần được thực hiện mỗi khi bài hát được di chuyển"?! - update songorder set order = order - 1 where order >= 12 & order <= 42; update songorder set order = 42 where id = 123;- đó là hai bản cập nhật - không phải ba mươi. Ba nếu bạn muốn đặt một ràng buộc duy nhất theo thứ tự.

2
Sử dụng tùy chọn một trừ khi bạn biết thực tế bạn cần thứ gì khác. Một vấn đề mà các lập trình viên mới gặp phải đối với cơ sở dữ liệu là không hiểu rằng cơ sở dữ liệu rất, rất tốt về loại điều này. Đừng ngại đặt db của bạn để làm việc.
GrandmasterB

1
Queries like 'find the Xth Song in the list' are no longer constant-timecũng đúng với tùy chọn 2.
Doc Brown

2
@MikeNakis: Có vẻ tốn kém, nhưng tất cả công việc đang được thực hiện trên máy chủ, thường được tối ưu hóa cho loại công việc này. Tôi sẽ không sử dụng kỹ thuật này trên một bảng có hàng triệu hàng, nhưng tôi sẽ không giảm giá cho một bảng chỉ có vài nghìn.
TMN

Câu trả lời:


29

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.


2
Tôi bắt đầu thích cách tiếp cận này.
Mike Nakis

2
@MikeNakis nó hoạt động tốt. Ngoài ra còn có một cây nhị phân dựa trên một ý tưởng tương tự - cây preorder đã sửa đổi . Phải mất thêm một chút để có được đầu óc của bạn, nhưng nó cho phép bạn thực hiện một số truy vấn rất hay cho dữ liệu phân cấp. Tôi chưa bao giờ gặp vấn đề về hiệu suất với nó, ngay cả trong những cây lớn. Có thể suy luận về mã là điều tôi rất chú trọng cho đến khi nó được chỉ ra rằng mã đơn giản thiếu hiệu năng cần thiết (và điều đó chỉ xảy ra trong các tình huống cực đoan).

Sẽ có bất kỳ vấn đề với việc sử dụng orderorder bylà một từ khóa?
kojow7

@ kojow7, nếu các trường của bạn có tên xung đột với từ khóa, bạn nên bọc chúng trong dấu "" ".
Andri

Cách tiếp cận này có ý nghĩa, nhưng cách tốt nhất để có được ordergiá trị khi thêm một bài hát mới vào danh sách phát. Nói đó là bài hát thứ 9, có cách nào tốt hơn để chèn 9 vào orderhơn là làm COUNTtrước khi thêm bản ghi không?
delashum

3

Trước hết, không rõ từ mô tả của bạn về những gì bạn đã làm, nhưng bạn cần một PlaylistSongsbảng chứa a PlaylistIdvà a SongId, mô tả những bài hát thuộc danh sách phát nào.

Trong bảng này, bạn phải thêm thông tin đặt hàng.

Cơ chế yêu thích của tôi là với những con số thực. Tôi đã thực hiện nó gần đây, và nó hoạt động như một cơ duyên. Khi bạn muốn di chuyển một bài hát đến một vị trí cụ thể, bạn tính Orderinggiá trị mới của nó là giá trị trung bình của các Orderinggiá trị của bài hát trước đó và bài hát tiếp theo. Nếu bạn sử dụng số thực 64 bit, bạn sẽ hết độ chính xác cùng lúc với địa ngục sẽ đóng băng, nhưng nếu bạn thực sự viết phần mềm của mình cho hậu thế, thì hãy xem xét việc gán lại các Orderinggiá trị số nguyên được làm tròn đẹp cho tất cả các bài hát trong mỗi bài hát danh sách nhạc mỗi lần trong một thời gian.

Là một phần thưởng bổ sung, đây là đoạn mã mà tôi đã viết để thực hiện điều này. Tất nhiên bạn không thể sử dụng nó như hiện tại và nó sẽ là quá nhiều công việc cho tôi ngay bây giờ để vệ sinh nó cho bạn, vì vậy tôi chỉ đăng nó cho bạn để lấy ý tưởng từ nó.

Lớp này là ParameterTemplate(bất cứ điều gì, đừng hỏi!) Phương thức lấy danh sách các mẫu tham số mà mẫu này thuộc về cha mẹ của nó ActivityTemplate. (Dù thế nào, đừng hỏi!) Mã chứa một số bảo vệ chống lại sự chính xác. Bộ chia được sử dụng để kiểm tra: kiểm tra đơn vị sử dụng số chia lớn để nhanh chóng hết độ chính xác và do đó kích hoạt mã bảo vệ chính xác. Phương thức thứ hai là công khai và "chỉ sử dụng nội bộ; không gọi" để mã kiểm tra có thể gọi nó. (Không thể là gói riêng tư vì mã kiểm tra của tôi không nằm trong cùng gói với mã mà nó kiểm tra.) Trường kiểm soát thứ tự được gọi Ordering, được truy cập qua getOrdering()setOrdering(). Bạn không thấy bất kỳ SQL nào vì tôi đang sử dụng Ánh xạ quan hệ đối tượng thông qua Hibernate.

/**
 * 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;
}

Tôi sẽ sử dụng một thứ tự số nguyên và nếu tôi cảm thấy việc sắp xếp lại quá tốn kém, tôi chỉ cần giảm số lần sắp xếp lại, bằng cách mỗi lần nhảy bằng X, trong đó X là số tiền tôi cần để sắp xếp lại theo thứ tự, giả sử là 20, nên tốt như là một khởi đầu.
Warren P

1
@WarrenP vâng, tôi biết, nó cũng có thể được thực hiện theo cách này, đó là lý do tại sao tôi chỉ gọi phương pháp "yêu thích" này thay vì phương pháp "tốt nhất" hoặc "duy nhất".
Mike Nakis

0

Điều làm việc cho tôi, cho một danh sách nhỏ theo thứ tự 100 mặt hàng là thực hiện một phương pháp lai:

  1. Cột Sắp xếp số thập phân, nhưng chỉ có độ chính xác đủ để lưu 0,5 chênh lệch (tức là số thập phân (8.2) hoặc thứ gì đó).
  2. Khi sắp xếp, lấy các PK của hàng bên trên và bên dưới nơi hàng hiện tại vừa được di chuyển đến, nếu chúng tồn tại. (Ví dụ, bạn sẽ không có hàng ở trên nếu bạn di chuyển mục đến vị trí đầu tiên)
  3. Đăng các PK của hàng hiện tại, trước đó và tiếp theo lên máy chủ để thực hiện sắp xếp.
  4. Nếu bạn có một hàng trước, hãy đặt vị trí của hàng hiện tại thành + 0,5. Nếu bạn chỉ có cái tiếp theo, hãy đặt vị trí của hàng hiện tại thành tiếp theo - 0,5.
  5. Tiếp theo, tôi có một Proc được lưu trữ cập nhật tất cả các vị trí bằng hàm SQL Server Row_Number, sắp xếp theo thứ tự sắp xếp mới. Điều này sẽ chuyển đổi thứ tự từ 1,1,5,2,3,4,6 thành 1,2,3,4,5,6, vì hàm row_number cung cấp cho bạn các số nguyên.

Vì vậy, bạn kết thúc với một thứ tự số nguyên không có khoảng trống, được lưu trữ trong một cột thập phân. Nó khá sạch sẽ, tôi cảm thấy. Nhưng nó có thể không mở rộng quy mô cực kỳ tốt khi bạn có hàng trăm ngàn hàng mà bạn cần cập nhật, tất cả cùng một lúc. Nhưng nếu bạn làm vậy, tại sao bạn lại sử dụng một loại người dùng xác định ở vị trí đầu tiên? (Lưu ý: nếu bạn có một bảng lớn với hàng triệu người dùng nhưng mỗi người dùng chỉ có vài trăm mục để sắp xếp, bạn có thể sử dụng cách tiếp cận trên tốt vì bạn sẽ sử dụng mệnh đề where để hạn chế thay đổi chỉ một người dùng )

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.