T-SQL a výber prvej asociovanej položky

Po Sirovom príspevku na T-SQL pridám aj ja niečo. Predstavte si, že máte dve tabuľky Contact a PhoneNumber.

Contact
ContactId
FirstName
LastName
ďalšie stĺpce
PhoneNumber
PhoneNumberId
ContactId
Number

Pričom ContactId v tabuľke PhoneNumber je foreign key na stĺpec ContactId z tabuľky Contact.

A problém je nasledovný. Treba vybrať všetky kontakty a ku každému najviac jedno telefónne číslo. Je úplne jedno ktoré, ale najviac jedno. Určite väčšina z vás už podobný problém riešila. Ja používam už od čias mojich začiatkov s SQL nasledovné riešenie.

WITH PhoneNumberMinId (ContactId, MinPhoneNumberId) AS

(

SELECT ContactId, MIN(PhoneNumberId) AS MinPhoneNumberId

FROM PhoneNumber

GROUP BY ContactId

)

SELECT C.*, PN.Number

FROM Contact C LEFT JOIN

PhoneNumberMinId PNMI ON PNMI.ContactId = C.ContactId LEFT JOIN

PhoneNumber PN ON PN.PhoneNumberId = PNMI.MinPhoneNumberId

Táto syntax sa dá použiť len v SQL Server 2005 a vyššej verzii, ale použil som ju kvôli lepšej čitateľnosti a zrozumiteľnosti. Pred SQL 2005 som používal nasledovný select, ktorý je sémanticky úplne rovnaký.

SELECT C.*, PN.Number

FROM Contact C LEFT JOIN

(

SELECT ContactId, MIN(PhoneNumberId) AS MinPhoneNumberId

FROM PhoneNumber

GROUP BY ContactId

) AS PNMI ON PNMI.ContactId = C.ContactId LEFT JOIN

PhoneNumber PN ON PN.PhoneNumberId = PNMI.MinPhoneNumberId

Podobnú štruktúru mám teraz aj v MS CRM. Lenže MS CRM primárne kľúče nepoužíva Integer ale GUID. A SQL nepodporuje funkciu MIN nad GUID. Takže moje dlhoročné riešenie prestalo fungovať. Našťastie v SQL Server 2005 pribudli novinky, ktoré podobný problém dokážu vyriešiť. A to konkrétne funkcia ROW_NUMBER spolu s PARTITION BY.

WITH IndexedPhoneNumbers (ContactId, Number, RowIndex) AS

(

SELECT ContactId, Number,

ROW_NUMBER() OVER (PARTITION BY ContactId ORDER BY PhoneNumberId) AS RowIndex

FROM PhoneNumber

)

SELECT C.*, IPN.Number

FROM Contact C LEFT JOIN

IndexedPhoneNumbers IPN ON IPN.ContactId = C.ContactId AND IPN.RowIndex = 1

Možno to niekomu pomôže. Možno niekto pozná ešte elegantnejšie riešenie, tak nech ho kľudne pridá do komentárov.

Bookmark and Share
Zaradené do:

Komentáre

# siro said:

Nedalo by sa to aj takto riešiť?

SELECT *, (SELECT TOP 1 Number FROM PhoneNumber d WHERE d.ContactId=c.ContactId) FROM Contact c

To WITH som nikdy nepoužíval, aspoň viem na čo slúži v T-SQL.

Thursday, August 13, 2009 10:33 AM
# duracellko said:

normalne to funguje.. som netusil, ze SQL server podporuje aj taketo expression v stlpci. vdaka.

Thursday, August 13, 2009 11:47 AM
# vlko said:

Hmm zaujimave, sirove riesenie je rychlejsie, ale len za predpokladu, ze v nom nepotrebujes order by, potom uz zacina pokryvkavat (ale mozno by to nejaky ten index vylepsil na ordovanym stlpcom:)

Cosi menej efektivnejsim riesenim (ale zase db nezavislym podobne ako sirove) je:

SELECT C.*, P.Number

FROM Contact C

left join PhoneNumber P on P.ContactId=C.ContactId

where P.PhoneNumberId in (select top 1 P2.PhoneNumberId from  PhoneNumber P2 on P2.ContactId=C.ContactId order by P2.PhoneNumberId )

S Indexom nad PhoneNumberId, je ale sirove riesenie (podla queryplanu) efektivnejsie o cca 16% (59% vs 41%).

Jo zamyslat sa nad order by ma vtedy nam zalezi na poradi vrateneho phone number, sice je to v tvojom pripade primary key, ale povedzme, ze mozu platit komplikovanejsie pravidla.

Thursday, August 13, 2009 11:48 AM
# vlko said:

to duracellko: nielen sql server, subqueries v stlpcoch su beznou zalezitostou vsade (podmienka je, aby vracali len jeden riadok, teda bud top 1 alebo agregate funkcie)

Thursday, August 13, 2009 1:23 PM
# duracellko said:

to vlko: no mozno ma to nenapadlo, lebo mi to nepride ciste z relacneho hladiska.. uz vidim, ako by profak na vyske za toto odsekaval ruky :D

Thursday, August 13, 2009 1:57 PM
# vlko said:

tak mam este vylepsenie toho mojho riesenia, ktore dava rovnake vysledky ako tie vase riesenia a podla query plan je najefektivnejsie s indexami aj bez (teda ak sa da query cost relative to the batch nejak verit:):

SELECT C.*, P.Number

FROM Contact C

left join PhoneNumber P on P.ContactId=C.ContactId

where P.PhoneNumberId is null

or P.PhoneNumberId in

(select top 1 P2.PhoneNumberId from  PhoneNumber P2 on P2.ContactId=C.ContactId order by P2.PhoneNumberId )

Friday, August 14, 2009 10:32 AM
# duracellko said:

to vlko: som necakal, ze ta to tak chyti, ze zacnes hladat najlepsi query plan.. v kazdom pripade vdaka za vysledky ;)

Friday, August 14, 2009 4:18 PM
# vlko said:

to duracellko: zasa az tolko casu som tomu nedal, ale ked ide o zaujimavu ulohu, nikdy mi to neda:)

Friday, August 14, 2009 11:14 PM
Prihlásiť | Registrovať | Pomoc