RDBMS'ler neden birleştirilmiş tabloları iç içe biçimde döndürmüyor?


14

Örneğin, bir Kullanıcıyı ve tüm telefon numaralarını ve e-posta adreslerini almak istediğimi varsayalım. Telefon numaraları ve e-postalar ayrı tablolarda saklanır, Bir kullanıcıya birçok telefon / e-posta. Bunu oldukça kolay bir şekilde yapabilirim:

SELECT * FROM users user 
    LEFT JOIN emails email ON email.user_id=user.id
    LEFT JOIN phones phone ON phone.user_id=user.id

Buradaki sorun * kullanıcının adı, DOB, favori rengi ve kullanıcı tablosunda saklanan diğer tüm bilgilerin her kayıt için tekrar tekrar geri dönmesidir (kullanıcılar telefon kayıtlarını e-posta ile gönderir), muhtemelen bant genişliğini tüketir ve yavaşlar sonuçları.

Her kullanıcı için tek bir satır döndü ve o kaydın içinde olsaydı daha güzel olmaz mıydı liste e-postaların ve bir liste telefonlarının? Verilerin çalışması da çok daha kolay olur.

LINQ veya belki de başka çerçeveler kullanarak böyle sonuçlar alabileceğinizi biliyorum, ancak ilişkisel veritabanlarının temel tasarımında bir zayıflık gibi görünüyor.

NoSQL kullanarak bu sorunu çözebiliriz, ancak orta yol olmamalı mı?

Bir şey mi kaçırıyorum? Bu neden mevcut değil?

* Evet, bu şekilde tasarlanmıştır. Anladım. Neden çalışmak daha kolay bir alternatif olmadığını merak ediyorum. SQL yaptıklarını yapmaya devam edebilir, ancak daha sonra verileri kartezyen bir ürün yerine iç içe bir biçimde döndüren biraz işlem sonrası yapmak için bir veya iki anahtar kelime ekleyebilirler.

Bu, seçtiğiniz bir betik dilinde yapılabilir, ancak SQL sunucusunun yedekli veri (aşağıdaki örnek) göndermesini veya sizin gibi birden fazla sorgu göndermenizi gerektirir SELECT email FROM emails WHERE user_id IN (/* result of first query */).


MySQL'in buna benzer bir şey döndürmesi yerine:

[
    {
        "name": "John Smith",
        "dob": "1945-05-13",
        "fav_color": "red",
        "email": "johnsmith45@gmail.com",
    },
    {
        "name": "John Smith",
        "dob": "1945-05-13",
        "fav_color": "red",
        "email": "john@smithsunite.com",
    },
    {
        "name": "Jane Doe",
        "dob": "1953-02-19",
        "fav_color": "green",
        "email": "originaljane@deerclan.com",
    }
]

Ve sonra sonuç kümesi nasıl istediğinizi yeniden biçimlendirmek için istemci tarafında bazı benzersiz tanımlayıcı gruplamak zorunda (yani ben de getirmek gerekir!), Sadece şunu döndürmek:

[
    {
        "name": "John Smith",
        "dob": "1945-05-13",
        "fav_color": "red",
        "emails": ["johnsmith45@gmail.com", "john@smithsunite.com"]
    },
    {
        "name": "Jane Doe",
        "dob": "1953-02-19",
        "fav_color": "green",
        "emails": ["originaljane@deerclan.com"],
    }
]

Alternatif olarak, 3 sorgu gönderebilirim: kullanıcılar için 1, e-postalar için 1 ve telefon numaraları için 1, ancak daha sonra e-posta ve telefon numarası sonuç kümelerinin kullanıcılarla eşleşebilmesi için user_id içermesi gerekir Daha önce getirmiştim. Yine, gereksiz veriler ve gereksiz işlem sonrası.


6
SQL'i Microsoft Excel'de olduğu gibi bir elektronik tablo olarak düşünün, ardından iç hücreleri içeren bir hücre değerinin nasıl oluşturulacağını anlamaya çalışın. Artık bir e-tablo olarak iyi çalışmıyor. Aradığın şey bir ağaç yapısı, ancak o zaman artık bir e-tablonun avantajlarına sahip değilsin (yani bir ağaçtaki bir sütunu toplayamazsın). Ağaç yapıları çok insan tarafından okunabilir raporlar oluşturmaz.
Reactgular

