Veritabanı tarih veri türü haçlı seferi hakkında: Valid? Değerli? Başka kimse hissediyor mu?


13

SO üzerinde SQL soruları cevaplamak için çok zaman harcamak. Ben sık sık bu ilk sorguları rastlamak:

SELECT * FROM person WHERE birthdate BETWEEN '01/01/2017' AND '01/03/2017'

SELECT * FROM person WHERE birthdate BETWEEN '2017-01-01' AND '2017-03-01'

SELECT * FROM person WHERE birthdate BETWEEN 'some string' AND 'other string'

yani ya dizeden tarihe örtük bir dönüşüme (kötü), verilen parametrelere dayanarak ya da x milyon veritabanı satırı değerlerini dizeye dönüştüren ve bir dize karşılaştırması (daha kötü) yaparak veritabanına güvenerek

Bazen akıllı bir cevap yazan, ancak gerçekten veri türleriyle daha az özensiz / dize yazılmış olması gerektiğini hissettiğim yüksek rep kullanıcısıysa, bir yorum yaparım

Yorum genellikle to_date (Oracle), str_to_date (MySQL), convert (SQLSERVER) veya benzer bir mekanizma kullanarak dizelerini açıkça tarihlere dönüştürürlerse muhtemelen daha iyi olacağı biçimini alır:

    --oracle
    SELECT * FROM person WHERE birthdate BETWEEN TO_DATE('20170101', 'YYYYMMDD') AND TO_DATE('20170301', 'YYYYMMDD')

    --mysql
    SELECT * FROM person WHERE birthdate BETWEEN STR_TO_DATE('20170101', '%Y%m%d') AND STR_TO_DATE('20170301', '%Y%m%d')

    --SQLS, ugh; magic numbers
    SELECT * FROM person WHERE birthdate BETWEEN CONVERT(datetime, '20170101', 112) AND CONVERT(datetime, '20170301', 112)

Bunu yapmak için teknik gerekçelerim, tarihin biçimine göre açık olması ve birkaç kaynak parametresinin kesinlikle hedef sütunun veri türü olmasını sağlar. Bu, veritabanının örtük bir dönüştürme hatası alma olasılığını önler (ilk örneğin 3 Ocak / 1 Mart bağımsız değişkeni) ve db'nin tablodaki bir milyon tarih değerini dizeye dönüştürmeye karar vermesini önler (sunucuya özel bir tarih kullanarak) sql içindeki dize parametrelerindeki tarih biçimiyle bile eşleşmeyebilecek biçimlendirme)

Benim sosyal / akademik gerekçem SO'nun bir öğrenme sitesi olması; üzerindeki insanlar bilgiyi dolaylı ya da açıkça edinirler. Bir cevap olarak bu sorguyu içeren bir acemi vurmak için:

SELECT * FROM person WHERE birthdate BETWEEN '2017-01-01' AND '2017-03-01'

Tercih ettikleri bazı formatlar için tarihi ayarlayarak, bu durumun mantıklı olduğunu düşünmelerine neden olabilir:

SELECT * FROM person WHERE birthdate BETWEEN '01/01/2017' AND '01/03/2017'

En azından tarihi dönüştürmek için açık bir girişimde bulunduysa, tuhaf tarih formatları için yapmaya başlayabilir ve ortaya çıkmadan önce sonsuza kadar bazı böcekleri öldürebilirler. Sonuçta, (I) insanları SQL enjeksiyon alışkanlığına girmekten caydırmaya çalışıyoruz (ve herkes bir sorguyu parametreleştirmeyi ve sonra @pBirthdateön uç datetime türüne sahip olduğunda, bir dize olan sürücüyü bildirmeyi savunur mu?)

Tavsiyemi yaptıktan sonra ne olduğuna geri dönelim: "Herkes açıkça", "her zaman benim için çalışıyor", "bana bazı manuel veya referans dokümanları göster" gibi "açık, x kullan" önerisine bir itiraz alıyorum açık "veya hatta" ne ?? "

