Таблицы - Таблицы и еще таблицы

Это уже реальный продвинутый уровень, и я помню сам не с первого раза понял, что именно и как делает объединение таблиц. Чтобы все это понять пришлось решить несколько различных задач и каждый раз смотреть на результат.

Я постараюсь рассказать про объединение таблицы максимально просто и очень надеюсь, что у меня получится.

У нас есть две таблицы Phone и City. В первой из них находятся телефоны, а во второй. . . внимание. . . подождите, подождите. . . Города!

Вспомним, как выглядит таблица телефонов:

SELECT * 
FROM phone;

Результат

+---------+-----------+-----------+-----------+--------+
| phoneid | firstname | lastname  | phone     | cityid |
+---------+-----------+-----------+-----------+--------+
|       1 | John      | Doe       | 4144122   |      1 |
|       2 | Steve     | Doe       | 414124    |      1 |
|       3 | Johnatan  | Something | 4142947   |      2 |
|       4 | Donald    | Trump     | 414251123 |      2 |
|       5 | Alice     | Cooper    | 414254234 |      2 |
|       6 | Michael   | Jackson   | 4142544   |      3 |
|       7 | John      | Abama     | 414254422 |      3 |
|       8 | Andre     | Jackson   | 414254422 |      3 |
|       9 | Mark      | Oh        | 414254422 |   NULL |
|      10 | Charly    | Lownoise  | 414254422 |   NULL |
+---------+-----------+-----------+-----------+--------+

В колонке cityid находиться только ID города, но не сам город. Как мы можем вместо ID вывести имя города? Тут нам помогает объединение join таблиц.

Объединение таблиц происходит в секции FROM и имеет формат:

FROM таблица1
   JOIN таблица2 ON колонки для связи. 

Снова лингвистически все выглядит достаточно плавно, потому что это можно прочитать как:

ИЗ таблица1
   СВЯЗАНА С таблица2 ПО колонкам

Тут нужно быть внимательным, потому что JOIN не создает новой секции в SQL запросе, он является частью FROM. В SQL можно выделить три основные секции:

SELECT . . . 
FROM . . .
WHERE . . . 

Связи JOIN – это что-то, что мы пишем внутри секции FROM. Именно поэтому я делаю небольшое отступление, когда пишу связи JOIN, чтобы подчеркнуть, что мы пишем этот код в секции FROM.

Итак, давайте наведем связь между телефонами и городами.

FROM phone
   JOIN city ON phone.cityid = city.cityid

Этим кодом мы просим показать нам данные из таблицы телефонов phone и привязать таблицу городов city. Связь происходит по колонке cityid в телефонах и cityid в городах. Так уже получилось, что в обеих таблицах колонка для ID города названа одинаково и так программисты поступают очень часто. Я не зря назвал колонку для хранения городов cityid, это сделано намеренно, потому что в таблице городов есть колонка с таким же именем и по одинаковому имени мы можем догадаться, что значения в них равны.

Итак, полный запрос будет выглядеть так:

SELECT * 
FROM phone 
   JOIN city ON phone.cityid = city.cityid

Как сервер будет его выполнять?

Он берет первую строку из таблицы phone:

| phoneid | firstname | lastname  | phone     | cityid |
|       1 | John      | Doe       | 4144122   |      1 |

Смотрит, что там cityid равен 1, ищет в связанной таблице city таблице запись, где cityid тоже равен 1:

| cityid | cityname  |
|      1 | Toronto   |

И соединяет оба результата вместе (делает операцию join):

| phoneid | firstname | lastname  | phone     | cityid | cityid | cityname  |
|       1 | John      | Doe       | 4144122   |      1 |      1 | Toronto   |

Потом переходит к следующей строке в таблице phone:

| phoneid | firstname | lastname  | phone     | cityid |
|       2 | Steve     | Doe       | 414124    |      1 |

Здесь снова cityid равен 1 и значит снова нужно добавить слить эту строку с Торонто:

| phoneid | firstname | lastname  | phone     | cityid | cityid | cityname  |
|       2 | Steve     | Doe       | 414124    |      1 |      1 | Toronto   |

Следующая строка:

| phoneid | firstname | lastname  | phone     | cityid |
|       3 | Johnatan  | Something | 4142947   |      2 |

Здесь уже cityid равен 2. Ищем в таблице городов, какой город имеет id равный 2:

| cityid | cityname  |
|      2 | Vancouver |

