(Ne)ekvivalentie (equi, non-equi) un pašsavienojumi (self join)

Šis ir noslēdzošais raksts sērijā par tabulu kombinēšanas veidiem.

For english speaking only check SQL join types. 

Sākot rakstīt šo rakstu, es sapratu, ka nu ir ziepes 🙂 Man bija izvēle – lietot tikai un vienīgi angliskos terminus vai darboties kā Rainim, tas ir, izdomāt jaunus terminus, jo, ja Inner un Outer Join vēl bija iespēja sameklēt kādu variantu googlē lapās latviešu valodā, tad diemžēl equi, non-equi un self join tajās vienkārši nav. Man diez ko nepatīk abi varianti, bet pirmais vēl mazāk kā otrais, tāpēc informēju, ka mani piedāvātie tulkojumi, kuri tiks tālāk izmantoti arī šajā rakstā, ir šādi:

  • equi join, equijoin, equi-join – ekvivalentais savienojums;
  • non-equi join, non equijoin, non-equi-join – neekvivalentais savienojums;
  • self join, self-join – pašsavienojums.

Šeit runātais nebūs nekas pilnīgi jauns – jau iepriekšējos rakstos par tabulu kombinēšanas veidiem ir minēts vismaz viens piemērs katram šeit apskatītajam veidam.

Terminoloģijas portālā notika arī neliela diskusija par šo te (atbildes var iegūt klikšķinot uz trīsstūrīšiem turp un atpakaļ).

Ekvivalentais savienojums

Lai pārliecinātos, vai savienojuma veids ir Ekvivalentais savienojums, ir jāpaskatās uz kombinēšanas nosacījumu. Ja tajā ir izmantota tikai vienādība, tad tas ir Ekvivalentais savienojums. Šis savienojuma veids vismaz salīdzinot ar Neekvivalento savienojumu ir daudz izplatītāks un iespējams tik pierasts, ka daži pat nespēj iedomāties, ka vispār var būt citādi. Tātad tā jēga ir tāda, ka mēs atlasam ierakstus no divām tabulām, kuriem noteiktu kolonu (iespējams vairāku) vērtības sakrīt.

Piemēriem, kā jau tas pierasts, tiks izmantotas tabulas, kas definētas rakstā par Dekarta reizinājumu. Kā redzams pirmajā piemērā kombinēšanas nosacījumā ir salīdzinātas kolonas prs_adr_id un adr_id – vai to vērtības ir vienādas.

Piemērs 1. Atlasa personas kodu, vārdu un adreses pilsētu katrai personai, kam adrese ir norādīta.
SQL> SELECT prs_personas_kods, prs_vards, adr_pilseta
  2  FROM personas
  3  INNER JOIN adreses ON (prs_adr_id = adr_id);
PRS_PERSONA PRS_VARDS                             ADR_PILSETA
----------- ------------------------------------- ------------
23456789012 PĒTERIS                               TALSI
34567890123 ANNA                                  TALSI
45678901234 KĀRLIS                                TALSI  

Protams, ne jau iekšējais savienojums vien var būt arī ekvivalentais savienojums, tāds var būt arī ārējais savienojums, Nākošajā piemērā ir ņemts tas pats 1, piemērs, kurā vārdiņš Inner nomainīts ar Left outer.

Piemērs 2. Atlasa personas kodu, vārdu un adreses pilsētu katrai personai.
SQL> SELECT prs_personas_kods, prs_vards, adr_pilseta
  2  FROM personas
  3  LEFT OUTER JOIN adreses ON (prs_adr_id = adr_id);
PRS_PERSONA PRS_VARDS                             ADR_PILSETA
----------- ------------------------------------- -----------
12345678910 JĀNIS
23456789012 PĒTERIS                               TALSI
34567890123 ANNA                                  TALSI
45678901234 KĀRLIS                                TALSI

Atgādinu, ka mums ir tādi divi brīnumaini savienojumu veidi kā Dabiskais savienojums un Nosaukto kolonu savienojums, kuros atklātā veidā kombinēšanas nosacījums neparādās. Tomēr tas tur, protams, slēptā veidā ir un tas vienmēr ir Ekvivalentais savienojums. Piemērus šiem savienojumu veidiem skatīt pie abiem šiem savienojumu veidiem.

Neekvivalentais savienojums

Lai cik tas sākumā neliktos dīvaini, tabulu kombinēšanu var veikt arī izmantojot ne tikai vienādības nosacījumu. Piemēram atrast visus pārus no vārdiem, kur pirmais vārds garāks nekā otrais vārds.

Piemērs 3. Atlasa visus personu vārdu pārus, kur pirmais vards garāks nekā otrais.
SQL> SELECT p1.prs_vards, p2.prs_vards
  2  FROM personas p1
  3  JOIN personas p2
  4  ON (length(p1.prs_vards) > length(p2.prs_vards));
PRS_VARDS                                PRS_VARDS
---------------------------------------- -------------
PĒTERIS                                  KĀRLIS
PĒTERIS                                  JĀNIS
PĒTERIS                                  ANNA
KĀRLIS                                   JĀNIS
KĀRLIS                                   ANNA
JĀNIS                                    ANNA