54
SQL veri döndürmede fena değil, ne istediğinizi sorgulamakta kötüsünüz. Genel bir kural olarak, yaygın olarak kullanılan bir aracın yaygın bir kullanım durumu için hatalı veya bozuk olduğunu düşünüyorsanız, sorun sizsiniz.
Sean McSomething

12
@SeanMcSomething O kadar acıyor ki, kendim daha iyi söyleyemezdim.
WernerCD

5
Bu harika bir soru. "Bu şekilde" diyen cevaplar eksik. Katıştırılmış satır koleksiyonlarına sahip satırları döndürmek neden mümkün değil?
Chris Pitman

8
@SeanMcSomething: Yaygın olarak kullanılan araç C ++ veya PHP olmadığı sürece, bu durumda muhtemelen haklısınız. ;)
Mason Wheeler

Yanıtlar:


11

Derinlemesine, ilişkisel bir veritabanının bağırsaklarında, tüm satırları ve sütunları. İlişkisel bir veritabanının çalışmak için optimize edildiği yapı budur. İmleçler her seferinde ayrı satırlar üzerinde çalışır. Bazı işlemler geçici tablolar oluşturur (yine satır ve sütun olması gerekir).

Sistem yalnızca satırlarla çalışıp yalnızca satır döndürerek, bellek ve ağ trafiği ile daha iyi başa çıkabilir.

Belirtildiği gibi, bu belirli optimizasyonların yapılmasına izin verir (dizinler, birleşimler, birlikler vb.)

İç içe bir ağaç yapısı isteniyorsa, bu, tüm verileri bir kerede almasını gerektirir . Veritabanı tarafındaki imleçler için optimizasyonlar gitti. Aynı şekilde, ağ üzerindeki trafik, satır satır yavaş yavaş damlamasından çok daha uzun sürebilen büyük bir patlama haline gelir (bu günümüz web dünyasında zaman zaman kaybolan bir şeydir).

Her dilin içinde diziler vardır. Bunlar, çalışmak ve arayüz kurmak için kolay şeylerdir. Çok ilkel bir yapı kullanarak, veritabanı ve program arasındaki sürücü - hangi dil olursa olsun - ortak bir şekilde çalışabilir. Kişi ağaç eklemeye başladığında, dildeki yapılar daha karmaşık ve geçişi daha zor hale gelir.

Bir programlama dilinin döndürülen satırları başka bir yapıya dönüştürmesi o kadar da zor değildir. Bir ağaca veya karma kümesine dönüştürün veya yineleyebileceğiniz satırların bir listesi olarak bırakın.