Отлично, это Ванкувер, значит база данных должна объединить телефон Johnatan Something с городом Vancouver:

|       3 | Johnatan  | Something | 4142947   |      2 |      2 | Vancouver |

И так процесс связи продолжается, пока база данных не вернет нам все записи, которые мы запросили:

+---------+-----------+-----------+-----------+--------+--------+-----------+
| phoneid | firstname | lastname  | phone     | cityid | cityid | cityname  |
+---------+-----------+-----------+-----------+--------+--------+-----------+
|       1 | John      | Doe       | 4144122   |      1 |      1 | Toronto   |
|       2 | Steve     | Doe       | 414124    |      1 |      1 | Toronto   |
|       3 | Johnatan  | Something | 4142947   |      2 |      2 | Vancouver |
|       4 | Donald    | Trump     | 414251123 |      2 |      2 | Vancouver |
|       5 | Alice     | Cooper    | 414254234 |      2 |      2 | Vancouver |
|       6 | Michael   | Jackson   | 4142544   |      3 |      3 | Montreal  |
|       7 | John      | Abama     | 414254422 |      3 |      3 | Montreal  |
|       8 | Andre     | Jackson   | 414254422 |      3 |      3 | Montreal  |
+---------+-----------+-----------+-----------+--------+--------+-----------+

А что, если мы при наведении связи напишем что-то типа:

SELECT * 
FROM phone 
   JOIN city ON phone.phoneid = city.cityid

Здесь я говорю, что связь между таблицами происходит по колонке phoneid в таблице телефонов с колонкой cityid в таблице городов:

Он берет первую строку из таблицы phone:

| phoneid | firstname | lastname  | phone     | cityid |
|       1 | John      | Doe       | 4144122   |      1 |

Смотрит, что там phoneid равен 1, ищет в связанной таблице city таблице запись, где cityid тоже равен 1:

| cityid | cityname  |
|      1 | Toronto   |

Случайным образом мы попали и получили правильную строку:

|       1 | John      | Doe       | 4144122   |      1 |      1 | Toronto   |

Потом берем следующую строку:

| phoneid | firstname | lastname  | phone     | cityid |
|       2 | Steve     | Doe       | 414124    |      1 |

Так как мы сказали, что связь должна быть по phoneid мы берем 2 из первой колонки и ищем в таблице городов город под номером 2. Но это неверно? Мы найдем не тот город!

В общем результат выполнения такого запроса будет таким и он неверный:

+---------+-----------+-----------+---------+--------+--------+-----------+
| phoneid | firstname | lastname  | phone   | cityid | cityid | cityname  |
+---------+-----------+-----------+---------+--------+--------+-----------+
|       1 | John      | Doe       | 4144122 |      1 |      1 | Toronto   |
|       2 | Steve     | Doe       | 414124  |      1 |      2 | Vancouver |
|       3 | Johnatan  | Something | 4142947 |      2 |      3 | Montreal  |
+---------+-----------+-----------+---------+--------+--------+-----------+

Поэтому очень важно указать правильную связь – по каким колонкам мы хотим выводить данные, тогда сервер вернет правильный результат.

Получается, что правильный способ связывания двух таблиц:

SELECT * 
FROM phone 
   JOIN city ON phone.cityid = city.cityid

В данном запросе мы выбираем все колонки, поэтому cityid попадает в результат дважды, один раз из таблицы phone, второй раз из таблицы city:

+---------+-----------+-----------+-----------+--------+--------+-----------+
| phoneid | firstname | lastname  | phone     | cityid | cityid | cityname  |
+---------+-----------+-----------+-----------+--------+--------+-----------+

Чаще всего мы не хотим этого делать, поэтому нужно перечислить колонки, которые мы хотим получить:

SELECT firstname, lastname, phone, cityname  
FROM phone 
    JOIN city ON phone.cityid = city.cityid;

Круто, теперь результат на много красивее:

+-----------+-----------+-----------+-----------+
| firstname | lastname  | phone     | cityname  |
+-----------+-----------+-----------+-----------+
| John      | Doe       | 4144122   | Toronto   |
| Steve     | Doe       | 414124    | Toronto   |
| Johnatan  | Something | 4142947   | Vancouver |
| Donald    | Trump     | 414251123 | Vancouver |
| Alice     | Cooper    | 414254234 | Vancouver |
| Michael   | Jackson   | 4142544   | Montreal  |
| John      | Abama     | 414254422 | Montreal  |
| Andre     | Jackson   | 414254422 | Montreal  |
+-----------+-----------+-----------+-----------+