Bunlardan bazılarına yanıt olarak WHERE age = '99', çağa bir dize olarak geçerek int sütununda arama yapıp yapmadıklarını sordum . "Aptal olma, 'int'i ararken' koymamız gerekmez" yanıtı gelir, bu yüzden akıllarında bir yerde farklı veri türleri için bazı takdirler vardır, ancak belki de int'i araştıran mantıksal sıçrayışla bağlantı yoktur Bir dizeyi geçirerek (görünüşte aptalca) ve bir dizeyi geçirerek (görünüşte mantıklı) bir tarih sütununda arama yapmak ikiyüzlülüktür

SQL'lerimizde bir şeyleri sayılar (sınırlayıcılar olmadan sayısal kullanın), dize dizeleri (kesme işareti sınırlayıcıları arasında herhangi bir şey kullanın) olarak yazmanın bir yolu var. Neden tarihler için sınırlayıcı yok? Çoğu DB'de böyle temel bir veri türü? Bütün bunlar, aynı şekilde bir tarih yazmanın bir yoluna sahip olarak çözülebilir mi? Javascript /, bazı karakterlerin her iki tarafını da koyarak bir normal ifadeyi belirlememize izin verir . /Hello\s+world/. Tarihler için neden bir şey yok?

Aslında, bildiğim kadarıyla, Microsoft Access aslında "bu sınırlayıcılar arasında bir tarih yazılmıştır" şeklinde sembollere sahiptir, bu yüzden iyi bir kısayol alabiliriz, WHERE datecolumn = #somedate#ancak tarih sunumu hala mm / di vs dd gibi problemler vermekle yükümlüdür. / mm, MS her zaman hızlı ve gevşek oynadığı için VB kalabalığının iyi bir fikir olduğunu düşündü


Ana noktaya geri dönelim: Bizi dizeler olarak çok sayıda farklı veri türünden geçirmeye zorlayan bu ortamla açık olmanın akıllıca olduğunu düşünüyorum.

Geçerli bir iddia mı?

Haçlı seferine devam etmeli miyim? Dize yazmanın modern bir hayır-hayır olduğu geçerli bir nokta mı? Ya da her RDBMS (eski sürümler dahil), bir sorgu WHERE datecolumn = 'string value'kesinlikle kesinlikle dizeyi bir tarihe dönüştürmek ve tablo verilerini dönüştürmeden / dizin kullanımını kaybetmeden arama yapmak zaman orada olacak? En azından Oracle 9'un kişisel deneyiminden hayır. string parametresi her zaman doğru bir şekilde dolaylı olarak dönüştürülecektir. Bu doğru mu?

Değerli bir görev mi?

Pek çok insan bunu anlamıyor ya da umursamıyor ya da ints ints, ancak tarihleri ​​dizgiler olduğu için iki yüzlülük sergiliyorlar. ne, senin fikrinle aynı fikirdeyim.


Birisinin WHERE datecolumn = 01/02/12 '' ile sorun yaşadığını bile gördüm , 1912, 2012, 2001, 1901, 12 veya 1 yılını sormaları mümkün. Ayrıca veritabanı dünyasının dışında bir sorun, sayı "09"bir int'e dönüştürmenin neden bir çökmeye neden olduğunu anlayamayan programcılar arasında lejyon, 9 geçerli bir sekizlik basamak değildir ve önde gelen 0, birçok sistemde dize sekizlik yapar
Steve Barnes

2
Ben WHERE age = '0x0F'bir veritabanı on beş yaşındakiler için arama umuyoruz geçerli bir yol olup olmadığını sormak için örnek uzatmayı düşündün ..
Caius Jard

1
Burada konu dışı bir soruyu kaldırdım - kaynak talepleri yapmıyoruz. Bu nedenle 2 yakın oylamadan biri verildi. Aksi takdirde, bence bu çok geniş olma sınırına sahip olsa da, geçerli bir soru. Umarım konu dışı sorunun kaldırılması işleri biraz daraltmaya yardımcı olur.
Thomas Owens