Burada işte tarih var. Yapısal verilerin aktarılması eskiden çirkin bir şeydi. Ne isteyebileceğiniz hakkında fikir edinmek için EDI biçimine bakın. Ağaçlar ayrıca, bazı dillerin desteklemediği özyineleme anlamına gelir (eski günlerin en önemli iki dili özyinelemeyi desteklemiyordu - özyineleme, F90 ve COBOL döneminden de Fortran'a girmedi).

Bugünün dilleri özyineleme ve daha gelişmiş veri türlerini desteklerken, bazı şeyleri değiştirmek için gerçekten iyi bir neden yoktur. Çalışıyorlar ve iyi çalışıyorlar. Olanlar vardır şeyleri değiştirerek NoSQL veritabanları bulunmaktadır. Ağaçları belge tabanlı bir belgedeki belgelerde saklayabilirsiniz. LDAP (aslında eskimiş) aynı zamanda ağaç tabanlı bir sistemdir (muhtemelen sizin peşinde olduğunuz şey değil). Kim bilir, belki de nosql veritabanlarındaki bir sonraki şey, sorguyu bir json nesnesi olarak geri döndüren bir şey olacaktır.

Ancak, 'eski' ilişkisel veritabanları ... satırlarla çalışıyorlar çünkü iyi oldukları şey bu ve her şey onlarla sorunsuz veya çeviri olmadan konuşabiliyor.

  1. Protokol tasarımında, eklenecek hiçbir şey kalmadığı zaman değil, alınacak hiçbir şey kalmadığında mükemmellik elde edildi.

Gönderen The Twelve Ağ Hakikatler - RFC 1925


"İç içe bir ağaç yapısı istiyorsanız, bunun için tüm verileri bir kerede alması gerekir. Gitti, veritabanı tarafındaki imleçler için optimizasyonlar." - Kulağa doğru gelmiyor. Sadece birkaç imleci korumak zorunda kalacaktı: biri ana tablo için, sonra da birleştirilen her tablo için bir tane. Arabirime bağlı olarak, bir satır ve birleştirilmiş tüm tabloları tek bir yığın halinde (kısmen akış olarak) döndürebilir veya siz yinelemeye başlayana kadar alt ağaçları akışlandırabilir (ve hatta sorgulayamaz). Ama evet, bu çok karmaşık şeyler.
mpen

3
Her modern dilde bir çeşit ağaç sınıfı olmalı, değil mi? Ve bununla başa çıkmak sürücüye kalmaz mı? Sanırım SQL çocuklar hala ortak bir format tasarımı gerekir (bu konuda çok şey bilmiyorum). Beni alır şey şey ya ben birleştirme ile 1 sorgu göndermek ve geri almak ve her satır (sadece her Nth satır değiştiren kullanıcı bilgisi) gereksiz verileri filtre veya 1 sorgu (kullanıcılar) , ve sonuçlar üzerinde döngü, daha sonra ihtiyacım olan bilgileri getirmek için her kayıt için iki daha sorgu (e-posta, telefon) gönderin. Her iki yöntem de savurgan görünüyor.
02:24

51

Tam olarak istediğinizi döndürüyor: birleşimler tarafından tanımlanan Kartezyen ürünü içeren tek bir kayıt kümesi. SQL'in kötü bir sonuç verdiğini (ve böylece değiştirirseniz daha iyi olacağını ima ederek) aslında birçok soruyu bertaraf edeceğini söylemek istediğiniz birçok geçerli senaryo vardır.

Yaşadığınız şey, " Nesne / İlişkisel Empedans Uyuşmazlığı " olarak bilinir; nesne yönelimli veri modeli ile ilişkisel veri modelinin temelde çeşitli şekillerde farklı olmasından kaynaklanan teknik zorluklar. LINQ ve diğer çerçeveler (tesadüfen ORM'ler, Nesne / İlişkisel Haritacılar olarak bilinir) sihirli bir şekilde "bu sorunu aşmaz"; sadece farklı sorgular yayınlarlar. SQL'de de yapılabilir. İşte böyle yapardım:

SELECT * FROM users user where [criteria here]

Kullanıcı listesini yineleyin ve kimliklerin bir listesini yapın.

SELECT * from EMAILS where user_id in (list of IDs here)
SELECT * from PHONES where user_id in (list of IDs here)

Ve sonra birleştirme müşteri tarafı yaparsınız. LINQ ve diğer çerçeveler böyle yapar. Gerçek bir sihir yok; sadece bir soyutlama katmanı.


14
+1 "tam olarak ne istediğini". Teknolojiyi etkili bir şekilde nasıl kullanacağımızı öğrenmemiz gerektiğinden ziyade teknoloji ile ilgili yanlış bir şey olduğu sonucuna sık sık atlıyoruz.
Matt

1
Hazırda bekletme modu, bu koleksiyonlar için istekli getirme modu kullanıldığında , kök varlığı ve belirli koleksiyonları tek bir sorguda alır ; bu durumda bellekteki kök varlık özelliklerinin azaltılmasını sağlar. Diğer ORM'ler de aynı şeyi yapabilir.
Mike Partridge

3
Aslında bu ilişkisel modelden sorumlu değildir. İç içe ilişkilerle çok güzel başa çıkıyor teşekkürler. Bu tamamen SQL'in ilk sürümlerinde bir uygulama hatasıdır. Bence daha yeni sürümler ekledi.
John Nilsson

8
Bunun bir nesne-ilişkisel empedans örneği olduğundan emin misiniz? İlişkisel modelin OP'nin kavramsal veri modeliyle mükemmel bir şekilde eşleştiğini düşünüyorum: her kullanıcı sıfır, bir veya daha fazla e-posta adresi listesiyle ilişkilidir. Bu model aynı zamanda bir OO paradigmasında mükemmel bir şekilde kullanılabilir (toplama: kullanıcı nesnesi bir e-posta koleksiyonuna sahiptir). Sınırlama, bir uygulama detayı olan veritabanını sorgulamak için kullanılan tekniktir. Hiyerarşik veri döndürecek yapmak etrafında sorgu teknikleri, örneğin vardır heirarchical DataSets .Net içinde
MarkJ

@ MarkJ bunu bir cevap olarak yazmalısınız.
Mr.Mindor

12

Kayıtları birleştirmek için yerleşik bir işlev kullanabilirsiniz. MySQL'de GROUP_CONCAT()işlevi, Oracle'da LISTAGG()işlevi kullanabilirsiniz .

MySQL'de bir sorgunun nasıl görünebileceğine dair bir örnek:

SELECT user.*, 
    (SELECT GROUP_CONCAT(DISTINCT emailAddy) FROM emails email WHERE email.user_id = user.id
    ) AS EmailAddresses,
    (SELECT GROUP_CONCAT(DISTINCT phoneNumber) FROM phones phone WHERE phone.user_id = user.id
    ) AS PhoneNumbers
FROM users user 

Bu böyle bir şey döndürür

username    department       EmailAddresses                        PhoneNumbers
Tim_Burton  Human Resources  hr@m.com, tb@me.com, nunya@what.com   231-123-1234, 231-123-1235

Bu, OP'nin yapmaya çalıştığı şeye en yakın çözüm (SQL'de) gibi görünüyor. E-postaAdresleri ve PhoneNumbers sonuçlarını listelere bölmek için hala istemci tarafı işlemesi yapması gerekecektir.
Mr.Mindor

2
Telefon numarasının "Hücre", "Ev" veya "İş" gibi bir "türü" varsa ne olur? Ayrıca, e-posta adreslerinde virgüllere teknik olarak izin verilir (alıntı yapıldıysa) - o zaman nasıl bölebilirim?
15:15

10

Buradaki sorun, kullanıcının adını, DOB'u, favori rengi ve saklanan diğer tüm bilgileri döndürmesidir.

Sorun yeterince seçici olmamanız. Söylediğin zaman her şeyi istedin

Select * from...

... ve anladınız (DOB ve favori renkler dahil).

Muhtemelen biraz daha fazla olmalısın (ahem) ... seçmeli ve şöyle bir şey söylemeliydin:

select users.name, emails.email_address, phones.home_phone, phones.bus_phone
from...

Yinelenenlere benzeyen kayıtları da görebilirsiniz, çünkü userbirden fazla emailkayda katılabilir , ancak bu ikisini ayıran alan ifadenizde değildir Select, bu nedenle şöyle bir şey söylemek isteyebilirsiniz

select distinct users.name, emails.email_address, phones.home_phone, phones.bus_phone
from...

... her kayıt için tekrar tekrar ...

Ayrıca, bir LEFT JOIN. Bu, birleştirme işleminin solundaki (yani users) tüm kayıtları sağdaki tüm kayıtlara veya başka bir deyişle birleştirir:

Sol dış birleşim, iç birleşimdeki tüm değerleri ve sol tabloda sağdaki tabloyla eşleşmeyen tüm değerleri döndürür.

( http://en.wikipedia.org/wiki/Join_(SQL)#Left_outer_join )

Öyleyse başka bir soru, aslında bir sola katılmaya mı ihtiyacınız var , yoksa INNER JOINyeterli miydi? Çok farklı birleşim türleri.

Her kullanıcı için tek bir satır döndürdüyse daha iyi olmaz ve bu kayıtta e-postaların bir listesi vardı

Sonuç kümesindeki tek bir sütunun anında oluşturulmuş bir liste içermesini istiyorsanız, bu yapılabilir, ancak kullandığınız veritabanına bağlı olarak değişir. Oracle listaggişlevi vardır .


Sonuçta, ben senin sorunun olduğunu düşünüyorum olabilir böyle bir şey için sorgu yakın yeniden halinde çözülebilir:

select distinct users.name, users.id, emails.email_address, phones.phone_number
from users
  inner join emails on users.user_id = emails.user_id
  inner join phones on users.user_id = phones.user_id

1
* kullanmak cesaretini kırmıştır, ancak sorunun temel noktası değildir. 0 kullanıcı sütunu seçse bile, hem Telefonların hem de E-postaların Kullanıcılar ile 1-çok ilişkisi olduğu için çoğaltma etkisi yaşayabilir. Farklı bir telefon numarasının iki kez ala phone1/name@hotmail.com, phone1/name@google.com adresinin görünmesini engellemez.
mike30

6
-1: "Sorunun olabilir çözülecek" sen ne etkisi olur değişikliği için bilmiyorum diyor left joinetmek inner join. Bu durumda, bu kullanıcının şikayet ettiği "tekrarları" azaltmaz; telefon veya e-posta eksikliği olan kullanıcıları atlar. neredeyse hiç gelişme yok. ayrıca, "soldaki tüm kayıtlar sağdaki tüm kayıtlara" yorumlanırken, ONKartezyen ürününün doğasında bulunan tüm "yanlış" ilişkileri budanan ancak tekrarlanan tüm alanları tutan kriterleri atlar .
Javier

@Javier: Evet, bu yüzden ben de gerçekten bir sol katılmaya mı ihtiyacınız var demiştim , yoksa bir INNER JOIN yeterli miydi? * OP'nin problemin açıklaması , bir iç birleşimin sonucunu beklermiş gibi ses çıkarır . Tabii ki, herhangi bir örnek veri veya gerçekten istediklerinin bir açıklaması olmadan , söylemek zor. Öneriyi yaptım çünkü aslında insanların (birlikte çalıştığım kişiler) bunu yaptığını gördüm: yanlış katılmayı seçin ve aldıkları sonuçları anlamadıklarında şikayet edin. Sonra görülen o, ben burada olanları olabileceğini düşündüm.
SinirliWithFormsDesigner 16:13

3
Sorunun amacını kaçırıyorsunuz. Bu varsayımsal örnekte, istediğiniz tüm kullanıcı verileri (ad, dob, vs) ve ben / görünmez tüm telefon numaralarını istiyorum. Dahili bir katılım, e-posta veya telefon olmayan kullanıcıları hariç tutar - bu nasıl yardımcı olur?
15'te mpen

4

Sorgular her zaman dikdörtgen (pürüzlü) tablo şeklinde bir veri kümesi üretir. Bir kümede iç içe alt küme yoktur. Setler dünyasında her şey saf, iç içe geçmiş bir dikdörtgendir.

Bir birleşimi 2 set yan yana koymak olarak düşünebilirsiniz. "Açık" koşulu, her kümedeki kayıtların nasıl eşleştirildiğidir. Bir kullanıcının 3 telefon numarası varsa, kullanıcı bilgilerinde 3 kez yineleme görürsünüz. Sorgu tarafından bir dikdörtgen pürüzlü küme üretilmelidir. Bu, 1'den çoka kadar bir ilişkiyle setlere katılmanın doğasıdır.

İstediğinizi elde etmek için, Mason Wheeler gibi açıklanan ayrı bir sorgu kullanmalısınız.

select * from Phones where user_id=344;

Bu sorgunun sonucu hala bir dikdörtgen dikdörtgen olmayan kümedir. Set dünyasındaki her şey gibi.


2

Darboğazların nerede olduğuna karar vermelisiniz. Veritabanınız ve uygulamanız arasındaki bant genişliği genellikle oldukça hızlıdır. Çoğu veritabanının bir çağrıda 3 ayrı veri kümesini ve birleştirilmemesi için hiçbir neden yoktur. Daha sonra isterseniz uygulamanızda hepsine katılabilirsiniz.

Aksi takdirde, veritabanının bu veri kümesini bir araya getirmesini ve ardından her satırdaki birleşmelerin sonucu olan tüm yinelenen değerleri kaldırmasını ve satırların aynı ada veya telefon numarasına sahip iki kişi gibi yinelenen verilere sahip olması gerekmez. Bant genişliğinden tasarruf etmek için çok fazla kafa gibi görünüyor. İhtiyacınız olmayan sütunları daha iyi filtreleyerek ve kaldırarak daha az veri döndürmeye odaklanmanız daha iyi olur. Çünkü Select * asla üretim kuyusunda kullanılmaz.


"Çoğu veritabanının bir çağrıda 3 ayrı veri kümesini ve birleştirilmemesi için bir neden yoktur" - Bir çağrıda 3 ayrı veri kümesini döndürmesini nasıl sağlarsınız? Her biri arasında gecikme yaratan 3 farklı sorgu göndermeniz gerektiğini düşündüm.
mpen

Saklı yordam 1 işlemde çağrılabilir ve sonra istediğiniz kadar veri kümesi dönebilirsiniz. Belki bir "SelectUserWithEmailsPhones" sproc gereklidir.
Graham

1
@Mark: Aynı toplu işin bir parçası olarak (en az sql sunucusunda) birden fazla komut gönderebilirsiniz. cmdText = "b'den * seçin; a'dan * seçin; c'den * seçin" ve sonra bunu sqlcommand için komut metni olarak kullanın.
jmoreno

2

Çok basit bir şekilde, bir kullanıcı sorgusu ve bir telefon numarası sorgusu için farklı sonuçlar istiyorsanız verilerinize katılmayın, aksi takdirde diğerleri "Ayarla" yı işaret ettiğinden veya veriler her satır için fazladan alanlar içereceğinden.

Birleştirme içeren bir tane yerine 2 ayrı sorgu yayınlayın.

Saklı yordam veya satır içi parametreli sql craft 2 sorgular ve her iki geri sonuçlarını döndürür. Çoğu veritabanı ve dil birden çok sonuç kümesini destekler.

Örneğin, SQL Server ve C #, bunu kullanarak işlevsellik elde eder IDataReader.NextResult().


1

Bir şey eksik. Verilerinizi normalleştirmek istiyorsanız, bunu kendiniz yapmanız gerekir.

;with toList as (
    select  *, Stuff(( select ',' + (phone.phoneType + ':' + phone.PhoneNumber) 
                    from phones phone
                    where phone.user_id = user.user_id
                    for xml path('')
                  ), 1,1,'') as phoneNumbers
from users user
)
select *
from toList

1

İlişkisel kapanma kavramı temel olarak, herhangi bir sorgunun sonucunun, diğer sorgularda temel tablo gibi kullanılabilen bir ilişki olduğu anlamına gelir. Bu güçlü bir kavramdır çünkü sorguları birleştirilebilir kılar.

SQL, iç içe veri yapıları üreten sorgular yazmanıza izin verdiyse, bu ilkeyi ihlal edersiniz. Yuvalanmış bir veri yapısı bir ilişki değildir, bu nedenle daha fazla sorgulamak veya diğer ilişkilere katılmak için SQL için yeni bir sorgu diline veya karmaşık uzantılara ihtiyacınız olacaktır.

Temel olarak ilişkisel DBMS'nin üzerine hiyerarşik bir DBMS oluşturacaksınız. Şüpheli bir fayda için çok daha karmaşık olacak ve tutarlı bir ilişkisel sistemin avantajlarını kaybedeceksiniz.

Neden bazen hiyerarşik olarak yapılandırılmış verileri SQL'den çıkmanın uygun olacağını anlıyorum, ancak bunu desteklemek için DBMS boyunca eklenen karmaşıklıktaki maliyet kesinlikle buna değmez.


-4

Pls, bir satırın (kullanıcı) sınırlandırılmış değerlerinin tek bir hücresi olarak çıkarılabilen bir sütunun (kişi) birden çok satırını (telefon numaraları) gruplayan STUFF işlevinin kullanımını ifade eder.

Bugün bunu yoğun olarak kullanıyoruz ancak bazı yüksek CPU ve performans sorunları yaşıyoruz. XML veri türü başka bir seçenektir, ancak sorgu düzeyinde bir tasarım değişikliği değildir.


5
Lütfen bunun soruyu nasıl çözdüğünü genişletin. "Pls kullanımı anlamına gelir" demek yerine, bunun sorulan soruya nasıl ulaşacağına dair bir örnek verin. Ayrıca, işleri daha açık hale getirdiği 3. taraf kaynaklarını alıntılamak da yararlı olabilir.
bitsoflogic

1
Birleşmeye benziyor STUFF. Bunun sorum için nasıl geçerli olduğundan emin değilim.
mpen
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.