ORDER BY ve karışık harf ve sayı dizelerinin karşılaştırılması


9

Genellikle 'doğal olarak' sıralanması gereken sayı ve harflerden oluşan karışık dizeler olan değerler hakkında bazı raporlar yapmamız gerekir. "P7B18" veya "P12B3" gibi şeyler. Dizeler çoğunlukla harf dizileri sonra sayıların dönüşümlü olacağı anlamına gelir. Bununla birlikte, bu segmentlerin sayısı ve her birinin uzunluğu değişebilir.

Bunların sayısal kısımlarının sayısal sırayla sıralanmasını istiyoruz. Açıkçası, ben sadece bu dize değerleri ile doğrudan işlemek ORDER BY, o zaman "P1B" önce "P7B18" önce gelecek, çünkü "P1" "P7" daha erken, ama ben "P7" doğal olarak önce olduğu gibi ters istiyorum "P12".

Ayrıca aralık karşılaştırmaları yapabilmek istiyorum, örneğin @bin < 'P13S6'. Kayan nokta veya negatif sayılarla uğraşmak zorunda değilim; bunlar kesinlikle uğraştığımız negatif olmayan tamsayılar olacaktır. Dize uzunlukları ve segment sayısı, sabit üst sınırlar olmaksızın potansiyel olarak keyfi olabilir.

Bizim durumumuzda, dize muhafazası önemli değildir, ancak bunu harmanlamaya duyarlı bir şekilde yapmanın bir yolu varsa, diğerleri bunu yararlı bulabilir. Tüm bunların en çirkin kısmı, WHEREmaddede hem sipariş verme hem de aralık filtreleme yapabilmek istiyorum .

Bunu C # 'da yapsaydım, oldukça basit bir iş olurdu: alfayı sayısaldan ayırmak, IComparable uygulamak ve temelde bitti. SQL Server, elbette, en azından bildiğim kadarıyla benzer bir işlevsellik sunmuyor gibi görünüyor.

Bu işi yapmak için iyi hileler bilen var mı? IComparable'ı uygulayan ve bunun beklendiği gibi davranmasını sağlayan özel CLR türleri oluşturma konusunda az yayınlanmış bir yetenek var mı? Ben de Stupid XML Tricks karşı değilim (ayrıca bakınız: liste birleştirme) ve ben de sunucuda kullanılabilir CLR regex eşleştirme / ayıklama / yedek sarma işlevleri var.

EDIT: Biraz daha ayrıntılı bir örnek olarak, verilerin böyle bir şey davranmasını istiyorum.

SELECT bin FROM bins ORDER BY bin

bin
--------------------
M7R16L
P8RF6JJ
P16B5
PR7S19
PR7S19L
S2F3
S12F0

yani, dizeleri tüm harflerin veya tüm sayıların jetonlarına ayırın ve sırasıyla alfabetik veya sayısal olarak sıralayın, en soldaki jetonlar en önemli sıralama terimidir. Bahsettiğim gibi, IComparable uygularsanız .NET'te çok kolay, ancak SQL Server'da bu tür şeyleri nasıl yapabileceğinizi (veya yapamıyorsanız) bilmiyorum. Kesinlikle on yıllarca onunla çalıştığım bir şey değil.


Bunu dizeyi bir tamsayıya çevirerek dizinlenmiş hesaplanmış bir sütun türüyle yapabilirsiniz. Öyleyse , o zaman P7B12olabilir P 07 B 12(ASCII aracılığıyla) 80 07 65 12, yani80076512
Philᵀᴹ

Her sayısal bileşeni büyük bir uzunluğa (yani 10 sıfır) dolduran hesaplanmış bir sütun oluşturmanızı öneririm. Biçim oldukça keyfi olduğundan, oldukça büyük bir satır içi ifadeye ihtiyacınız olacak, ancak yapılabilir. Daha sonra bu sütunda istediğiniz yere / tarafından dizine ekleyebilir / sıralayabilirsiniz.
Nick.McDermaid

Lütfen cevabımın en üstüne eklediğim bağlantıya bakınız :)
Solomon Rutzky

1
@srutzky Güzel, oy verdim.
db2

Hey db2: Microsoft'un Connect'ten UserVoice'e geçmesi ve oy sayımını tam olarak tutmaması nedeniyle (bir yorumda yer alıyorlar, ancak buna baktıklarından emin değiller), yeniden oy vermeniz gerekebilir: Destek "doğal sıralama" / Harmanlama seçeneği olarak DIGITSASNUMBERS . Teşekkürler!
Solomon Rutzky

Yanıtlar:


8

