Uczymy się robić join oraz leftJoin w Laravelu. Powinniśmy to znać, bo to podstawa SQL, ale w dzisiejszych czasach ludzie często zabierają się za frameworki bez poznania podstaw, do dzieła!

Ok, przypomnijmy sobie tę metodę:

Artisan::command('with-address {id}', function (int $id) {

    $person = Person::with('address')->findOrFail($id);

    $this->comment("Name: {$person->firstName} {$person->lastName}");
    $this->comment("Age: {$person->age}");
    $this->comment("Created at: {$person->created_at}");

    if($person->address){
        $this->comment("City: {$person->address->city}");
        $this->comment("Street: {$person->address->street}");
    }
    
});

Mamy tutaj:

  • eager loading relacji address
  • findOrFail, czyli będzie błąd, jak nie istnieje ->first albo ->find
  • find/findOrFail(id) można teoretycznie zamienić na whereKey([$id]) albo whereKey($id) – to już powinniśmy znać
  • Do adresu odwołujemy się przez person->address

Dobra, zróbmy join:

Artisan::command('join', function () {
    $sql = DB::table('people as ppl')
            ->join('addresses as addr', 'ppl.id', '=', 'addr.person_id')
            ->select('ppl.*', 'addr.city', 'addr.street')
            ->get();
    $this->comment($sql);
});

Dużo nam tego wywali, więc możemy użyć albo pętli albo wrzucić gdzieś i przeformatować. Albo debugowanie przez toSql:

Artisan::command('join-inspect', function () {
    $sql = DB::table('people as ppl')
            ->join('addresses as addr', 'ppl.id', '=', 'addr.person_id')
            ->select('ppl.*', 'addr.city', 'addr.street')
            ->toSql();
    $this->comment($sql);
});

Nasz sql wygląda tak:

select `ppl`.*, `addr`.`city`, `addr`.`street` 
from `people` as `ppl` 
inner join 
`addresses` as `addr` 
on `ppl`.`id` = `addr`.`person_id`

Możemy wejść w phpMyAdmin, w tabelkę people, SQL, wkleić i dać go.

Co nam się rzuci w oczy?

  • Po pierwsze, result set zwrócił tylko te rekordy, które pokrywają się po obu stronach, czyli osoby, które posiadają adres, takich, które nie posiadają po prostu nie ma
  • Po drugie, wyniki są (jak w sql przystało) nazwami kolumn (żadnego aliasu nie dawaliśmy), czyli jest city i name na prawo od tabeli lewej. Oznacza to, że odnosilibyśmy się do tych wyników przez $result->city, nie $result->address->city

Tak działa join, tabela po lewej, tabela po prawej, połącz po pk id z lewej odpowiadającej fk nazwa_id po prawej, zwraca wyniki, które sobie odpowiadają.

Jeżeli ktoś chce zabrać wszystkie rekordy people (tabela po lewej) i dołączyć do result setu adresy (jak istnieją, a ja nie to null) to musi wykonać leftJoin:

Artisan::command('leftjoin', function () {

    $sql = DB::table('people as ppl')
            ->leftJoin('addresses as addr', 'ppl.id', '=', 'addr.person_id')
            ->select('ppl.*', 'addr.city', 'addr.street')
            ->get();

    foreach($sql as $person){
        $this->comment("{$person->firstName} {$person->lastName}");
        $this->comment("Age:  {$person->age}");
        if($person->city)
            $this->comment("City: {$person->city}");
        if($person->street)
            $this->comment("Street: {$person->street}");
        $this->comment("----------------------------------");
        
    }
    
});

Left join, czyli:

  • Weź wszystkie wyniki z tabeli po lewej
  • Zrób join (warunek jak przy joinach)
  • Po prawej doklejasz do, co pasuje, jak nie to null

Pamiętamy także, że wynik to jest SQL, zatem miasto będzie pod $result->city, nie pod $result->address->city.

Ok, wykonajmy inspekcję:

rtisan::command('leftjoin-inspect', function () {

    $sql = DB::table('people as ppl')
            ->leftJoin('addresses as addr', 'ppl.id', '=', 'addr.person_id')
            ->select('ppl.*', 'addr.city', 'addr.street')
            ->toSql();

    $this->comment($sql);
    
});

SQL wygląda tak:

select `ppl`.*, `addr`.`city`, `addr`.`street` 
from `people` as `ppl` 
left join 
`addresses` as `addr` 
on `ppl`.`id` = `addr`.`person_id`; 

I kawałek z wyniku (powinniśmy sami to sprawdzić) wygląda tak:

id 	firstName 	lastName 	age 	created_at 	            updated_at 	            city 	street 	
1 	Telly 	    Hermann 	19 	    2024-08-15 12:22:14 	2024-08-16 10:47:32 	London 	London Street
2 	Laney 	    Terry 	    38 	    2024-08-15 12:22:14 	2024-08-16 10:47:32 	London 	London Street
3 	Kaitlyn 	Kertzmann 	36 	    2024-08-15 12:22:14 	2024-08-16 10:47:32 	Warsaw 	Warsaw Street
4 	Corine 	    Auer 	    35 	    2024-08-15 12:22:14 	2024-08-16 10:47:32 	NULL 	NULL
5 	Juana 	    Pfeffer 	19 	    2024-08-15 12:22:14 	2024-08-16 10:47:32 	NULL 	NULL
6 	Camille 	Gulgowski 	27 	    2024-08-15 12:22:14 	2024-08-16 10:47:32 	NULL 	NULL
7 	Thora 	    Conn 	    24 	    2024-08-15 12:22:14 	2024-08-16 10:47:32 	NULL 	NULL
8 	Jakayla 	Hoppe 	    38 	    2024-08-15 12:22:14 	2024-08-16 10:47:32 	NULL 	NULL
9 	Angeline 	Mante 	    43 	    2024-08-15 12:22:14 	2024-08-16 10:47:32 	NULL 	NULL
10 	Elliot 	    Reilly 	    40 	    2024-08-15 12:22:14 	2024-08-16 10:47:32 	East Pansy 	McClure Viaduc

Kolejność wyznaczają pk tabeli lewej, po prawej mamy pasujące wyniki z tabeli prawej albo nulle.