TL; DR ama üretim sistemlerinde böyle tarihlerin neredeyse her zaman parametrelerde olmasını beklerdim. Sorgulara tarih kodlaması, örtük dönüşümler kullanıp kullanmadığınızdan daha büyük bir sorundur. Eğer bazı atmak sorgu yazıyorsanız, ya çalışır ya da çalışmaz. Yine de bunu asla yapmam (çünkü varsayılan tarih biçimini asla hatırlayamıyorum) ama çok önemli olduğundan emin değilim.
JimmyJames

1
Hayat savaşlarınızı seçmekle ilgilidir. Bence bu savaşmaya değmez ...
Robbie Dee

Yanıtlar:


7

Sen yazdın:

1 Ocak - 3 Ocak arasındaki parametreler veya 1 Mart.

Bu aslında potansiyel bir hata kaynağıdır. Bunu bir askere işaret etmek diğer okuyuculara yardımcı olabilir, bu yüzden evet, bu geçerli bir endişe. Ancak yapıcı olabilmek için

  • ANSI SQL'e bakın ve bu standarttan DATE veya DATETIME değişmez değerlerini kullanın

  • belirli bir DBMS'nin olağan, açık olmayan tarih biçimini kullanın (ve hangi SQL lehçesinin kullanıldığından bahsedin)

Ne yazık ki, her DBMS ANSI SQL tarih değişmezlerini tam olarak aynı şekilde desteklemez (eğer destekliyorlarsa), bu genellikle ikinci yaklaşımın bir varyantına yol açacaktır. "Standart" farklı DB satıcıları tarafından katı bir şekilde uygulanmadığı gerçeği muhtemelen burada sorunun bir parçasıdır.

Ayrıca, birçok gerçek dünya sistemi için, istemci uygulamaları yerelleştirilmiş olsa bile, insanlar veritabanı sunucusunda belirli bir sabit yerel ayara güvenebilirler, çünkü her zaman aynı şekilde yapılandırılmış yalnızca bir tür sunucu vardır. Bu nedenle '01 / 03/2017 'nin, birlikte çalıştıkları belirli sistemde kullanılan herhangi bir SQL için sabit' dd / mm / yyyy 'veya' mm / dd / yyyy 'biçiminde olduğu varsayılabilir. Birisi size "her zaman benim için işe yarar" derse, bu gerçekten onun çevresi için mantıklı bir cevap olabilir . Bu durumda, bu konuyu tartışmak daha az değerli olur.

"Performans nedenleri" hakkında konuşmak: ölçülebilir performans problemleri olmadığı sürece, "potansiyel performans sorunları" ile tartışmak oldukça batıl inançtır. Bir veritabanı bir milyon dize güncel dönüşüm yapıyorsa veya zaman farkı sadece 1/1000 saniye olduğunda önemli değildir ve gerçek darboğaz, sorgunun 10 saniye sürmesine neden olan ağdır. Bu yüzden, birileri açıkça performans değerlendirmeleri istediği sürece bu endişeleri bir kenara bırakın.

Haçlı seferine devam etmeli miyim?

Size bir sır veriyorum: Dini savaşlardan nefret ediyorum. Yararlı hiçbir şeye yol açmazlar. Bu nedenle, SQL'deki belirsiz tarih / saat özellikleri sorunlara yol açabilirse, bunlardan bahsedin, ancak mevcut bağlamlarında gerçekten herhangi bir fayda sağlamazsa insanları daha katı olmaya zorlamayın.


