Poprzednio de facto zrobiliśmy relację has many through many i choć może to dostatecznie nie wybrzmiało, wykonaliśmy konkretną robotę, którą niełatwo jest odtworzyć w czystym SQL. Teraz to zrobimy, poznając lepiej różne relacje Laravela na poziomie tabel.

Ok, przypomnijmy sobie komendę:

Artisan::command('m-tags {id}', function (int $id) {

    $ids_r = DB::table('reviews')
    ->select('id')
    ->where('movie_id', $id)
    ->pluck('id')
    ->toArray(); 

    $ids_t = DB::table('review_tag')
    ->select('tag_id')
    ->whereIn('review_id', $ids_r)
    ->pluck('tag_id')
    ->toArray();

    $tagnames = DB::table('tags')
    ->select('tagname')
    ->whereIn('id', $ids_t)
    ->pluck('tagname')
    ->toArray();

    $movie = Movie::findOrFail($id);

    $tagnamesAsString = implode(", ", $tagnames);
    
    $this->comment("Movie title: {$movie->title}");
    $this->comment("Tags: {$tagnamesAsString}");

    
    
});

Rzućmy okiem na tabele, przypomnę je:

  • movies, zero kluczy obcych, model hasMany względem reviews
  • reviews, klucz obcy movie_id, belongsTo względem movies, belongsToMany względem tags
  • tags, zero kluczy obcych, belongsToMany względem reviews
  • review_tag – piwotalna, każdy rekord ma id (nieistotne dla nas) oraz klucze obce review_id i tag_id łączące reviews i tags w relację many to many

Ok, najpierw coś prostego, wszystkie tagi:

SELECT t.tagname FROM `tags` as t;

Teraz wszystkie pary w tabeli piwotalnej:

SELECT pivot.tag_id, pivot.review_id FROM `review_tag` as pivot; 

Teraz wszystkie pary tag-recenzja, pod warunkiem, że recenzja wskazuje na film o id 1:

SELECT 
pivot.tag_id, 
pivot.review_id 
FROM `review_tag` as pivot 
where 
pivot.review_id in 
(
    select r.id 
    from `reviews` as r 
    where r.movie_id = 1
    ) ; 

Teraz do nas dotrze, że review_id nie jest potrzebne w result secie, tylko w warunku:


SELECT 
pivot.tag_id
FROM `review_tag` as pivot 
where 
pivot.review_id in 
(
    select r.id 
    from `reviews` as r 
    where r.movie_id = 1
    ) ; 

Nie chcemy, żeby tag_id się powtarzało, jeden tag to jest jeden tag:

SELECT DISTINCT 
pivot.tag_id 
FROM `review_tag` as pivot 
where pivot.review_id in 
( 
    select r.id 
    from `reviews` as r 
    where r.movie_id = 2 
); 

To teraz unikatowe nazwy tagów dla takiego filmu, którego recenzje… Po prostu to zróbmy i przeanalizujmy:

SELECT 
t.tagname 
from `tags` as t 
where t.id in 
( 
    SELECT DISTINCT 
    pivot.tag_id 
    FROM `review_tag` as pivot 
    where pivot.review_id in 
    ( 
        select r.id 
        from `reviews` as r 
        where r.movie_id = 1 
    ) 
);

Ok, teraz zaciągamy dwie tabelki, jedna z tagami, druga z informacjami o filmie:

SELECT 
t.tagname 
from `tags` as t 
where t.id in 
( 
    SELECT DISTINCT 
    pivot.tag_id 
    FROM `review_tag` as pivot 
    where pivot.review_id in 
    ( 
        select r.id 
        from `reviews` as r 
        where r.movie_id = 1 
    ) 
);

SELECT * from `movies` WHERE id = 1;

Troszkę oszukujemy, są dwa result sety. Jeżeli mimo wszystko ogarniamy, co tu się dzieje, to już jest dobrze.

To teraz tak – movie o id 2 i tagi z recenzji dla tego filmu, o id 2:

SELECT 
t.tagname 
from `tags` as t 
where t.id in 
( 
    SELECT DISTINCT 
    pivot.tag_id 
    FROM `review_tag` as pivot 
    where pivot.review_id in 
    ( 
        select r.id 
        from `reviews` as r 
        where r.movie_id = 2 
    ) 
);

SELECT * from `movies` WHERE id = 2;

Wyniki porównujemy z naszymi komendami. Ok, teraz pora zrobić jeden result set.

SELECT 
m.*, 
( 
    SELECT GROUP_CONCAT(t.tagname) 
    from `tags` as t 
    where t.id in 
    ( 
        SELECT DISTINCT 
        pivot.tag_id 
        FROM `review_tag` as pivot 
        where pivot.review_id in 
        ( 
            select r.id 
            from `reviews` as r 
            where r.movie_id = 2 
        ) 
    ) 
    GROUP BY 'all'
) as 'tags'  
from `movies` as m 
where m.id = 2; 

PS. Chciałem to etapowo wprowadzić, najpierw implode samych tagów, ale blog odmówił posłuszeństwa (JSON error). Można niby gutenberga wyłączyć i w starym dodawać bloki, ale aż tak się dla was poświęcać nie będę.