Mazliet mazāk aiz matiem pavilkts piemērs būtu tad, ja ir nepieciešams sadalīt kaut kādas lietas pa grupām, kam ir zināmas dinamiskas augšējās un apakšējās robežas. Piemēram, mūsu jau zināmās personas tiek sadalītas grupās ņemot vērā to vārdus. Lai to izdarītu, ir nepieciešama jauna tabula, kas saturētu šo dinamisko grupēšanas informāciju:

CREATE TABLE grupas (
  grp_nosaukums VARCHAR2(20) NOT NULL,
  grp_min_vert VARCHAR2(1) NOT NULL,
  grp_max_vert VARCHAR2(1) NOT NULL);
INSERT INTO grupas VALUES ('Pirmā grupa', 'A', 'F');
INSERT INTO grupas VALUES ('Otrā grupa', 'G', 'M');
INSERT INTO grupas VALUES ('Trešā grupa', 'N', 'Ž'); 

Tagad atrodam katrai personai tai pienākošos grupu izmantojot operatoru between (personu vārdus, protams, var sakārtot pēc alfabēta, bet jāatceras, ka jāsalīdzina tikai izmantojot vārda pirmo burtu. Ja netiks izmantots pirmais burts, tad vārds FĒLIKSS nenonāks nevienā grupā, jo tas ir lielāks nekā pirmās grupas augšējā robeža F, bet mazāks nekā otrās grupas apakšējā robeža G.): 

Piemērs 4. Atlasam personu vārdus un tai pienākošos grupu, balstoties uz norādītajām grupas robežām.
SQL> SELECT prs_vards, grp_nosaukums
  2  FROM personas
  3  INNER JOIN grupas
  4  ON substr(prs_vards, 1, 1) BETWEEN grp_min_vert AND grp_max_vert;
PRS_VARDS                                GRP_NOSAUKUMS
---------------------------------------- --------------------
ANNA                                     Pirmā grupa
JĀNIS                                    Otrā grupa
KĀRLIS                                   Otrā grupa
PĒTERIS                                  Trešā grupa

Pašsavienojums

Pašsavienojums var vienlaicīgi būt arī jebkurš cits no līdz šim minētajiem savienojumu veidiem. Pašsavienojums vienkārši izceļas uz pārējo savienojumu fona ar to, ka tabula tiek savienota pati ar sevi. Pirmajā brīdī varbūt ir neskaidrība ko tas nozīmē, kā to uzrakstīt un saprast, bet tādā gadījumā var rīkoties šādi:

  • iedomājamies, ka mums ir 2 dažāda nosaukuma tabulas, kurās ir vienāda nosaukuma kolonas un arī vienādi dati;
  • izveidojam nepieciešamo SQL teikumu balstoties uz abām dažādu nosaukumu tabulām un pielietojam tām aizstājējvārdus (alias). Šajā gadījumā taču nekāda nesapratne nerodas, vai ne?
  • Visbeidzot nomainam dažādo tabulu nosaukumus uz vienu. SQL vaicājums vēl joprojām ir korekts, jo mums tika izmantoti aizstājējvārdi, kas tika izmantoti gan kolonu atlasē, gan kombinēšanas nosacījumā, gan citās klauzās, ja tas nepieciešams.

Augstāk redzamais trešais piemērs arī ir pašsavienojums. Nākošais ir pašsavienojuma piemērs, kad katrai personai tiek atlasīti tās vecāki. Tā kā vecāki glabājas šai pašā tabulā un viena ārējā atslēga norāda uz māti un otra uz tēvu, tad mums ir nepieciešams personu tabulu kombinēt ar sevi divreiz.

Piemērs 5. Atlasa personas un to vecākus kombinējot tabulu pašu ar sevi ārējā savienojumā divas reizes.
SQL> SELECT
  2    tevs.prs_vards "Teva Vārds",
  3    mate.prs_vards "Mātes vārds",
  4    berns.prs_vards "Bērna vārds"
  5  FROM personas berns
  6  LEFT JOIN personas tevs ON berns.prs_tevs_id = tevs.prs_id
  7  LEFT JOIN personas mate ON berns.prs_mate_id = mate.prs_id;
Teva Vārds       Mātes vārds         Bērna vārds
---------------- ------------------- ------------
                                     JĀNIS
JĀNIS                                PĒTERIS
                                     ANNA
PĒTERIS          ANNA                KĀRLIS

Protams, jāatceras, ka pašsavienojums kombinē tabulu pašu ar sevi fiksētu reižu skaitu. Ja jums ir rekursīva saite tabulai pašai uz sevi un iepriekš nav zināms, cik reizes rekursija notiek, tad pašsavienojumi jums neder. Tadā gadījumā ir jālieto rekursīvie pieprasījumi, kas, cik man zināms, ir pieejami vismaz Oracle un Microsoft SQL Server. Bet par tiem citreiz 🙂

Un pēdējā piezīme – testēt, protams, vajag visu, bet, ja vien Jūs neesat savienojumu virtuozs, tad noteikti papildus pārliecinieties, ka neekvivalentie savienojumi un pašsavienojumi arī uz reāliem datiem strādā tā kā Jūs to esat iedomājies.

1 Responses to (Ne)ekvivalentie (equi, non-equi) un pašsavienojumi (self join)

  1. Edgars saka:

    Izsmeļošs raksts.
    Oracle atslēgvārdi interesentiem pašsavienojuma izveidošanai neierobežotu reižu skaitu (hierarhiski vaicājumi): CONNECT BY … PRIOR

Komentēt