Order by najpierw po u.id. Będziesz wysyłał do jednego usera X maili, gdzie X to liczba kanałów do jakich się zapisał? To się załatwia
jednym mailem zbierającym wszystkie kategorie wiadomości stąd struktura początkowa jaką podałem jest taka a nie inna... To do usera są przyporządkowane kanały, a nie na odwrót. Stąd wydzieliłem jako dwie osobne struktury user-kanał i kanał-wiadomości. Taki podział sprawia, że w mailu w pętli wybieram usera i sprawdzam jakie kanały subskrybował. Wtedy ze struktury kanałów wybieram przypisane do tegoż kanału wiadomości. Poza tym po stronie PHP mam więcej do kontroli w Twoim wariancie, ponieważ muszę kontrolować strukturę userów oraz kategorii. Bo przecież nie wrzucam tego jak leci, ale muszę to jakoś sensownie opakować. To co podałeś ma sens gdy maila tworzymy "in-line", mieszając php z html. Jeśli już myślisz o sensownej strukturze obiektowej, to przecież do widoku nie wyślesz tego co podałeś, bo widok dostanie mnóstwo nadmiarowej informacji, którą jeszcze będzie musiał obrabiać. Inna sprawa, że wrzucanie warunku łączenia do where jest najgorszym możliwym rozwiązaniem. Robisz bowiem najpierw iloczyn kartezjański wszystkich możliwości, które potem redukujesz warunkiem.
EDIT: zulu ma rację... To co Ty proponujesz Wykrywacz to identyczne rozwiązanie z tym co opisałem w swoim pierwszym poście. Jednak to można optymalizować. I wariant optymalizacyjny podałem w swoim kolejnym poście. Liczba zwróconych rekordów zmniejsza Ci się wielokrotnie. Inna sprawa polega na tym, że skrypt w Podanym przez Ciebie i mój pierwszy wariant są bezsensowne na hostingu, gdzie są limity. 30 sekund czasu skryptu + limit maili na godzinę wymusza porcjowanie wyników. To się robi poprzez wrzucenie do zapytania LIMIT, a jak rozpoznasz kiedy go włożyć? Nie wiesz bowiem ile rekordów należy pobrać by nie podzieliło nam w połowie kategorii danego usera czy maili. Może się zdarzyć tak bowiem, że wybrany limit wybierze tylko 4 wiadomości z pierwszej kategorii jakiegoś usera, zaś reszta poszła by drugim w kolejnym porcjowaniu. Bezpieczniej zrobić... Grupowanie kategorii po userze i group_concat numeru kanału lub jego nazwy

Mamy redukcję rekordów poprzez pominięcie niepotrzebnych w takiej sytuacji łączeń. Otrzymujemy zamiast user x kategoria x 10 rekordów w najbardziej i najlepiej optymalizowanym przypadku user + unikalne_kategorie x 10

Wrzućmy liczby... 1500 userów, 10 kategorii, z których średnio liczac userzy wybierają po 2.
Wariant1: (uśredniłem tu tę dwójkę)
1500 x 2 x 10 = 30000 rekordów
Wariant2: (pójdę najmniej korzystnym wariantem, czyli unikalne kategorie stworzą wybranie wszystkich możliwych, a więc 10)
1500 + 10 x 10 = 1600
W przypadku gdy userzy zaczną wybierać po kilka kategorii, nie ząś tylko 2 masz do obrobienia w pętlach 15000 x uśredniona_liczba_kategorii_na_użytkownika dla wariantu1. W wariancie2 nawet zwiększenie liczby wiadomości do z 10 do 20 sprawi śmiesznie niskie zwiększenie liczby operacji (ze 100 do 200). By ilość przekroczyła 3000 rekordów obrabianych musielibyśmy zwiększyć przykładowo liczbę kategorii do 30, a liczbę wiadomości do 50 na kategorię lub 30 wiadomości przy pełnym wykorzystaniu 50 kategorii. To zaś jest niemal nierealne. Przy tych zmienionych parametrach wariant1 obrabiał by już setki tysięcy rekordów. To jest potęga dobrze przemyślanego algorytmu pod kątem wydajnościowym

W pierwszym przypadku na php jest duży nacisk przy selekcji tego na userów oraz kategorie oraz mamy możliwość wysłania maili kilku do jednego usera przy porcjowaniu danych. W drugim przypadku na php też jest nacisk przy obróbce danych, ale ilość przebiegów pętli jest znacząco zredukowana i całość jest odporna na stosowanie LIMIT (jeśli zastosujemy grupowanie). Trochę inne będą operacje po stronie php też. Zamiast pętli pojawi się kilka operacji na tablicach.