Bu Amerikan vs Duygusal tarih formatlarının belirsizliği hakkında bir soru değil. Bir SQL deyimindeki tarihleri ​​bir dize olarak iletmenin mantıklı olup olmadığı ve bugüne kadar örtük dönüştürmeye bağlı olduğu ile ilgilidir. Veritabanının tüm milyon satırlar için bir milyon tarih-> str dönüşümü yapması sorunu bir performans özelliğidir ve bir sorgu için yalnızca saniyenin 1 / 1000'ini alabilir, ancak şimdi bunu eşzamanlı binlerce eşzamanlı bağlamda hayal edin kullanıcılar. Daha büyük performans sorunu, veri dönüştürmenin endekslerin artık kullanılamayacağı ve bunun gerçekten ciddi olabileceği anlamına geliyor
Caius Jard

@CaiusJard: cevabım duruyor: bazen mantıklı, bazen de değil, bağlama bağlı. Ve dürüst olmak gerekirse, burada herhangi bir şeyi "... hayal etmeyi ..." reddediyorum . Performans söz konusu olduğunda, herhangi bir varsayımsal durumu tartışmak yararlı değildir. Ölçülebilir performans sorunları olduğunda, önceden değil, optimize etme ve bazen de mikro optimize etme zamanı.
Doc Brown

Bunu varsayımsal olarak görmek ilginç; Örtük davranışa güvenmenin, hataların ve performans komplikasyonlarının ortaya çıkması için açık bir fırsat olduğunu görüyorum (iyi belgelenmiş nedenlerden dolayı: tüm sütun verileri aranmadan önce dönüştürülürse dizinler çalışmaz) ve açık talimatlarla bunlar gerçekleşemez
Caius Jard

@CaiusJard: kelimelerle oynamayın - "varsayımsal" ile "olası değil" demek istemiyorum, bu terimi, birinin ne olduğunu ölçebileceği "gerçek mevcut durumun" aksine, her türlü hayal senaryosu yerine kullandım.
Doc Brown

1
@CaiusJard: Diğer endüstri profesyonellerini etkilemek istiyorsanız, "performans optimizasyonu" nun neden "güvenlik optimizasyonu" ndan çok farklı olduğunu tam olarak bilmelisiniz ve bu tam olarak burada benim açımdan - performans sorunları ortaya çıktıktan sonra ele alınabilir, nadiren çok geç. Güvenlik sorunları değil, oluşmadan önce bunlardan tamamen kaçınılmalıdır. Lütfen elmaları portakalla karşılaştırmayın. Haçlı seferlerini seviyorsanız, güvenlik argümanları bunun için çok daha uygundur ;-)
Doc Brown

5

Haçlı seferiniz sorunu çözmez.

İki ayrı sorun vardır:

  • SQL'de örtük tür dönüşümü

  • 05/06/07 gibi belirsiz tarih biçimleri

Haçlı seferi ile nereden geldiğini görüyorum, ancak açık dönüşümün aslında eldeki sorunu çözdüğünü düşünmüyorum:

  • Karşılaştırmadaki türler arasında uyumsuzluk olması durumunda örtük dönüşüm gerçekleşir. Bir dize bir tarihle karşılaştırılırsa, SQL dizeyi önce bir tarihe dönüştürmeye çalışır. Dolayısıyla, bir tarih türü sütunu açıkça dönüştürülmüş bir tarih değeriyle karşılaştırmak, dize biçimindeki bir tarihle karşılaştırmakla tamamen aynıdır. Gördüğüm tek fark, bir tarih değerini, aslında tarihleri ​​değil, dizeleri içermeyen bir sütuna göre karşılaştırmanızdır, ancak bu her durumda bir hata olacaktır.

  • Açık dönüşüm kullanmak, ISO olmayan tarih biçimlerindeki belirsizliği çözmez.

Gördüğüm tek çözüm:

  • dize türü sütunları dize olmayan değerlerle karşılaştırmayın.
  • yalnızca ISO türü tarih biçimlerini kullanın.

Ve elbette, tarihleri ​​bir dize türü sütununda saklamayın. Ancak yine, tarih değişmezlerinin açıkça dönüştürülmesi bunu engellemeyecektir.

Muhtemelen, örtük dönüşümler SQL'de bir hataydı, ancak dilin nasıl tasarlandığı göz önüne alındığında, açık dönüşümün faydasını görmüyorum. Zaten örtük dönüşümden kaçınmayacak ve sadece kodun okunmasını ve yazılmasını zorlaştırıyor.


Doğru. Belki de bu perspektiften bahsetmeliyim ki, yapılacak en mantıklı şey, datecolumn operandının ve değer operandının aynı veri tipine (dize, tarih, her neyse) sahip olmasını sağlamaktır. Ben sadece bu tablo DATETIME olduğunu bildiğim sorular ve bu örnek örnek örtük dönüşüm ile bir dize işlenen kullanıyor olduğunu sorularda bu öneri yapmak ..
Caius Jard

Bu cevapta benimle ilgili bir şey yok. Bazı ilginç noktalara değiniyorsun ama sonucun idealist olduğunu hissediyorum. Tasarım açısından, evet, ISO dışı tarih biçimleri insan gözü için belirsizdir, ancak açık dönüşüm kullanılıyorsa sözdizimsel olarak ayrıştırıcı için belirsiz değildir . Benzer şekilde, tarihleri ​​içeren birçok ETL işlemi , bir dizenin veritabanının tarih biçimiyle bir miktar karşılaştırılmasını (dosya içe aktarma şeklinde) gerektirir. Dizeden bugüne karşılaştırmaları ortadan kaldırmaya çalışmak benim için gerçekçi görünmüyor.
DanK

@DanK: ETL farklı bir sorundur - bir CSV dosyasından veya başka bir şeyden veri okuyorsanız, verileri açıkça dizeler olarak işlemeniz ve yazdığınız değerlere ayrıştırmanız gerekir. Ancak OP'nin açıkladığı senaryo bu değil.
JacquesB

Yine de tarif ettiğim nokta kolayca olabilir; bir ayrıştırma sırasında biçimi açıkça bildirmek isteyen bir csv saklanan sayılar dizisi hakkında özel bir şey yoktur ve bir acemi SO açıkça pro herhangi bir çaba yapmaz nerede SO bir cevap okursa yaptığım argüman ile ilgili olur tarih formatı beyan, yeni başlayanlar bu konuda endişelenmelerine gerek olmadığını varsayalım (ya da db her zaman doğru ayrıştırmak için)
Caius Jard

@CaiusJard: Bunların çok farklı senaryolar olduğuna inanıyorum. Normal senaryolarda SQL hakkında konuşurken, sütunların uygun türlere sahip olduğunu varsayalım - yani tamsayı sütunları tamsayı türüdür, tarih sütunları veri türündedir. Tablolarda doğru türlere sahip değilseniz (yani tarihleri ​​dizeler olarak saklayın) derin bir sorunla karşı karşıyasınız ve sorgulardaki açık dönüştürme tarihi değişmezleri sizi kurtaramaz , bu da benim açımdan.
JacquesB

3

İlk ve en önemlisi, bir noktanız var. Tarihler dizgilere konmamalıdır. Veritabanı motorları, rastgele bir sorgu verildiğinde kaputun altında tam olarak ne olacağını asla% 100 emin olmadığınız karmaşık hayvanlardır. Tarihe dönüştürmek her şeyi netleştirir ve performansı artırabilir.

FAKAT

Çoğu insan için çözülmesi gereken ekstra çabaya değer bir sorun değildir. Bir sorguda tarih değişmezlerini kullanmak kolay olsaydı, konumunuzu savunmak kolay olurdu. Ama değil. Çoğunlukla SQL Server kullanıyorum, bu yüzden bir tarihi dönüştürmek için bu karışıklığı hatırlamaya çalışmak sadece gerçekleşmiyor.