Когда мы наводим связи, то мы должны указать по каким колонкам нужно соединять данные и в нашем случае это cityid:

FROM phone 
    JOIN city ON phone.cityid = city.cityid;

Так как cityid колонка есть в обеих таблицах, мы вынуждены перед колонкой указать, какую колонку мы конкретно имеет в виду. То же самое и при выводе данных. Если попытаться вывести cityid, указав его в SELECT, сервер попросит нас уточнить, а какую мы имеем в виду – та, что в phone или та, что в city:

SELECT firstname, lastname, phone, cityname, cityid
FROM phone 
    JOIN city ON phone.cityid = city.cityid;

В результате будет ошибка:

ERROR 1052 (23000): Column 'cityid' in field list is ambiguous

Здесь нам говорят, что Колонка cityid в списке полей двусмысленная. Она двусмысленна, потому что находиться в двух разных колонках. Это мы знаем, что они равны и нам все равно, какую из них выбрать, а сервер не особо думает об этом. Поэтому правильно было бы указать так:

SELECT firstname, lastname, phone, cityname, phone.cityid
FROM phone 
    JOIN city ON phone.cityid = city.cityid;

Вот теперь мы точно сказали, что мы хотим именно из таблицы phone. Каждый раз писать имя таблицы не очень приятно. И вот тут мы вспоминаем великолепную возможность – псевдонимы. Мы можем дать псевдонимы обеим таблицам и использовать их. Давайте телефону дадим псевдоним p, а городу c.

SELECT firstname, lastname, phone, cityname, p.cityid
FROM phone p
    JOIN city c ON p.cityid = c.cityid;

Теперь вместо того, чтобы писать полное имя таблицы мы можем писать их псевдонимы. Вот оно преимущество псевдонимов.

Я не помню в какой базе данных, но где-то я видел прикол, когда я давал хотя бы один псевдоним, то я обязан был указывать его перед каждой колонкой. Не помню уже какая это была база данных, но точно не MySQL и не MS SQL Server.

SELECT *
FROM phone p
    JOIN city c ON p.cityid = c.cityid;

Если вы столкнулись с тем, что колонка не найдена, то возможно вам просто нужно добавить везде псевдонимы.

Давайте добавим еще один город в таблицу городов.

insert into city (cityname) values ('New York');

Налево пойдешь?

Давайте снова посмотрим на результат, который мы получаем при объединении двух таблиц:

SELECT *
FROM phone p
     JOIN city c ON p.cityid = c.cityid;

Все круто, но есть одно НО – мы не видим записей с телефонных номеров, у которых cityid равен null.

SELECT * FROM phone p where cityid is null;

У меня таких записей две:

+---------+-----------+----------+-----------+--------+
| phoneid | firstname | lastname | phone     | cityid |
+---------+-----------+----------+-----------+--------+
|       9 | Mark      | Oh       | 414254422 |   NULL |
|      10 | Charly    | Lownoise | 414254422 |   NULL |
+---------+-----------+----------+-----------+--------+

Но мы их не видим потому, что сервер не смог найти города для этих записей.

По умолчанию, если мы связываем две таблицы с помощью join, то происходит inner join, то есть мы могли бы написать команду так:

SELECT *
FROM phone p
     INNER JOIN city c ON p.cityid = c.cityid;

Результат не изменился, мы все также видим те же данные. INNER JOIN равен просто JOIN и приводит к тому, что в результате мы видим только те записи, для которых найдены данные в обеих связанных таблицах. Если хотя бы в одной из этих таблиц данные отсутствуют или колонка равна нулю, то мы эти данные не увидим.

Так произошло со строками телефонных записей, у которых в поле cityid находится null и то же самое произошло и с городом New York, который мы недавно добавили, но при этом у нас нет ни одного телефонного номера, который бы был связан с этим городом.

Помимо INNER JOIN есть еще два популярных метода связывать таблицы LEFT JOIN и RIGHT JOIN. В чем смысл и что означают эти магические LEFT (лево) и RIGHT (право)?

Давайте напишем LEFT JOIN:

SELECT *
FROM phone p LEFT JOIN city c ON p.cityid = c.cityid;

