Вы сейчас написали очень большое полотно, а могли бы вместо этого помочь автору вопроса, если целью было не просто так воздух сотрясти. Вопрос не в том, знает ли кто-то о джойнах, а о том, как они поведут себя в конкретном инструменте в конкретных условиях. Потому что в тайморм почти ничего не работает, как только ты делаешь шаг вправо или влево от простого сценария
ну во-первых, насколько я понял, автор уже справился с задачей, а во-вторых, «в тайморм почти ничего не работает» – это неправда, в нём как раз работает чуть менее чем всё, а задача «выбрать по many-to-many записи у которых нет связи» решается элементарно благодаря свойствам LEFT JOIN – когда связанных записей нет он джойнит на вектор из NULL-ов, поэтому достаточно добавить в query builder left join на связанную таблицу (а для many-to-many это будет пивот-таблица связи), и в where добавить условие что pivot_table.otherEntityId IS NULL.
то есть задача решается исключительно на уровне знания SQL, а искать решение где-то в апи ORM-ки и, не найдя, жаловаться что «в тайморм почти ничего не работает» – это как раз то про что я и написал «большое полотно»…
TypeORM как раз отличная штука. в ней многие вещи сделаны очень просто и логично, есть несколько уровней абстракции, которые можно выбирать в зависимости от того что нужно – на самом верхнем есть Repository.findXXX() которые минимальными усилиями закрывают 80% потребностей, чуть пониже есть query builder который покрывает ещё 19% и оставшийся 1% решается сырым SQL на EntityManager.query()
в TypeORM есть хитрые моменты – первый это зачем он делает отдельно SELECT DISTINCT перед основным запросом (ответ – для правильной работы LIMIT в условиях выборках с джойнами на релейшены), и второй – чем отличаются разные версии innerJoinXXX и leftJoinXXX в query builder (ответ – тем куда они складывают результат, кладут одну запись или несколько или вообще выбрасывают их, но можно использовать WHERE). всё это решается заглядыванием в исходники…