Dizelerdeki sayıları gerçek sayılar olarak sıralamak için mantıklı ve etkili bir araç mı istiyorsunuz? Microsoft Connect önerim için oy vermeyi düşünün: Harmanlama seçeneği olarak "doğal sıralama" / DIGITSASNUMBERS desteği


Bunu yapmanın kolay ve yerleşik bir yolu yoktur, ancak işte bir olasılık:

Dizeleri sabit uzunluklu segmentlere yeniden biçimlendirerek normalleştirin:

  • Tür bir sıralama sütunu oluşturun VARCHAR(50) COLLATE Latin1_General_100_BIN2. Maksimum uzunluk 50'nin maksimum segment sayısına ve bunların potansiyel maksimum uzunluklarına göre ayarlanması gerekebilir.
  • Normalleştirme uygulama katmanında daha verimli bir şekilde yapılabilse de, bunu bir T-SQL UDF kullanarak veritabanında işlemek, skaler UDF'yi bir AFTER [or FOR] INSERT, UPDATETetikleyiciye yerleştirmenize izin verecek, böylece tüm kayıtlar için değeri düzgün bir şekilde ayarlayacağınız garanti edilecektir. Elbette, skaler UDF SQLCLR yoluyla da ele alınabilir, ancak hangisinin gerçekten daha verimli olduğunu belirlemek için test edilmesi gerekir. **
  • UDF (T-SQL veya SQLCLR'de olmasına bakılmaksızın):
    • Her karakteri okuyarak ve tür alfadan sayısalya veya sayısaldan alfaya geçtiğinde durarak, bilinmeyen sayıda segmenti işleyin.
    • Her segment için, herhangi bir segmentin (veya gelecekteki büyümeyi hesaba katmak için belki maksimum + 1 veya 2) maksimum karaktere / rakama ayarlanmış bir sabit uzunluklu dize döndürmelidir.
    • Alfa segmentleri sola dayalı olmalı ve boşluklarla sağ tarafa doldurulmalıdır.
    • Sayısal segmentler sağa dayalı olmalı ve sıfırlarla sola doldurulmalıdır.
    • Alfa karakterleri karışık harf olarak gelebilir ancak siparişin büyük / küçük harfe duyarlı olmaması gerekiyorsa, UPPER()işlevi tüm segmentlerin nihai sonucuna uygulayın (böylece segment başına değil, yalnızca bir kez yapılması gerekir). Bu, sıralama sütununun ikili harmanlaması göz önüne alındığında uygun sıralama yapılmasına izin verecektir.
  • AFTER INSERT, UPDATESıralama sütununu ayarlamak için UDF'yi çağıran tabloda bir Tetikleyici oluşturun . Performansı artırmak için, UPDATE()bu kod sütununun deyimin SETyan tümcesinde bile UPDATE(yalnızca RETURNyanlışsa) olup olmadığını belirlemek için işlevi kullanın ve sonra yalnızca kod değerinde değişikliklere sahip satırları işlemek için kod sütunundaki INSERTEDve DELETEDsözde tablolarına katılın . COLLATE Latin1_General_100_BIN2Bir değişiklik olup olmadığının belirlenmesinde doğruluk sağlamak için bu JOIN koşulunu belirttiğinizden emin olun .
  • Yeni sıralama sütununda bir Dizin oluşturun.

Misal:

P7B18   -> "P     000007B     000018"
P12B3   -> "P     000012B     000003"
P12B3C8 -> "P     000012B     000003C     000008"

Bu yaklaşımda, aşağıdakileri kullanarak sıralayabilirsiniz:

ORDER BY tbl.SortColumn

Ayrıca, aralık filtreleme yoluyla şunları yapabilirsiniz:

WHERE tbl.SortColumn BETWEEN dbo.MyUDF('P7B18') AND dbo.MyUDF('P12B3')

veya:

DECLARE @RangeStart VARCHAR(50),
        @RangeEnd VARCHAR(50);
SELECT @RangeStart = dbo.MyUDF('P7B18'),
       @RangeEnd = dbo.MyUDF('P12B3');

WHERE tbl.SortColumn BETWEEN @RangeStart AND @RangeEnd

Hem ve ORDER BYhem de WHEREfiltre, SortColumnHarmanlama Önceliği nedeniyle tanımlanan ikili harmanlamayı kullanmalıdır .

Eşitlik karşılaştırmaları hala orijinal değer sütununda yapılacaktır.


Diğer düşünceler:

  • Bir SQLCLR UDT kullanın. Bu, işe yarayabilir, ancak yukarıda açıklanan yaklaşıma kıyasla net bir kazanç sağlayıp sağlamadığı belirsizdir.

    Evet, bir SQLCLR UDT karşılaştırma işleçlerini özel algoritmalarla geçersiz kılabilir. Bu, değerin zaten aynı özel türden başka bir değerle veya örtük olarak dönüştürülmesi gereken bir değerle karşılaştırıldığı durumları işler. Bu , aralık filtresini bir koşulda ele almalıdırWHERE .

    UDT'yi normal bir sütun türü (hesaplanmış bir sütun değil) olarak sıralamakla ilgili olarak, bu yalnızca UDT "bayt sıralıysa" mümkündür. "Bayt sıralı" olmak, UDT'nin (UDT'de tanımlanabilen) ikili temsilinin doğal olarak uygun sırayla sıralandığı anlamına gelir. İkili sunumun, dolgulu, sabit uzunluklu bölümleri olan VARCHAR (50) sütunu için yukarıda açıklanan yaklaşıma benzer şekilde ele alındığı kabul edilir. Veya, ikili gösterimin doğal olarak doğru şekilde sipariş edilmesini sağlamak kolay olmasaydı, UDT'nin düzgün bir şekilde sipariş edilecek bir değer çıkaran bir yöntem veya özelliğini açığa çıkarabilir ve daha sonra PERSISTEDbunun üzerinde bir hesaplanmış sütun oluşturabilirsiniz . yöntem veya özellik. Yöntemin deterministik olması ve olarak işaretlenmesi gerekir IsDeterministic = true.

    Bu yaklaşımın faydaları:

    • "Orijinal değer" alanına gerek yoktur.
    • Verileri eklemek veya değerleri karşılaştırmak için bir UDF çağırmanıza gerek yoktur. ParseUDT yönteminin P7B18değeri aldığını ve dönüştürdüğünü varsayarsak , değerleri doğal olarak olduğu gibi girebilmeniz gerekir P7B18. Ve UDT'de ayarlanan örtük dönüştürme yöntemi ile, WHERE koşulu da sadece P7B18` kullanılmasına izin verecektir.

    Bu yaklaşımın sonuçları:

    • Sütun veri türü olarak bayt sıralı UDT kullanılıyorsa, yalnızca alanın seçilmesi ikili temsili döndürür. Veya PERSISTEDUDT'nin bir özelliği veya yönteminde hesaplanmış bir sütun kullanıyorsanız , özellik veya yöntem tarafından döndürülen temsili alırsınız. Orijinal P7B18değeri istiyorsanız, bu temsili döndürmek için kodlanmış UDT'nin bir yöntemini veya özelliğini çağırmanız gerekir. ToStringYine de yöntemi geçersiz kılmanız gerektiğinden, bunu sağlamak için iyi bir adaydır.
    • İkili gösterimde herhangi bir değişiklik yapmanın ne kadar kolay / zor olacağı belli değil (en azından şu anda bana göre bu kısmı test etmedim). Depolanmış, sıralanabilir temsili değiştirmek, alanın bırakılmasını ve yeniden eklenmesini gerektirebilir. Ayrıca, her iki şekilde de kullanıldığında, UDT içeren Meclisin düşürülmesi başarısız olur, bu nedenle Meclis'te bu UDT'nin dışında başka bir şey olmadığından emin olmak istersiniz. Sen olabilir ALTER ASSEMBLYtanımını değiştirmek için, ancak bu konuda bazı kısıtlamalar vardır.

      Öte yandan, VARCHAR()alan algoritmanın bağlantısı kesilmiş verilerdir, bu nedenle yalnızca sütunun güncellenmesi gerekir. Ve on milyonlarca satır (veya daha fazla) varsa, bu toplu bir yaklaşımla yapılabilir.

  • Bu alfasayısal sınıflandırmayı gerçekten sağlayan ICU kitaplığını uygulayın . Oldukça işlevsel olsa da, kütüphane sadece iki dilde gelir: C / C ++ ve Java. Bu, Visual C ++ ile çalışması için bazı ince ayarlar yapmanız gerekebileceği veya Java kodunun IKVM kullanılarak MSIL'e dönüştürülebilme şansı olmadığı anlamına gelir . Sitede yönetilen kodda erişilebilen bir COM arabirimi sağlayan bir veya iki .NET tarafı projesi var, ancak bir süredir güncellenmediğine ve bunları denemediğime inanıyorum. Buradaki en iyi bahis, sıralama anahtarları oluşturmak amacıyla bunu uygulama katmanında ele almak olacaktır. Daha sonra sıralama anahtarları yeni bir sıralama sütununa kaydedilir.

    Bu en pratik yaklaşım olmayabilir. Bununla birlikte, böyle bir yeteneğin var olması hala çok havalı. Aşağıdaki yanıtta bunun bir örneğini daha ayrıntılı olarak açıkladım:

    Aşağıdaki dizeleri 1,2,3,6,10,10A, 10B, 11 sırasıyla sıralamak için bir karşılaştırma var mı?

    Ancak bu soruda ele alınan kalıp biraz daha basittir. Bu Soruda ele alınan desen türünün de işe yaradığını gösteren bir örnek için lütfen aşağıdaki sayfaya gidin:

    YBÜ Harmanlama Demosu

    "Ayarlar" altında, "sayısal" seçeneğini "açık" olarak ayarlayın ve diğerlerinin tümü "varsayılan" olarak ayarlanmalıdır. Ardından, "sıralama" düğmesinin sağında "fark güçlü yönleri" seçeneğinin işaretini kaldırın ve "sıralama anahtarları" seçeneğini işaretleyin. Ardından, "Giriş" metin alanındaki öğelerin listesini aşağıdaki listeyle değiştirin:

    P12B22
    P7B18
    P12B3
    as456456hgjg6786867
    P7Bb19
    P7BA19
    P7BB19
    P007B18
    P7Bb20
    P7Bb19z23

    "Sırala" düğmesini tıklayın. "Çıktı" metin alanı aşağıdakileri görüntülemelidir:

    as456456hgjg6786867
        29 4D 0F 7A EA C8 37 35 3B 35 0F 84 17 A7 0F 93 90 , 0D , , 0D .
    P7B18
        47 0F 09 2B 0F 14 , 08 , FD F1 , DC C5 DC 05 .
    P007B18
        47 0F 09 2B 0F 14 , 08 , FD F1 , DC C5 DC 05 .
    P7BA19
        47 0F 09 2B 29 0F 15 , 09 , FD FF 10 , DC C5 DC DC 05 .
    P7Bb19
        47 0F 09 2B 2B 0F 15 , 09 , FD F2 , DC C5 DC 06 .
    P7BB19
        47 0F 09 2B 2B 0F 15 , 09 , FD FF 10 , DC C5 DC DC 05 .
    P7Bb19z23
        47 0F 09 2B 2B 0F 15 5B 0F 19 , 0B , FD F4 , DC C5 DC 08 .
    P7Bb20
        47 0F 09 2B 2B 0F 16 , 09 , FD F2 , DC C5 DC 06 .
    P12B3
        47 0F 0E 2B 0F 05 , 08 , FD F1 , DC C5 DC 05 .
    P12B22
        47 0F 0E 2B 0F 18 , 08 , FD F1 , DC C5 DC 05 .

    Sıralama anahtarlarının virgülle ayrılmış birden çok alanda yapı olduğunu lütfen unutmayın. Her alanın bağımsız olarak sıralanması gerekir, böylece SQL Server'da uygulanması gerekiyorsa çözülmesi gereken başka bir küçük sorun ortaya çıkar.


** Kullanıcı Tanımlı İşlevlerin kullanımı ile ilgili performansla ilgili herhangi bir endişe varsa, önerilen yaklaşımların bu işlevleri en az düzeyde kullandığını lütfen unutmayın. Aslında, normalize edilmiş değeri saklamanın temel nedeni, her bir sorgunun her satırı için bir UDF çağırmaktan kaçınmaktı. Birincil yaklaşımda UDF değerini ayarlamak için kullanılır SortColumn, ve bu sadece üzerine yapılır INSERTve UPDATETetikleyici ile. Değerleri seçmek, eklemek ve güncellemekten çok daha yaygındır ve bazı değerler asla güncellenmez. Cümlesindeki aralık süzgecini SELECTkullanan her sorgu için UDF, normalleştirilmiş değerleri almak için aralık_başlangıç ​​ve aralık_end değerlerinin her biri için yalnızca bir kez gerekir; UDF satır başına çağrılmaz.SortColumnWHERE

UDT ile ilgili olarak, kullanım aslında skaler UDF ile aynıdır. Anlamı, ekleme ve güncelleme, değeri ayarlamak için her satıra bir kez normalleştirme yöntemini çağırır. Daha sonra normalleştirme yöntemi, aralık filtresindeki her aralık_start ve range_value başına sorgu başına bir kez çağrılır, ancak satır başına değil.

Normalleştirmeyi tamamen bir SQLCLR UDF'de ele almanın bir avantajı, veri erişimi yapmaması ve deterministik olması, işaretlenirse IsDeterministic = true, paralel planlara ( INSERTve UPDATEişlemlere yardımcı olabilir) katılabilir. T-SQL UDF, paralel bir planın kullanılmasını önleyecektir.

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.