В этот раз я записал связь двух таблиц в одну строку и сделал это специально, потому что так проще объяснять. Левая связь работает так, что мы увидим все записи, которые находятся в левой таблице и в тех случаях, где база данных найдет соответствующие данные из второй таблице, они будут добавлены.

Смотрим на результат:

+---------+-----------+-----------+-----------+--------+--------+-----------+
| phoneid | firstname | lastname  | phone     | cityid | cityid | cityname  |
+---------+-----------+-----------+-----------+--------+--------+-----------+
|       1 | John      | Doe       | 4144122   |      1 |      1 | Toronto   |
|       2 | Steve     | Doe       | 414124    |      1 |      1 | Toronto   |
|       3 | Johnatan  | Something | 4142947   |      2 |      2 | Vancouver |
|       4 | Donald    | Trump     | 414251123 |      2 |      2 | Vancouver |
|       5 | Alice     | Cooper    | 414254234 |      2 |      2 | Vancouver |
|       6 | Michael   | Jackson   | 4142544   |      3 |      3 | Montreal  |
|       7 | John      | Abama     | 414254422 |      3 |      3 | Montreal  |
|       8 | Andre     | Jackson   | 414254422 |      3 |      3 | Montreal  |
|       9 | Mark      | Oh        | 414254422 |   NULL |   NULL | NULL      |
|      10 | Charly    | Lownoise  | 414254422 |   NULL |   NULL | NULL      |
+---------+-----------+-----------+-----------+--------+--------+-----------+

Теперь мы видим все телефоны и даже те, у которых нет города. Там, где город отсутствует значения в колонках города тоже равны NULL, потому что ничего не найдено и поэтому нам говорят, что здесь значения отсутствуют.

LEFT JOIN – показать все записи из левой таблицы и там где возможно, показать значения из правой.

Но мы не видим город New York, потому что он находится для нас в правой таблице, но для него в левой нет ничего. Чтобы исправить эту ситуацию можно заменить LEFT JOIN на RIGHT JOIN:

SELECT *
FROM phone p RIGHT JOIN city c ON p.cityid = c.cityid;

В результате мы увидим все записи из правой таблицы от команды JOIN (у нас это city), включая New York. Если для соответствующей записи есть данные в левой таблице phone, то они будут отображены:

+---------+-----------+-----------+-----------+--------+--------+-----------+
| phoneid | firstname | lastname  | phone     | cityid | cityid | cityname  |
+---------+-----------+-----------+-----------+--------+--------+-----------+
|       1 | John      | Doe       | 4144122   |      1 |      1 | Toronto   |
|       2 | Steve     | Doe       | 414124    |      1 |      1 | Toronto   |
|       3 | Johnatan  | Something | 4142947   |      2 |      2 | Vancouver |
|       4 | Donald    | Trump     | 414251123 |      2 |      2 | Vancouver |
|       5 | Alice     | Cooper    | 414254234 |      2 |      2 | Vancouver |
|       6 | Michael   | Jackson   | 4142544   |      3 |      3 | Montreal  |
|       7 | John      | Abama     | 414254422 |      3 |      3 | Montreal  |
|       8 | Andre     | Jackson   | 414254422 |      3 |      3 | Montreal  |
|    NULL | NULL      | NULL      | NULL      |   NULL |      4 | New York  |
+---------+-----------+-----------+-----------+--------+--------+-----------+

Теперь мы видим New York и раз ни одного телефона в Нью Йорке в нашей базе нет, то все поля из таблицы phone показывают отсутствующее значение – NULL.

Нужно ли запоминать Right Join? Из личного опыта LEFT JOIN не все сразу схватывают, а если и схватывают, почему-то испытывают проблемы с RIGHT JOIN.

Я не вижу ничего сложного, но в реальной жизни я вижу большинство использует все же LEFT JOIN. Кажется, в КВН была шутка, что в казахском языке нет слова назад. Просто, когда нужно отступать, они разворачиваются на 180 градусов и бегут вперед. Шутка шуткой, но она четко подходит здесь.

Посмотрим на наш запрос RIGHT JOIN:

SELECT *
FROM phone p RIGHT JOIN city c ON p.cityid = c.cityid;

А что, если мы поменяем местами таблицы phone и city? Нужная нам таблица окажется слева, а значит мы сможем поменять RIGHT на LEFT и получить нужный нам результат:

SELECT *
FROM city c LEFT JOIN phone p ON p.cityid = c.cityid;

Несуществующие связи

Давайте добавим телефон с ID города 10. Такого города не существует и именно от подобных вещей нас умеет защищать сама база данных, но я специально пока эту защиту не сделал, чтобы было пространство для маневров:

insert into phone (firstname, cityid) values ('Mark', 10);

Теперь как нам найти все записи телефонов, города которых реально не существуют в таблице городов. Нам нужно пробежаться по всем записям в телефонах и для каждой из них проверить – существует его cityid в городах или нет. Вот именно так мы это и можем написать это на языке SQL.

SELECT * 
FROM phone p 
WHERE NOT EXISTS (SELECT * FROM city c WHERE c.cityid = p.cityid);

Здесь в одном запросе два SELECT и их можно рассматривать по-отдельности. Первый - выбрать все записи из таблицы phone где не существует (и тут у нас запрос, который проверяет на существование записи в таблице city). Второй запрос проверяет существование, и он возвращает все записи из таблицы city, для которых cityid равен текущего cityid в таблице телефонов.

В результате мы получим две записи телефонов с нулевым cityid и один с 10:

+---------+-----------+----------+-----------+--------+
| phoneid | firstname | lastname | phone     | cityid |
+---------+-----------+----------+-----------+--------+
|       9 | Mark      | Oh       | 414254422 |   NULL |
|      10 | Charly    | Lownoise | 414254422 |   NULL |
|      11 | Mark      | NULL     | NULL      |     10 |
+---------+-----------+----------+-----------+--------+

Все верно, таких городов не существует в таблице city.

EXISTS может проверять и на существование. Если мы хотим проверить на то, какие города ЕСТЬ, мы можем использовать EXISTS:

SELECT * 
FROM phone p 
WHERE EXISTS (SELECT * FROM city c WHERE c.cityid = p.cityid);
+---------+-----------+-----------+-----------+--------+
| phoneid | firstname | lastname  | phone     | cityid |
+---------+-----------+-----------+-----------+--------+
|       1 | John      | Doe       | 4144122   |      1 |
|       2 | Steve     | Doe       | 414124    |      1 |
|       3 | Johnatan  | Something | 4142947   |      2 |
|       4 | Donald    | Trump     | 414251123 |      2 |
|       5 | Alice     | Cooper    | 414254234 |      2 |
|       6 | Michael   | Jackson   | 4142544   |      3 |
|       7 | John      | Abama     | 414254422 |      3 |
|       8 | Andre     | Jackson   | 414254422 |      3 |
+---------+-----------+-----------+-----------+--------+

Запрос с EXISTS идентичен INNER JOIN и следующие два запроса выведут абсолютно идентичный результат:

SELECT * 
FROM phone p 
WHERE EXISTS (SELECT * FROM city c WHERE c.cityid = p.cityid);

И

SELECT p.*
FROM phone p 
    JOIN city c ON c.cityid = p.cityid;

Можете выполнить их, чтобы убедиться в идентичности.

NOT EXISTS можно реализовать с помощью LEFT JOIN. Давайте выполним LEFT JOIN и посмотрим на результат:

SELECT *
FROM phone p 
    LEFT JOIN city c ON c.cityid = p.cityid;

Результат будет таким:

+---------+-----------+-----------+-----------+--------+--------+-----------+
| phoneid | firstname | lastname  | phone     | cityid | cityid | cityname  |
+---------+-----------+-----------+-----------+--------+--------+-----------+
|       1 | John      | Doe       | 4144122   |      1 |      1 | Toronto   |
|       2 | Steve     | Doe       | 414124    |      1 |      1 | Toronto   |
|       3 | Johnatan  | Something | 4142947   |      2 |      2 | Vancouver |
|       4 | Donald    | Trump     | 414251123 |      2 |      2 | Vancouver |
|       5 | Alice     | Cooper    | 414254234 |      2 |      2 | Vancouver |
|       6 | Michael   | Jackson   | 4142544   |      3 |      3 | Montreal  |
|       7 | John      | Abama     | 414254422 |      3 |      3 | Montreal  |
|       8 | Andre     | Jackson   | 414254422 |      3 |      3 | Montreal  |
|       9 | Mark      | Oh        | 414254422 |   NULL |   NULL | NULL      |
|      10 | Charly    | Lownoise  | 414254422 |   NULL |   NULL | NULL      |
|      11 | Mark      | NULL      | NULL      |     10 |   NULL | NULL      |
+---------+-----------+-----------+-----------+--------+--------+-----------+

Первая колонка с cityid из таблицы телефонов. Вторая колонка – из таблицы городов. Обратите внимание, что для нужных нам записей вторая cityid и cityname равны NULL, там отсутствуют значения. Это же логично и именно эти записи нам нужны. Так что задачу NOT EXIST можно решить с помощью LEFT JOIN, если попросить вернуть записи, в которых cityid в колонке городов равен NULL. Главное не забывать, что проверка на NULL производиться с помощью IS:

SELECT p.*
FROM phone p 
    LEFT JOIN city c ON c.cityid = p.cityid
WHERE c.cityid IS NULL;

Так что этот запрос идентичен NOT NULL.

объединения

Что если мы хотим объединить две таблицы в одну? Без проблем, для этого существует оператор UNION, который объединяет два запроса. Вы просто пишете два SELECT запроса, которые возвращают какие-то данные и между ними пишите UNION или UNION ALL. Разница в том, что в первом случае база данных берет два результата запросов и убирает повторения из них, возвращая только уникальные строки из обеих строк. Во втором случае вернуться все записи, без попытки избавиться от дубликатов.

Например, запрос, который возвращает имена и фамилии и запрос, который возвращает названия городов?

select concat(firstname, ' ', lastname) from phone

и

select cityname from city;

превращается в:

select concat(firstname, ' ', lastname) from phone
union
select cityname from city;

В результате мы увидим в качестве результата абсолютно все имена+фамилии и города.

+----------------------------------+
| concat(firstname, ' ', lastname) |
+----------------------------------+
| John Doe                         |
| Steve Doe                        |
| Johnatan Something               |
| Donald Trump                     |
| Alice Cooper                     |
| Michael Jackson                  |
| John Abama                       |
| Andre Jackson                    |
| Mark Oh                          |
| Charly Lownoise                  |
| NULL                             |
| Toronto                          |
| Vancouver                        |
| Montreal                         |
+----------------------------------+

При объединении запросов с помощью UNION нужно соблюдать достаточно простое правило – количество колонок должно совпадать. У меня в обеих таблицах один только одна колонка, поэтому проблем нет.

Если имена колонок в обеих таблицах разные, то в качестве финального имени будет выбрано имя колонки из первого запроса.

Обратите внимание, что у нас в заголовке имя – это операция объединения. Я это сделал специально. Чтобы у заголовка появилась имя, в данном случае нужно дать псевдоним колонке первого запроса.

select concat(firstname, ' ', lastname) as name from phone
union
select cityname from city;

таблицы из таблицы

Мы можем получать данные не только из таблиц в базе данных, но и из других SELECT запросов. Мы только что разобрались с union запросом и возникает вопрос – а как отсортировать все данные? В нашем случае сначала были выбраны колонки из первой таблицы, а потом из второй. Чтобы отсортировать этот результат мы можем написать запрос, который будет обращаться к другому запросу.

Для этого мы берем запрос, к которому мы будем впоследствии обращаться, помещаем его в круглые скобки и даем ему псевдоним:

(
   select concat(firstname, ' ', lastname) as name from phone
   union
   select cityname from city
) g

Вот эту всю конструкцию мы можем воспринимать как что-то, к чему можно обращаться. Главное, чтобы все поля этого нашего запроса имели имена, иначе как к ним обращаться?

Итак, пишем SELECT к запросу:

SELECT *
FROM (
   select concat(firstname, ' ', lastname) as name from phone
   union
   select cityname from city
) g
Order by name

Таким образом мы можем реализовать что-то типа глобального поиска, который ищет по именам людей и городов одновременно.

Мы можем добавлять и WHERE к такому запросу и фильтровать данные:

SELECT *
FROM (
   select concat(firstname, ' ', lastname) as name from phone
   union
   select cityname from city
) g
WHERE Name like 'A%'
Order by name

Предыдущая глава

SQL углубимся

Следующая глава

Агрегатные функции SQL

О блоге

Программист, автор нескольких книг серии глазами хакера и просто блогер. Интересуюсь безопасностью, хотя хакером себя не считаю

Обратная связь

Без проблем вступаю в неразборчивые разговоры по e-mail. Стараюсь отвечать на письма всех читателей вне зависимости от страны проживания, вероисповедания, на русском или английском языке.

Пишите мне