INNER JOIN
Внутреннее присоединение. Равносильно просто JOIN или CROSS JOIN (верно для MYSQL, в стандарте SQL INNER JOIN не эквивалентен синтаксически CROSS JOIN, т.к. используется с выражением ON).
SELECT id_person, name, id_pos, title
FROM `persons`
INNER JOIN `positions` ON id_pos = position_ref
1 |
SELECT id_person,name,id_pos,title FROM`persons` INNER JOIN`positions`ON id_pos=position_ref
|
Такое присоединение покажет нам данные из таблиц, только если условие связывания соблюдается — т.е. для сотрудника указан существующий в словаре идентификатор должности.
Если поменять порядок соединения таблиц — получим тот же результат.
Условно представим себе эти таблицы, как пересекающиеся множества, где пересечение — это наличие связи между таблицами. Получим картинку:
Далее проследим как получить разные части (подмножества) данного множества.
Оператор UNION – Объединение
Для объединения запросов используется следующий синтаксис:
По умолчанию оператор удаляет повторяющиеся строки из результирующего набора. Если указан параметр ALL, то результат будет содержать все строки, в том числе повторяющиеся.
Объединяемые запросы должны быть совместимы между собой. В терминологии SQL это обозначает, что:
- Таблицы должны иметь одинаковое количество столбцов.
- Типы данных в соответствующих столбцах должны быть совместимыми.
Объединяемые запросы должны быть совместимы между собой. В терминологии SQL это обозначает, что:
- Таблицы должны иметь одинаковое количество столбцов.
- Типы данных в соответствующих столбцах должны быть совместимыми.
При объединении данных из столбцов с разными именами результирующему столбцу присваивается имя столбца из первого запроса.
К результату объединения рекомендуется применять предложение , где можно ссылаться только на имена столбцов левого запроса в операторе .
Пример 4.Пусть задана таблица P1.
Номер | Наименование |
---|---|
5 | Орлов |
Определим результат следующего объединения:
Номер | Наименование |
---|---|
1 | Иванов |
5 | Орлов |
Пример 5.Получить номера деталей, цена которых более 20 рублей или суммарное поставляемое количество более 500 штук.
Запрос разбивается на две части:
- Вывод номеров деталей, цена которых более 200 рублей.
- Вывод номеров деталей, которые поставляются в количестве более 500 штук.
Результирующая таблица получается при объединении двух частей запроса.
Пример 6.Вывести информацию о деталях. В том случае если цена детали не указана вывести ‘цены нет’.
Запрос разбивается на две части:
- Вывод информации о деталях, для которых указана цена.
- Вывод информации о деталях, для которых не указана цена. В этом случае в предложении вместо атрибута dprice нужно указывать строковую константу ‘цены нет’.
Когда можно пренебречь скобками
Скобки вокруг функционального выражения обычно принуждают функцию к виду выражения, вместо обычного объявления функции.
Но когда JavaScript движку понятно, что это функциональное выражение и нам технически просто не нужны оборачивающие скобки, как показано в примере ниже.
В примере выше, слово это не первое слово в объявлении. Поэтому JavaScript не воспринимает его, как указание функции. Также есть и другие случаи, в которых вы можете избежать кавычек, когда вы знаете то, что это выражение.
Но даже в этом случае я пытаюсь использовать скобки. Они улучшают читабельность, прямо с первых строк стилистически подсказывая человеку, что это будет IIFE. И никому не надо будет скроллить к последней строке функции, чтобы понять то, что только что они прочитали не простую функцию, а именно IIFE.
Это всё, что вам нужно знать для начала работы с IIFE. Они не только помогают организовывать и изображать ваш код в приятной и понятной манере, они также помогают сократить количество багов, избегая создания совершенно ненужных элементов глобальной области видимости.
Ну всё, теперь вы, можно сказать, что сертифицированный IIFE ниндзя!
Демонстрация N+1 проблемы
Для этого заполним базу пятью топиками с пятью комментариями (по одному на каждый) в методе @BeforeAll — то есть перед всеми тестами мы заполнили базу.
А затем просто выполним select для комментариев:
select c from Comment c
Заполнение и выборка представлены двумя методами: @BeforeAll, выполняющимся перед тестом, и самим тестом @Test:
public class NPlus1Test { @BeforeAll private static void createTopics() { HibernateUtil.doInHibernate(session -> { for (int i = 0; i < 5; i++) { Topic topic = new Topic("topic" + i); Comment comment = new Comment("comment" + i); comment.setTopic(topic); session.persist(comment); session.persist(topic); } }); } @Test @DisplayName("если fetch = FetchType.EAGER, то получаем в консоли N+1 select: 5+1") public void whenEager_thenNplus1Problem() { HibernateUtil.doInHibernate(session -> { Query<Comment> query = session.createQuery("select c from Comment c"); List<Comment> comments = query.getResultList(); Assertions.assertEquals(5, comments.size()); comments.forEach(comment -> System.out.println(comment.getText() + " " + comment.getTopic().getTitle())); }); } }
В консоли для самого теста получим:
Hibernate: select comment0_.id as id1_0_, comment0_.text as text2_0_, comment0_.topic_id as topic_id3_0_ from Comment comment0_ Hibernate: select topic0_.id as id1_1_0_, topic0_.title as title2_1_0_ from Topic topic0_ where topic0_.id=? Hibernate: select topic0_.id as id1_1_0_, topic0_.title as title2_1_0_ from Topic topic0_ where topic0_.id=? Hibernate: select topic0_.id as id1_1_0_, topic0_.title as title2_1_0_ from Topic topic0_ where topic0_.id=? Hibernate: select topic0_.id as id1_1_0_, topic0_.title as title2_1_0_ from Topic topic0_ where topic0_.id=? Hibernate: select topic0_.id as id1_1_0_, topic0_.title as title2_1_0_ from Topic topic0_ where topic0_.id=? comment0 topic0 comment1 topic1 comment2 topic2 comment3 topic3 comment4 topic4
То есть мы написали один select, а в консоли видно, что на самом деле их выполняется 6. В этом и состоит проблема.
Происходит так потому, что для каждого комментария (а их выбралось пять в первом select-е) Hibernate под капотом выполняет дополнительный select для заполнения поля Topic. То есть делает select из Topic. В конце для наглядности мы выводим значения comment.text и comment.topic.title.
Встает вопрос, как избавиться от проблемы: как сократить количество неожиданных select-ов, либо вовсе их убрать?
Может поставить fetch = FetchType.LAZY?
Если сменить для поля topic сущности Comment стратегию извлечения, то есть поставить над полем аннотацию fetch = FetchType.LAZY (ленивое извлечение), то сразу после выполнения запроса — то есть после
query.getResultList())
поле Topic не заполняется и соответствующий select … from topic не выполняется. То есть сразу N+1 проблема не возникнет.
Но когда значение поля Topic нам понадобится (а рано или поздно наверняка понадобится), и мы к нему обратимся с помощью:
comment.getTopic()
дополнительный select все же выполнится — просто это произойдет позже.
Так как же этого избежать? Есть выход — join fetch. С помощью него мы сделаем в одном запросе выборку из обеих таблиц сразу.
Всё кроме пересечения
Остался один вариант, тот когда исключено пересечение множеств. Его можно сложить из двух предыдущих запросов через UNION ALL (т.к. подмножества не пересекаются).
(SELECT id_person, name, id_pos, title
FROM persons
LEFT OUTER JOIN positions ON id_pos = position_ref
WHERE id_pos is NULL)
UNION ALL
(SELECT id_person, name, id_pos, title
FROM persons
RIGHT OUTER JOIN positions ON id_pos = position_ref
WHERE id_person is NULL)
1 |
(SELECT id_person,name,id_pos,title FROM persons LEFT OUTER JOIN positions ON id_pos=position_ref WHERE id_pos isNULL) UNION ALL (SELECT id_person,name,id_pos,title FROM persons RIGHT OUTER JOIN positions ON id_pos=position_ref WHERE id_person isNULL)
|
Это запрос соберет все случаи, когда по какой то причине данные из таблиц не связаны.
А графически такое объединение выглядит следующим образом:
Кейс. Модели атрибуции
Благодаря модели атрибуции можно обоснованно оценить вклад каждого канала в достижение конверсии. Давайте попробуем посчитать две разных модели атрибуции с помощью оконных функций.
У нас есть таблица с id посетителя (им может быть Client ID, номер телефона и тп.), датами и количеством посещений сайта, а также с информацией о достигнутых конверсиях.
Первый клик
В Google Analytics стандартной моделью атрибуции является последний непрямой клик. И в данном случае 100% ценности конверсии присваивается последнему каналу в цепочке взаимодействий.
Попробуем посчитать модель по первому взаимодействию, когда 100% ценности конверсии присваивается первому каналу в цепочке при помощи функции FIRST_VALUE.
SELECT Date , Client_ID , Medium , FIRST_VALUE(Medium) OVER(PARTITION BY Client_ID ORDER BY Date) AS 'First_Click' , Sessions , Conversions FROM Orders
Рядом со столбцом «Medium» появился новый столбец «First_Click», в котором указан канал в первый раз приведший посетителя к нам на сайт и вся ценность зачтена данному каналу.
Произведем агрегацию и получим отчет.
WITH First AS ( SELECT Date , Client_ID , Medium , FIRST_VALUE(Medium) OVER(PARTITION BY Client_ID ORDER BY Date) AS 'First_Click' , Sessions , Conversions FROM Orders ) SELECT First_Click , SUM(Conversions) AS 'Conversions' FROM First GROUP BY First_Click
С учетом давности взаимодействий
В этом случае работает правило: чем ближе к конверсии находится точка взаимодействия, тем более ценной она считается. Попробуем рассчитать эту модель при помощи функции DENSE_RANK.
SELECT Date , Client_ID , Medium -- Присваиваем ранг в зависимости от близости к дате конверсии , DENSE_RANK() OVER(PARTITION BY Client_ID ORDER BY Date) AS 'Ranks' , Sessions , Conversions FROM Orders
Рядом со столбцом «Medium» появился новый столбец «Ranks», в котором указан ранг каждой строки в зависимости от близости к дате конверсии.
Теперь используем этот запрос для того, чтобы распределить ценность равную 1 (100%) по всем точкам на пути к конверсии.
SELECT Date , Client_ID , Medium -- Делим ранг определенной строки на сумму рангов по пользователю , ROUND(CAST(DENSE_RANK() OVER(PARTITION BY Client_ID ORDER BY Date) AS FLOAT) / CAST(SUM(ranks) OVER(PARTITION BY Client_ID) AS FLOAT), 2) AS 'Time_Decay' , Sessions , Conversions FROM ( SELECT Date , Client_ID , Medium -- Присваиваем ранг в зависимости от близости к дате конверсии , DENSE_RANK() OVER(PARTITION BY Client_ID ORDER BY Date) AS 'Ranks' , Sessions , Conversions FROM Orders ) rank_table
Рядом со столбцом «Medium» появился новый столбец «Time_Decay» с распределенной ценностью.
И теперь, если сделать агрегацию, можно увидеть как распределилась ценность по каналам.
WITH Ranks AS ( SELECT Date , Client_ID , Medium -- Делим ранг определенной строки на сумму рангов по пользователю , ROUND(CAST(DENSE_RANK() OVER(PARTITION BY Client_ID ORDER BY Date) AS FLOAT) / CAST(SUM(ranks) OVER(PARTITION BY Client_ID) AS FLOAT), 2) AS 'Time_Decay' , Sessions , Conversions FROM ( SELECT Date , Client_ID , Medium -- Присваиваем ранг в зависимости от близости к дате конверсии , DENSE_RANK() OVER(PARTITION BY Client_ID ORDER BY Date) AS 'Ranks' , Sessions , Conversions FROM Orders ) rank_table ) SELECT Medium , SUM(Time_Decay) AS 'Value' , SUM(Conversions) AS 'Conversions' FROM Ranks GROUP BY Medium ORDER BY Value DESC
Из получившегося отчета видно, что самым весомым каналом является канал «cpc», а канал «cpa», который был бы исключен при применении стандартной модели атрибуции, тоже получил свою долю при распределении ценности.
Полезные ссылки:
- SELECT — предложение OVER (Transact-SQL)
- Как работать с оконными функциями в Google BigQuery — подробное руководство
- Модель атрибуции на основе онлайн/офлайн данных в Google BigQuery
Роман Романчук
Digital-аналитик и иногда спортсмен.
- Учимся применять оконные функции — 29.09.2020
- Автоматизация отчетности при помощи SQL и Power BI — 05.04.2020
- Зачем аналитику нужно программирование на SQL? — 22.10.2019
Так, всё! Показывайте мне уже IIFE или я сваливаю!
Спасибо за терпение при объяснении этого важнейшего скилла, который так важен в JavaScript.
Теперь, когда вы узнали про функциональные выражения и объявления функций, давайте погрузимся в тайный мир IIFE, мир немедленно вызываемых функций. Они могут иметь несколько стилистический вариаций. Сначала посмотрим на самую простую.
Это, друзья мои, наша ненаглядная IIFE функция в действии. Когда вы скопируете этот код и вставите его в консоль браузера, вы увидите из второй строчки кода. Вот и всё, собственно. Никто никогда не увидит этот снова.
А теперь давайте разберем этот не совсем интуитивно понятный синтаксис: я знаю, вы обратили внимание на “”, если же нет, то не переживайте, вы обратили внимание на это сейчас. Как мы видели до этого, объявление функции всегда начиналось со слова
Всякий раз, когда JavaScript видит слово , как вводное слово, он ожидает того, что сейчас будет объявлена функция. Чтобы этого не случилось, мы префиксим “!” перед словом function на первой строке. Это просто подталкивает JavaScript рассматривать всё, что идёт после восклицательного знака, как выражение
Как мы видели до этого, объявление функции всегда начиналось со слова . Всякий раз, когда JavaScript видит слово , как вводное слово, он ожидает того, что сейчас будет объявлена функция. Чтобы этого не случилось, мы префиксим “!” перед словом function на первой строке. Это просто подталкивает JavaScript рассматривать всё, что идёт после восклицательного знака, как выражение.
Но самое интересное происходит на 3 сроке, где мы уже мгновенно вызываем это выражение.
Вообще, в такой вариации можно использовать не только “!”, а заменить его на , или . В общем, подойдет любой унарный оператор.
А теперь, попробуйте это всё в консоли. Поиграйтесь с IIFE в своё удовольствие.
Ещё один быстрый пример:
И снова, просто принуждает функцию к тому, чтобы ее рассматривали как выражение.
Все подходы описанные выше могут быть полезны тогда, когда вам не нужно получать никакого значения от IIFE.
Но что делать, если вам нужно его получить и ещё потом где-то использовать? Читайте дальше, чтобы увидеть ответ на этот вопрос.
Полное множество
MySQL не знает соединения FULL OUTER JOIN. Что если нужно получить полное множество?
Первый способ — объединение запросов LEFT и RIGHT.
(SELECT id_person, name, id_pos, title
FROM persons
LEFT OUTER JOIN positions ON id_pos = position_ref)
UNION
(SELECT id_person, name, id_pos, title
FROM persons
RIGHT OUTER JOIN positions ON id_pos = position_ref)
1 |
(SELECT id_person,name,id_pos,title FROM persons LEFT OUTER JOIN positions ON id_pos=position_ref) (SELECT id_person,name,id_pos,title FROM persons RIGHT OUTER JOIN positions ON id_pos=position_ref)
|
При таком вызове UNION, после слияния результатов, SQL отсечет дубли (как DISTINCT). Для отсечения дублей SQL прибегает к сортировке. Это может сказываться на быстродействии.
Второй способ — объединение LEFT и RIGHT, но в одном из запросов мы исключаем часть, соответствующую INNER. А объединение задаём как UNION ALL, что позволяет движку SQL обойтись без сортировки.
(SELECT id_person, name, id_pos, title
FROM persons
LEFT OUTER JOIN positions ON id_pos = position_ref)
UNION ALL
(SELECT id_person, name, id_pos, title FROM persons
RIGHT OUTER JOIN positions ON id_pos = position_ref
WHERE id_person is NULL)
1 |
(SELECT id_person,name,id_pos,title FROM persons LEFT OUTER JOIN positions ON id_pos=position_ref) UNION ALL (SELECT id_person,name,id_pos,title FROM persons RIGHT OUTER JOIN positions ON id_pos=position_ref WHERE id_person isNULL)
|
Этот пример показывает нам как исключить пересечение и получить только левую или правую часть множества.
CROSS JOIN
У него и персональное название есть — декартово произведение. Для двух множеств в результате CROSS JOIN получаются все возможные пары, в каждой из которых будет представитель одного и второго множества.
— Стоп! А где же тут соединение по ключу: ON D.Key=T.Key?
— В том-то и дело, что мы составляем пары, не обращая внимания на ключи, просто каждый элемент первой группы сопоставляем с каждым из второй.
— Звучит как-то сложно. Зачем вообще могут понадобиться такие пары? Для фильмов это бессмыслица какая-то получится.
— Пожалуй. Давай возьмём пример ближе к жизни. Предположим, есть магазин одежды, и мы хотим составить для него таблицу размеров одежды, но с учётом её цвета. То есть нужны все возможные комбинации размер + цвет. Это и достигается с помощью CROSS JOIN.
А схематично изобразить это можно вот так:
Декартово произведение множеств
CROSS JOIN (перекрёстное объединение) возвращает декартово произведение: все возможные комбинации соединения записей из первой и второй таблиц.
— Ок, с джойнами теперь всё ясно. Остался только один вопрос.
— И какой же?
LEFT JOIN
Подойдёт, если:
- я хочу выбрать датские фильмы;
- и согласна, что среди них могут быть триллеры;
- но мне не интересны триллеры производства других стран, только датские.
Вот так будет выглядеть SQL-запрос:
И подобающая случаю диаграмма:
Датские любых жанров, а из триллеров только датские
LEFT JOIN (левое внешнее объединение) — то же самое, что LEFT OUTER JOIN.
В результат попадают совпадающие по ключу данные обеих таблиц и все записи из левой таблицы, для которых не нашлось пары в правой.
— А что, если я вообще не фанат триллеров, но датские фильмы мне интересны?
— Тогда к скрипту выше нужно дописать одно условие:
Дословно T.Key IS NULL означает, что нужно включить в результат только записи, в которых значение ключа для записей из множества триллеров пусто.
— Как это пусто? Ведь у фильма не может быть пустой номер!
— Верно. Думай об этом не как о едином множестве фильмов, а как о парах фильмов из двух групп. Мы берём один фильм из первой группы (датские) и ищем во второй группе (триллеров) для него пару — фильм с таким же номером.
Если пара найдётся (значит, попался датский триллер) — считаем, что T.Key не пустой, иначе он как раз и будет IS NULL.
Диаграмма теперь выглядит так:
Датские — все, кроме триллеров. Триллеры полностью исключаем
Виды функций
Оконные функции можно подразделить на следующие группы:
- Агрегатные функции;
- Ранжирующие функции;
- Функции смещения;
- Аналитические функции.
В одной инструкции SELECT с одним предложением FROM можно использовать сразу несколько оконных функций. Давайте подробно разберем каждую группу и пройдемся по основным функциям.
Агрегатные функции
Агрегатные функции – это функции, которые выполняют на наборе данных арифметические вычисления и возвращают итоговое значение.
- SUM – возвращает сумму значений в столбце;
- COUNT — вычисляет количество значений в столбце (значения NULL не учитываются);
- AVG — определяет среднее значение в столбце;
- MAX — определяет максимальное значение в столбце;
- MIN — определяет минимальное значение в столбце.
Пример использования агрегатных функций с оконной инструкцией OVER:
SELECT Date , Medium , Conversions , SUM(Conversions) OVER(PARTITION BY Date) AS 'Sum' , COUNT(Conversions) OVER(PARTITION BY Date) AS 'Count' , AVG(Conversions) OVER(PARTITION BY Date) AS 'Avg' , MAX(Conversions) OVER(PARTITION BY Date) AS 'Max' , MIN(Conversions) OVER(PARTITION BY Date) AS 'Min' FROM Orders
Ранжирующие функции
Ранжирующие функции – это функции, которые ранжируют значение для каждой строки в окне. Например, их можно использовать для того, чтобы присвоить порядковый номер строке или составить рейтинг.
- ROW_NUMBER – функция возвращает номер строки и используется для нумерации;
- RANK — функция возвращает ранг каждой строки. В данном случае значения уже анализируются и, в случае нахождения одинаковых, возвращает одинаковый ранг с пропуском следующего значения;
- DENSE_RANK — функция возвращает ранг каждой строки. Но в отличие от функции RANK, она для одинаковых значений возвращает ранг, не пропуская следующий;
- NTILE – это функция, которая позволяет определить к какой группе относится текущая строка. Количество групп задается в скобках.
SELECT Date , Medium , Conversions , ROW_NUMBER() OVER(PARTITION BY Date ORDER BY Conversions) AS 'Row_number' , RANK() OVER(PARTITION BY Date ORDER BY Conversions) AS 'Rank' , DENSE_RANK() OVER(PARTITION BY Date ORDER BY Conversions) AS 'Dense_Rank' , NTILE(3) OVER(PARTITION BY Date ORDER BY Conversions) AS 'Ntile' FROM Orders
Функции смещения
Функции смещения – это функции, которые позволяют перемещаться и обращаться к разным строкам в окне, относительно текущей строки, а также обращаться к значениям в начале или в конце окна.
- LAG или LEAD – функция LAG обращается к данным из предыдущей строки окна, а LEAD к данным из следующей строки. Функцию можно использовать для того, чтобы сравнивать текущее значение строки с предыдущим или следующим. Имеет три параметра: столбец, значение которого необходимо вернуть, количество строк для смещения (по умолчанию 1), значение, которое необходимо вернуть если после смещения возвращается значение NULL;
- FIRST_VALUE или LAST_VALUE — с помощью функции можно получить первое и последнее значение в окне. В качестве параметра принимает столбец, значение которого необходимо вернуть.
SELECT Date , Medium , Conversions , LAG(Conversions) OVER(PARTITION BY Date ORDER BY Date) AS 'Lag' , LEAD(Conversions) OVER(PARTITION BY Date ORDER BY Date) AS 'Lead' , FIRST_VALUE(Conversions) OVER(PARTITION BY Date ORDER BY Date) AS 'First_Value' , LAST_VALUE(Conversions) OVER(PARTITION BY Date ORDER BY Date) AS 'Last_Value' FROM Orders
Аналитические функции
Аналитические функции — это функции которые возвращают информацию о распределении данных и используются для статистического анализа.
- CUME_DIST — вычисляет интегральное распределение (относительное положение) значений в окне;
- PERCENT_RANK — вычисляет относительный ранг строки в окне;
- PERCENTILE_DISC — вычисляет определенный процентиль для отсортированных значений в наборе данных. В качестве параметра принимает процентиль, который необходимо вычислить.
Важно! У функций PERCENTILE_CONT и PERCENTILE_DISC, столбец, по которому будет происходить сортировка, указывается с помощью ключевого слова WITHIN GROUP
SELECT Date , Medium , Conversions , CUME_DIST() OVER(PARTITION BY Date ORDER BY Conversions) AS 'Cume_Dist' , PERCENT_RANK() OVER(PARTITION BY Date ORDER BY Conversions) AS 'Percent_Rank' , PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY Conversions) OVER(PARTITION BY Date) AS 'Percentile_Cont' , PERCENTILE_DISC(0.5) WITHIN GROUP (ORDER BY Conversions) OVER(PARTITION BY Date) AS 'Percentile_Disc' FROM Orders
Классический модульный паттерн в JavaScript
Теперь, когда вы отточили навыки работы с IIFE, давайте посмотрим на пример модульного паттерна, который раскрывают всю мощь совместного применения замыканий и IIFE функций.
Тут мы применим классический синглтон объект , в котором всё стабильно отрабатывает без возможности непреднамеренного изменения значения.
Мы разделим код на два шага, чтобы постепенно понять то, что происходит под капотом.
В этом примере, наша IIFE отдаёт объект. Смотрите строки 7 и 8.
Также тут есть локальная переменная под названием .
Отдаваемое IIFE значение, которое является объектом и назначается на переменную .
А теперь давайте усложним код, добавив несколько функций в отдаваемый нами объект.
В этом примере мы добавили две функции нашему отдаваемому объекту.
Строки 8–10 хранят функцию , которая отдаёт значение из переменной .
Строки 12–15 добавляют функцию , которая увеличивает значение на 1 и затем отдаёт его значение.
Так как переменная является приватной в IIFE, то никто кроме функций, имеющих доступ к IIFE через замыкание, не имеет к ней доступа.
Теперь вы узнали реально мощный паттерн JavaScript. Он включает в себя мощь использования IIFE и замыканий вместе.
Это очень простая вариация модульного паттерна. Есть и другие паттерны такого плана, но почти все из них используют IIFE для создания закрытой области видимости.
LEFT JOIN / RIGHT JOIN / FULL JOIN
LEFT JOIN, RIGHT JOIN и FULL JOIN считаются внешними соединениями (OUTER JOIN), поэтому у них также есть синонимы: LEFT OUTER JOIN, RIGHT OUTER JOIN и FULL OUTER JOIN.
LEFT JOIN и RIGHT JOIN отличаются от INNER JOIN тем, что к результирующей таблице добавляются строки не имеющие совпадений в соседней таблице. Если используется LEFT JOIN, добавляются все записи из таблицы указанной по левую сторону от оператора, если RIGHT JOIN, то из таблицы по правую сторону от оператора. В пару к таким строкам устанавливается значение NULL. Оба оператора не возможно использовать без какого-либо условия.
Это используется если, к примеру, надо вывести все доступные бренды машин, не зависимо от того указан у них цвет или нет:
Или все возможные цвета, независимо от того есть ли у брендов такой цвет в наличии:
Можно дополнить запрос условием на проверку несуществования соседних данных, и получить список записей, которые не имеют пары, при этом поля, которые необходимо вывести, можно указать, как и при обычном SELECT запросе:
FULL JOIN объединяет в себе LEFT JOIN и RIGHT JOIN.
В MySQL он используется без условий, результат использования этого оператора будет таким:
Но, при добавлении сравнения USING в MySQL, результат будет аналогичен INNER JOIN:
Другие условия с оператором FULL JOIN в MySQL использовать нельзя, по крайней мере на момент написания статьи.
RIGHT JOIN
Если день не задался и смотреть что-то доброе и вечное настроения нет, можно установить фильтр для отбора только триллеров. И пусть даже среди них будут датские, но вот другие категории датских фильмов рассматривать не будем.
Вот как это выглядит на диаграмме:
Триллеры любых стран, а из датских фильмов — только триллеры
— Подожди, ну не настолько же всё плохо — давай хотя бы датские триллеры исключим. Мне кажется, я даже догадываюсь, как это сделать:
— Совершенно верно! Наверняка и диаграмма для этого случая тебя не удивит:
Триллеры, но только не датские. Датских фильмов вообще не нужно.
RIGHT JOIN (правое внешнее соединение) — то же самое, что RIGHT OUTER JOIN.
В результат объединения попадают совпадающие по ключу записи обеих таблиц и все данные из правой таблицы, для которых не нашлось пары в левой.
Функциональные выражения
Тут уже все становится интереснее. Давайте посмотрим на то, как выглядят функциональные выражения в JavaScript.
Этот вроде бы простой пример может помочь вам прокачать скиллы в JavaScript до следующего уровня.
На первой строке объявляется переменная и ей назначается строковое значение.
Строки 2–4 объявляют переменную и назначают ей значение, являющееся функцией.
На шестой строке мы вызываем эту самую функцию .
Первую строку легко понять. Но когда разработчики видят строки 2–4 в первый раз, то это обычно вызывает затруднения, если они зашли из другого языка программирования, например из Java.
Вы возможно уже использовали их, но без понимания того, как это работает под капотом. Но поняв принцип их работы, вы обретёте некую скрытую суперсилу, доступную в JavaScript.
Модель (Сущности)
Пример простой. Как уже говорилось, нас есть комментарии. Каждый из них относится к какому-то то топику, то есть отношение ManyToOne:
Таблицы в базе
Класс Comment:
@Data @NoArgsConstructor @Entity public class Comment { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE) private long id; private String text; @ManyToOne//(fetch = FetchType.LAZY) private Topic topic; public Comment(String text){ this.text=text; } }
Класс Topic:
@Data @NoArgsConstructor @Entity public class Topic { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE) private long id; private String title; public Topic(String title){ this.title=title; } }
Если не указать стратегию явно, для поля topic подразумевается стратегия fetch = FetchType.EAGER. (Эта стратегия считается стратегией по умолчанию для всех полей, аннотированных @ManyToOne). Это означает, что при выборе комментариев (select c from Comment c) Hibernate будет заполнять значением поле topic. Для этого он выполнит дополнительный select для каждого комментария. А значит, возникнет n+1 проблема.
Продемонстрируем это в тесте.
Классический стиль IIFE
Паттерн IIFE, который мы видели выше, довольно легко понять. Поэтому я начал с него, а не с других, куда более часто используемых подходов.
Сначала давайте посмотрим на ещё один пример функционального выражения!
В примере выше функциональное выражение на строках 1–3 обернуто в скобки. Пока что, это не IIFE, так как это функциональное выражение никогда не запускалось и не запуститься. А теперь давайте доделаем этот код и превратим его в IIFE, для этого у нас есть два варианта стилизации:
Теперь у нас есть две рабочие IIFE. Может быть немного тяжеловато подметить разницу между ними. Так что давайте я вам сейчас её объясню.
В первом варианте, на строке 4, скобки для запуска функционального выражения содержат еще и внешние скобки. Они нужны для того, чтобы создать функцию за пределами этой функции.
Во втором варианте, на строке 9, скобки для запуска функционального выражения находятся за пределами оборачивающих функцию кавычек.
Оба вариант широко используют. Лично я предпочитаю первый. Если мы начнем придираться и капнем глубже, то оба варианта слегка отличаются друг от друга в плане работы под капотом. Но для практических целей и для того, чтобы хоть как-то сократить этот туториал, то скажу я вам так — используйте просто любой из этих способов на ваше усмотрение.
Давайте снова разберем всё на рабочих примерах. Мы начнем с раздачи имён нашим IIFE, так как использование анонимных функций никогда не было хорошей идеей в этом случае.
Теперь вы знаете почему странные обертывающие скобки вокруг функционального выражения нужны в паттерне IIFE.
Запомните, вам нужно функциональное выражение, чтобы сформировать IIFE. Объявления/определения функции никогда не используются для создания IIFE.
INNER JOIN / CROSS JOIN
В некоторых SQL базах INNER JOIN не может идти без условия, но в MySQL это возможно, поэтому INNER JOIN и CROSS JOIN в данной SQL системе идентичны, как и JOIN, который является синонимом для INNER JOIN.
Простая выборка, без условий, подставит ко всем вариантам из левой таблицы, все варианты из правой таблицы (перекрестное соединение):
Тот же самый результат можно получить путем следующих записей, которые идентичны:
К выборке можно добавить условие, это актуально как для CROSS, так и для INNER JOIN. Выборку можно производить следующими способами:
- USING — если в условии участвуют столбцы с одинаковым названием. Не возможно использовать при перечислении таблиц через запятую.
- ON — если сопоставляются столбцы с разным названием. Фильтрация этой командой происходит до того как сопостовляются строки таблицы. Не возможно использовать при перечислении таблиц через запятую.
- WHERE — если сопоставляются столбцы с разным названием. Фильтрация этой командой происходит после того как сопостовляются строки таблицы. Можно использовать при перечислении через запятую. Список возможных условий.
В таблице ниже, сопоставилены строки из разных таблиц, но имеющие одинаковый id. В этом случае для BMW и зеленого цвета пары не нашлось, и они не попали в результирующую таблицу:
Ту же самую таблицу можно получить следущими записями:
Если бы столбец id у таблицы с цветами назывался бы color_id, то запись для ON и WHERE была бы следующей: