Sipariş edilen bilgiler İlişkisel Veritabanında nasıl saklanır


20

Düzgün ilişkisel bir veritabanında sipariş bilgileri depolamak anlamaya çalışıyorum.

Bir örnek:

Diyelim ki Şarkılardan oluşan bir Çalma Listem var. İlişkisel Veritabanımda, Playlistsbazı meta veriler (ad, yaratıcı, vb.) İçeren bir tablo var . Ayrıca şarkı Songsiçeren bir playlist_idbilgi (ad, sanatçı, süre, vb) yanı sıra , içeren bir tablo var .

Varsayılan olarak, bir Çalma Listesine yeni bir Şarkı eklendiğinde sonuna eklenir. Şarkı kimliğine (artan) sipariş verirken, sipariş ekleme sırası olacaktır. Peki ya bir kullanıcı çalma listesindeki şarkıları yeniden sıralayabilirse?

Her biri avantajları ve dezavantajları olan birkaç fikir buldum:

  1. Adlıorder bir tamsayı olan sütun . Bir şarkı taşındığında, eski ve yeni konumları arasındaki tüm şarkıların sırası, değişikliği yansıtacak şekilde değiştirilir. Bunun dezavantajı, bir şarkı her taşındığında birçok sorgunun yapılması gerektiğidir ve hareketli algoritma diğer seçeneklerle olduğu kadar önemsiz değildir.
  2. Adı verilen bir sütun ordera,, ondalık ( NUMERIC). Bir şarkı taşındığında, bitişik iki sayı arasında kayan nokta değeri atanır. Dezavantajı: Ondalık alanlar daha fazla yer kaplar ve her birkaç değişiklikten sonra aralığı yeniden dağıtmaya özen gösterilmedikçe, kesinlik azalması mümkün olabilir.
  3. Başka bir yol, diğer Şarkılara atıfta bulunan bir previousve bir nextalana sahip olmaktır . (veya şu anda çalma listesindeki ilk ve son şarkıda NULL'dur; Temel olarak bağlantılı bir liste oluşturursunuz ). Dezavantajı: 'Listedeki X. Şarkıyı Bul' gibi sorgular artık sabit zaman değil, doğrusal zaman.

Bu prosedürlerden hangisi pratikte en sık kullanılır? Bu yordamlardan hangisi orta ve büyük veritabanlarında en hızlıdır? Bunu elde etmenin başka yolları var mı?

DÜZENLEME: Basitçe söylemek gerekirse, örnekte bir Şarkı yalnızca bir Çalma Listesine aittir (çoktan bire ilişki). Tabii ki, bir Kavşak Tablosu da kullanılabilir, bu yüzden şarkı çalma listesi çoktan çoğa bir ilişkidir (ve yukarıdaki stratejilerden birini bu tabloya uygulayın).


1
100 adımla birinci seçeneği (Tamsayı olarak sipariş edin) kullanabilirsiniz. Sonra bir şarkıyı taşırsanız yeniden sıralamaya gerek yoktur, sadece 100 arasında bir değer alın. Zaman zaman şarkılar arasındaki boşlukları tekrar almak için yeni bir yeniden numaralandırmaya ihtiyacınız olabilir.
knut

4
"Bunun dezavantajı, bir şarkı her taşındığında çok fazla sorgu yapılması gerektiğidir"?! - update songorder set order = order - 1 where order >= 12 & order <= 42; update songorder set order = 42 where id = 123;- bu iki güncelleme - otuz değil. Sipariş üzerine benzersiz bir kısıtlama koymak istiyorsanız üç.

2
Başka bir şeye ihtiyacınız olduğunu bilmiyorsanız birinci seçeneği kullanın. Veritabanlarının karşılaştığı yeni problem programcıları, bu tür şeylerde veritabanlarının çok ama çok iyi olduğunu anlamak değildir. Db'nizi işe koymaktan korkmayın.
GrandmasterB

1
Queries like 'find the Xth Song in the list' are no longer constant-timeseçenek 2 için de geçerlidir.
Doc Brown

2
@MikeNakis: Pahalı görünüyor, ancak tüm işler sunucuda yapılıyor, bu tür işler için (genellikle) optimize ediliyor. Bu tekniği milyonlarca satır içeren bir tabloda kullanmazdım, ama sadece birkaç bin kişilik bir tablo için indirim yapmazdım.
TMN

Yanıtlar:


29

Veritabanları belirli şeyler için optimize edilmiştir. Çok sayıda satırı hızlı bir şekilde güncellemek bunlardan biridir. Bu özellikle veritabanının çalışmasına izin verdiğinizde geçerli olur.

Düşünmek:

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

Ve Beat Itsonuna kadar gitmek istersen, iki sorgunuz olur:

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

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

Ve bu kadar. Bu, çok büyük sayılarla çok iyi ölçeklenir. Veritabanınızdaki varsayımsal bir çalma listesine birkaç bin şarkı koymayı deneyin ve bir şarkıyı bir konumdan diğerine taşımanın ne kadar sürdüğünü görün. Bunların çok standartlaştırılmış formları olduğundan:

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

update table
  set order = ?
  where song = ?

Çok verimli bir şekilde tekrar kullanabileceğiniz iki hazır ifadeniz var.

Bu bazı önemli avantajlar sağlar - tablonun sırası aklınıza gelebilecek bir şeydir. Üçüncü şarkı orderher zaman 3'tür. Bunu garanti etmenin tek yolu, ardışık tamsayıları sipariş olarak kullanmaktır. Yalancı bağlantılı listeler veya ondalık sayı veya boşluklu tam sayı kullanmak bu özelliği garanti etmenize izin vermez; bu durumlarda n. şarkıyı almanın tek yolu tüm tabloyu sıralamak ve n. kaydı elde etmektir.

Ve gerçekten, bu sandığınızdan çok daha kolay. Ne yapmak istediğinizi anlamak, iki güncelleme ifadesi oluşturmak ve diğer kişilerin bu iki güncelleme ifadesine bakmak ve ne yapıldığını anlamak kolaydır.


2
Bu yaklaşımı sevmeye başladım.
Mike Nakis

2
@MikeNakis iyi çalışıyor. Benzer bir fikre dayanan ikili bir ağaç da var - değiştirilmiş ön sipariş ağacı . Başınızı döndürmek biraz daha zaman alır, ancak hiyerarşik veriler için çok güzel sorgular yapmanızı sağlar. Büyük ağaçlarda bile performans sorunlarım olmadı. Kod hakkında akıl yürütmek, basit kodun gereken performansa sahip olmadığı (ve sadece aşırı durumlarda olduğu) gösterilinceye kadar büyük önem verdiğim bir şeydir.

Kullanarak herhangi bir sorun olacak mı orderberi order byönemli bir kelimedir?
kojow7

@ kojow7, alanlarınızın anahtar kelimelerle çakışan adları varsa, bunları "" "işaretlerine sarmalısınız.
Andri

Bu yaklaşım mantıklıdır, ancak orderbir çalma listesine yeni bir şarkı eklerken değer elde etmenin en iyi yolu nedir ? İçine 9 eklemek için daha iyi bir yol yoktur, bu 9 şarkı söyle orderbir yapmaktan daha COUNTkaydı ekleme öncesinde?
delashum

3

Her şeyden önce, ne yaptığınızla ilgili açıklamanız net değil, ancak hangi şarkıların hangi çalma listelerine ait olduğunu açıklayan PlaylistSongsa PlaylistIdve a içeren bir tabloya ihtiyacınız var SongId.

Bu tabloda sipariş bilgileri eklemelisiniz.

En sevdiğim mekanizma gerçek sayılarla. Son zamanlarda uyguladım ve bir cazibe gibi çalıştı. Bir şarkıyı belirli bir konuma taşımak istediğinizde, yeni Orderingdeğerini Orderingönceki şarkının ve sonraki şarkının değerlerinin ortalaması olarak hesaplarsınız . 64 bit gerçek bir sayı kullanırsanız, cehennemin donacağıyla aynı zamanda kesinlikten kurtulacaksınız, ancak yazılımınızı posterity için gerçekten yazıyorsanız, her bir şarkıdaki Orderingtüm şarkılar için güzel yuvarlak tamsayı değerlerini yeniden atamayı düşünün arada bir çalma listesi.

Ek bir bonus olarak, bunu uygulayan yazdığım kod. Tabii ki olduğu gibi kullanamazsınız ve şu anda sizin için sterilize etmek benim için çok fazla iş olurdu, bu yüzden sadece ondan fikir almak için gönderiyorum.

Sınıf ParameterTemplate(ne olursa olsun sorma!) Yöntem, bu şablonun üst öğesinden ait olduğu parametre şablonlarının listesini alır ActivityTemplate. (Her neyse, sormayın!) Kod, hassasiyetin tükenmesine karşı biraz koruma içerir. Bölen test için kullanılır: birim testi, hassasiyeti hızlı bir şekilde bitirmek ve böylece hassas koruma kodunu tetiklemek için büyük bir bölücü kullanır. İkinci yöntem herkese açıktır ve "yalnızca dahili kullanım için; çağırmayın", böylece test kodu onu çağırabilir. (Test kodum test ettiği kodla aynı pakette olmadığından pakete özel olamaz.) Siparişi kontrol eden alana çağrılan ve Orderingüzerinden erişilir . Hibernate üzerinden Nesne İlişkisel Eşleme kullandığım için herhangi bir SQL görmüyorsunuz.getOrdering()setOrdering()

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

Bir tamsayı sırası kullanırdım ve yeniden sıralamanın çok pahalı olduğunu hissettiysem, her birinin X ile atlamasını sağlayarak, yeniden sıralama sayısını azaltacağım, burada X, yeniden sıralamayı azaltmak için ihtiyacım olan miktardır, örneğin 20, bir başlangıç ​​kadar iyi olmalı.
Warren P

1
@WarrenP evet, biliyorum, bu şekilde de yapılabilir, bu yüzden "en iyi" veya "bir" yaklaşım yerine bu "en sevdiğim" yaklaşımı çağırdım.
Mike Nakis

0

Benim için işe yarayan, 100 maddelik küçük bir liste için hibrit bir yaklaşım benimsemekti:

  1. Ondalık SortOrder sütunu, ancak 0,5 farkı (yani Ondalık (8,2) vb.)
  2. Sıralama yaparken, varsa mevcut satırın yeni taşındığı yerin üstündeki ve altındaki satırın PK'lerini alın. (Örneğin, öğeyi ilk konuma taşırsanız yukarıdaki satırınız olmaz)
  3. Sıralamayı gerçekleştirmek için geçerli, önceki ve sonraki satırın PK'larını sunucuya gönderin.
  4. Bir önceki satırınız varsa, geçerli satırın konumunu önceki + 0,5 olarak ayarlayın. Yalnızca bir sonrakiniz varsa, geçerli satırın konumunu sonraki - 0.5'e ayarlayın.
  5. Sonra yeni sıralama düzeni tarafından sipariş, SQL Server Row_Number işlevini kullanarak tüm konumları güncelleştiren bir saklı proc var. Bu, sıralamayı 1,1,5,2,3,4,6'dan 1,2,3,4,5,6'ya dönüştürür, çünkü row_number işlevi tamsayı sıraları verir.

Böylece, ondalık sütunda saklanan boşluk içermeyen bir tamsayı sırası ile sonuçlanırsınız. Oldukça temiz, hissediyorum. Ancak, güncellemeniz gereken yüz binlerce sıraya sahip olduğunuzda, hepsi aynı anda çok iyi ölçeklenmeyebilir. Ama eğer öyleyse, neden ilk etapta kullanıcı tanımlı bir sıralama kullanıyorsunuz? (Not: Milyonlarca kullanıcılı büyük bir tablonuz varsa, ancak her kullanıcının sıralamak için yalnızca birkaç yüz öğesi varsa, yukarıdaki yaklaşımı kullanabilirsiniz, çünkü değişiklikleri tek bir kullanıcıyla sınırlamak için yine de bir where cümlesi kullanacaksınız. )

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.