Çoğu insan için performans artışı göz ardı edilebilir. "Neden evet Bay Boss-man, bu basit hatayı düzeltmek için fazladan 10 dakika harcadım (sözleri sözdizimi ... özel olduğu için nasıl google'ı değiştirmek zorunda kaldım.) Ama fazladan bir 0.00001 saniye kazandım nadiren yürütülen bir sorgu. " Bu, çalıştığım çoğu yerde uçmayacak.

Ancak söylediğiniz tarih biçimlerindeki belirsizliği ortadan kaldırır. Yine, birçok uygulama için (şirket içi uygulamalar, yerel yönetim işleri, vb.) Bu gerçekten bir endişe kaynağı değildir. Ve bunun bir endişe kaynağı olduğu uygulamalar (büyük, uluslararası veya kurumsal uygulamalar), ya bir UI / iş katmanı endişesi haline gelir veya bu şirketler zaten bunu zaten bilen iyi deneyimli DBA'lardan oluşan bir ekibe sahiptir. TL / DR: Uluslararasılaşma bir endişe kaynağıysa, birisi zaten bunu düşünüyor ve önerdiğiniz gibi zaten yapmış (ya da sorunu hafifletmiş).

Peki şimdi ne olacak?

Eğer bu kadar eğimli hissediyorsanız, iyi dövüşlerle savaşmaya devam edin. Ancak çoğu insan bunun endişelenecek kadar önemli olduğunu düşünmezse şaşırmayın. Bunun önemli olduğu durumlar olduğu için, bunun herkesin durumu olduğu anlamına gelmez (ve muhtemelen değildir). Bu nedenle, teknik olarak doğru ve daha iyi, ama gerçekten alakalı olmayan bir şey için biraz geri döndüğünüzde şaşırmayın.


1

Bizi dizeler olarak çok sayıda farklı veri türünden geçirmeye zorlayan bu ortamla açık olmanın akıllıca olduğunu düşünüyorum.

"Tarihler" in " Dizeler " içinde "geçildiğini varsayarsak o zaman evet; Bunu yapma hakkın olduğunu kesinlikle kabul ediyorum .

Ne zaman bir "01/04/07"?
* 4 Ocak mı?
* 1 Nisan?
* 7 Nisan [2001]?

Bunların herhangi biri veya tümü, "bilgisayar" ın bunları nasıl yorumlamayı seçtiğine bağlı olarak doğru olabilir .

Eğer varsa var onları değişmezleri ile dinamik SQL, inşa etmek, sonra biçimlendirme tarih, iyi tanımlanmış olması gerekir ve tercihen, makine-bağımsız (Ben Bir Windows Hizmet içinde tarih tabanlı işlem ters gitti bir Windows Server garip bir tane vardı çünkü bir operatör konsola farklı tarih formatı tercihleriyle giriş yaptı!). Şahsen ben sadece [d] "yyyy-aa-gg" biçimini kullanıyorum.

Ancak ...

En iyi çözüm, SQL dahil edilmeden önce veri türünü dönüştürülmeye zorlayan Parametrelendirilmiş Sorguları kullanmaktır - "tarih" değerini bir Tarih parametresine almak, tür dönüştürmeyi erkenden zorlar (SQL değil, yalnızca kodlama sorunu haline getirir) .


Aynı sorun, parametreli sorgular ile, WHERE datecolumn = @dateParametersonra ve sonra ön uç kodunda @dateParametervarchar tipi DB sürücüsüne söyleyerek ve içine yapıştırarak yeniden zorla kabul edilebilir, katılıyorum "01/04/07". Sorumun asıl ilham kaynağı, parametreli bir sorguya bunu yaptığım için bana deli olduğumu söyleyecek kimsenin, aynı nefeste, benzeyen bir satır SO cevabı vereceğinden şüphelenmem WHERE datecol = 'some string that looks like a date'(ve bir aceminin bilmesi gerektiğini beklemem) sorunları önlemek için sadece bir ipucu / parametreleştirir)
Caius Jard
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.