# SQL Данное занятие посвящено основным манипуляциям с *данными* в SQL. То есть речь пойдет о **DML** (**Data Manipulation Language**), операциях вставки, удаления, изменения и выборки данных. ## Вставка Для того, что бы манипулировать с нашими данными в дальнейшем, нам нужно их добавить в таблицы. Для этого используется `INSERT INTO` ```mysql INSERT INTO (column1, column2, column3...columnN) VALUES (value1, value2, value3...valueN) ``` где: - **columnN** - имена колонок таблицы для вставки данных - **valueN** - значения соответствующих колонок. **Например** ```mysql MariaDB [test]> INSERT INTO person (date_of_birth, name, surname, father_name) VALUES("2000-03-15", "Ivan", "Ivanoff", "Petrovich"); Query OK, 1 row affected (0.10 sec) ``` Для проверки используем **запрос выборки** `SELECT` в простейшей форме. Подробнее о выборке будет ниже: ```mysql MariaDB [test]> SELECT * from person; +-----------+---------------+------+---------+-------------+ | person_id | date_of_birth | name | surname | father_name | +-----------+---------------+------+---------+-------------+ | 1 | 2000-03-15 | Ivan | Ivanoff | Petrovich | +-----------+---------------+------+---------+-------------+ 1 row in set (0.00 sec) ``` Как можно заметить, *не обязательно* указывать все поля таблицы для заполнения; более того, *некоторые поля* лучше **не** указывать. В данном случае это *автоинкремент*, который на то и *авто*, что бы заполняться автоматически при добавлении в таблицу. Поля, которые не указаны в `INSERT`, заполняются их значениями по умолчанию, которые можно задать при создании таблицы. Обычный синтаксис `INSERT INTO (columns) VALUES(values)` не очень нагляден, особенно когда колонок много, так как имена колонок находятся далеко от значений; при редактировании такого запроса часто бывают ошибки, например вставка данных не в ту колонку. Зато эта форма удобна для пакетной вставки и вставки данных из запроса выборки. ### Вставка: альтернативный синтаксис. Более наглядным является синтаксис: ```mysql INSERT INTO
SET column1 = value1, column2 = value2, ..... columnN = valueN ``` Данный синтаксис очень сильно похож на обычное присвоение в языках программирования. **Например** ```mysql MariaDB [test]> INSERT INTO person SET date_of_birth = "1999-05-25", name = 'Vasiliy', surname = 'Pupkin', father_name = 'Petrovich'; Query OK, 1 row affected (0.00 sec) ``` ## Изменение Для изменения данных используется запрос, похожий на альтернативную форму `INSERT INTO
SET`: ```mysql UPDATE
SET column1 = value1, column2 = value2, ..... columnN = valueN ``` Такая форма изменяет **все** записи в таблице, так как не указано условие, какие именно записи подлежат изменению. **Запомните это!!!!!**, так как это верный способ испортить данные: ```mysql MariaDB [test]> UPDATE person SET date_of_birth = NOW(); Query OK, 2 rows affected, 2 warnings (0.02 sec) Rows matched: 2 Changed: 2 Warnings: 2 MariaDB [test]> SELECT * from person; +-----------+---------------+---------+---------+-------------+ | person_id | date_of_birth | name | surname | father_name | +-----------+---------------+---------+---------+-------------+ | 1 | 2016-10-23 | Ivan | Ivanoff | Petrovich | | 2 | 2016-10-23 | Vasiliy | Pupkin | Petrovich | +-----------+---------------+---------+---------+-------------+ 2 rows in set (0.00 sec) ``` Обратите внимание на даты. **SQL** позволяет не только вставлять литеральные значения, но и использовать внутренние функции (`NOW()` в примере выше) или выражения с полями текущей записи. **Например** ```mysql MariaDB [test]> ALTER TABLE person ADD COLUMN fullname VARCHAR(255); Query OK, 0 rows affected (0.50 sec) Records: 0 Duplicates: 0 Warnings: 0 MariaDB [test]> desc person; +---------------+------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +---------------+------------------+------+-----+---------+----------------+ | person_id | int(10) unsigned | NO | PRI | NULL | auto_increment | | date_of_birth | date | YES | MUL | NULL | | | name | varchar(64) | YES | | NULL | | | surname | varchar(64) | YES | | NULL | | | father_name | varchar(64) | YES | | NULL | | | fullname | varchar(255) | YES | | NULL | | +---------------+------------------+------+-----+---------+----------------+ 6 rows in set (0.00 sec) MariaDB [test]> UPDATE person SET fullname = CONCAT(name," ",father_name," ",surname); Query OK, 2 rows affected (0.01 sec) Rows matched: 2 Changed: 2 Warnings: 0 MariaDB [test]> SELECT * FROM person; +-----------+---------------+---------+---------+-------------+--------------------------+ | person_id | date_of_birth | name | surname | father_name | fullname | +-----------+---------------+---------+---------+-------------+--------------------------+ | 1 | 2016-10-23 | Ivan | Ivanoff | Petrovich | Ivan Petrovich Ivanoff | | 2 | 2016-10-23 | Vasiliy | Pupkin | Petrovich | Vasiliy Petrovich Pupkin | +-----------+---------------+---------+---------+-------------+--------------------------+ 2 rows in set (0.00 sec) ``` ### Условие изменения `WHERE` Для того, что бы `UPDATE` изменял не все, а только нужные записи, в конце запроса добавляется конструкция `WHERE` и логическое условие, схожее с применяемыми в операторах `if` в языках программирования. Обычно в этом условии используется значение поля автоинкремента как уникальный идентификатор записи: ```mysql MariaDB [test]> UPDATE person SET date_of_birth = '1999-05-25' WHERE person_id = 2; Query OK, 1 row affected (0.01 sec) Rows matched: 1 Changed: 1 Warnings: 0 MariaDB [test]> SELECT * from person; +-----------+---------------+---------+---------+-------------+--------------------------+ | person_id | date_of_birth | name | surname | father_name | fullname | +-----------+---------------+---------+---------+-------------+--------------------------+ | 1 | 2016-10-23 | Ivan | Ivanoff | Petrovich | Ivan Petrovich Ivanoff | | 2 | 1999-05-25 | Vasiliy | Pupkin | Petrovich | Vasiliy Petrovich Pupkin | +-----------+---------------+---------+---------+-------------+--------------------------+ 2 rows in set (0.00 sec) ``` Также, для ограничения количества записей, подверженных изменению, можно добавить `LIMIT`: ```mysql UPDATE
SET column1 = value1, column2 = value2, ..... columnN = valueN WHERE LIMIT 1 ``` `LIMIT 1` означает, что `UPDATE` должен изменить **не более** одной записи. Таким образом вы можете минимизировать последствия вашего запроса, если не уверены в том, что записано в `WHERE` в качестве условия. ## `DELETE` В общем случае: ```mysql DELETE FROM
``` ...как и `UPDATE` удаляет всё, если не указано иное. Поэтому перед тем, как нажать **ENTER** несколько раз смотрим, всё ли верно, используем `WHERE` и/или `LIMIT`. ## **Автоинкремент** и **связность** Для установки связи значения автоинкремента заносится в другую таблицу как обычный INT. ```mysql MariaDB [test]> DESC phone; +--------------+-----------------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +--------------+-----------------------------+------+-----+---------+----------------+ | phone_id | int(10) unsigned | NO | PRI | NULL | auto_increment | | person_id | int(10) unsigned | YES | | NULL | | | type | enum('home','mobile','job') | YES | | NULL | | | phone_number | varchar(16) | YES | | NULL | | +--------------+-----------------------------+------+-----+---------+----------------+ 4 rows in set (0.00 sec) MariaDB [test]> ALTER TABLE phone MODIFY COLUMN type ENUM('home', 'mobile', 'job') DEFAULT 'mobile'; Query OK, 0 rows affected (0.00 sec) Records: 0 Duplicates: 0 Warnings: 0 MariaDB [test]> DESC phone; +--------------+-----------------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +--------------+-----------------------------+------+-----+---------+----------------+ | phone_id | int(10) unsigned | NO | PRI | NULL | auto_increment | | person_id | int(10) unsigned | YES | | NULL | | | type | enum('home','mobile','job') | YES | | mobile | | | phone_number | varchar(16) | YES | | NULL | | +--------------+-----------------------------+------+-----+---------+----------------+ 4 rows in set (0.00 sec) MariaDB [test]> INSERT INTO phone SET person_id = 2, phone_number = '+380 577 102'; Query OK, 1 row affected (0.00 sec) MariaDB [test]> INSERT INTO phone SET person_id = 2, phone_number = '+380 577 103'; Query OK, 1 row affected (0.01 sec) MariaDB [test]> INSERT INTO phone SET person_id = 1, phone_number = '+380 577 104'; Query OK, 1 row affected (0.00 sec) MariaDB [test]> INSERT INTO phone SET person_id = 1, phone_number = '+380 577 101'; Query OK, 1 row affected (0.02 sec) MariaDB [test]> select * from phone; +----------+-----------+--------+--------------+ | phone_id | person_id | type | phone_number | +----------+-----------+--------+--------------+ | 1 | 2 | mobile | +380 577 102 | | 2 | 2 | mobile | +380 577 103 | | 3 | 1 | mobile | +380 577 104 | | 4 | 1 | mobile | +380 577 101 | +----------+-----------+--------+--------------+ 4 rows in set (0.00 sec) ``` Таким образом на каждого человека по два телефонных номера. ## Задание Сформируйте нужные данные в ваших таблицах, которые вы делали дома. Не забудьте об отношениях. Для установки отношения при добавлении записи указывается значение **автоинкремента** из другой таблицы ## `SELECT` `SELECT` - самая мощная часть DML, и основная фича SQL ### Cинтаксис В общем виде: ```mysql SELECT <поле1> <поле2> <поле3> ... FROM ... WHERE ORDER BY <поле1> ASC <поле2> DESC LIMIT N,M ``` **где** *<поле1>* - поля таблиц для вывода; *table1* - таблицы для запроса; `WHERE` *cond* - условия, что и по каким правилам включать в выборку; `ORDER BY` - по каким полям сортировать `LIMIT` - ограничения с какой по какую запись выводить результирующую выборку **Например** ```mysql SELECT * FROM person; SELECT id, name, surname FROM person; SELECT count(*) FROM person; ``` ### `ORDER BY` Сортировка. Может быть прямой (`ASC`) и обратной (`DESC`). Возможна сортировка по нескольким полям, в таком случае набор сортируется вначале по первым полям, а при их совпадении - по последующим, аналогично сортировке слов в словаре. Для работы сортировки **не** требуется индекс, однако с ним на больших наборах данных выборка будет сделана намного быстрее. ```mysql SELECT * FROM person ORDER by id; SELECT * FROM person ORDER by date_of_birth; SELECT * FROM person ORDER by father_name DESC; SELECT id,name,surname,date_of_birth FROM person ORDER BY name; SELECT id,name,surname,date_of_birth FROM person ORDER BY name, id DESC; ``` **Не обязательно** использовать имена полей в `ORDER BY`; можно использовать разные конструкции и функции Mysql, например `RAND()` для случайной выборки. ### `WHERE` и `LIMIT` Как вы заметили выше, `WHERE` - очень мощный инструмент, который позволяет строить практически любые условия. Как и в языках программирования, возможно использовать логические операции (`OR`, `AND`, `NOT`), операции сравнения и другие выражения. ```mysql SELECT * FROM person WHERE date_of_birth > "2000-01-01"; SELECT * FROM person WHERE date_of_birth > "2000-01-01" AND name = 'Ivan'; SELECT * FROM person WHERE date_of_birth > "2000-01-01" OR name IN ('Petr', 'Vasiliy'); SELECT * FROM person WHERE id % 2 = 0; ``` `LIMIT` позволяет получить только часть выборки, именно благодаря этой функции работает пагинация на подавляющем большинстве сайтов: ```mysql SELECT * FROM person LIMIT 1 ``` выдает одну запись сначала выборки согласно сортировке ```mysql SELECT * FROM person LIMIT 1,2 ``` выдает две записи начиная со второй записи (пропуская 1 запись от начала) выборки согласно сортировке. Таким образом, `LIMIT N` аналогичен `LIMIT 0,N` ### `DISTINCT` Это слово применяется для удаления повторов в результирующем наборе: ```mysql SELECT DISTINCT name FROM person; SELECT DISTINCT name, surname FROM person; ``` Под повтором понимается полное совпадение всех полей набора. Обычно используется для устранения повторов в выборке из нескольких таблиц. ### Несколько таблиц (`CROSS JOIN`) Можно сделать выборку сразу из нескольких таблиц и получить *декартово произведение* ```mysql SELECT * FROM person, phone; SELECT * FROM person CROSS JOIN phone ``` На каждую запись первой таблицы приходятся все записи второй таблицы. Если одна из таблиц пустая, то результирующий набор пуст. Количество записей в результате равно произведению количеств записей в каждой из таблиц. ### Несколько таблиц и `WHERE` (`INNER JOIN`) С помощью условия в `WHERE` можно сделать более осмысленный запрос. Например найти все телефоны пользователя с id = 1 через смежную таблицу связи **многие-ко-многим**: ```mysql SELECT * FROM person_to_phone, phone WHERE person_id = 1 AND person_to_phone.phone_id = phone.id; ``` Тоже самое через `INNER JOIN`: ```mysql SELECT * FROM person_to_phone INNER JOIN phone ON (person_to_phone.phone_id = phone.id) WHERE person_id = 1; ``` Однако, если мы сделаем запрос для выборки всех людей с их номерами телефонов, мы получим *не всех людей*: ```mysql SELECT * FROM person, person_to_phone, phone WHERE person.id = person_to_phone.person_id AND person_to_phone.phone_id = phone.id; ``` ...a только тех, у кого *есть номера телефонов*. ### Несколько таблиц, `RIGHT` и `LEFT` `JOIN` (`OUTER JOIN`) Для решения вышеозначенной проблемы есть запросы `LEFT` и `RIGHT JOIN`: ```mysql SELECT * FROM person LEFT JOIN person_to_phone ON (person.id = person_to_phone.person_id) LEFT JOIN phone ON (person_to_phone.phone_id = phone.id); ``` Запрос выше показывает *всех* пользователей, даже тех, у кого телефонов *нет*. Недостающие поля заполняются `NULL` ```mysql SELECT * FROM person RIGHT JOIN person_to_phone ON (person.id = person_to_phone.person_id) RIGHT JOIN phone ON (person_to_phone.phone_id = phone.id); ``` Этот запрос, наоборот, показывает все телефоны, один из который не принадлежит никому из пользователей. ### `GROUP BY` и агрегация Нередко возникает задача, когда требуется не выводить все присоединенные записи, а произвести над ними определенные действия и вывести один результат для каждой "родительской" записи. Например, подсчитаем количество телефонов у каждого пользователя ```mysql SELECT person.id, person.surname, count(phone.id) FROM person LEFT JOIN person_to_phone ON (person.id = person_to_phone.person_id) LEFT JOIN phone ON (person_to_phone.phone_id = phone.id) GROUP BY person.id; ``` Так же мы можем получить все номера телефонов в удобном для `explode` виде: ```mysql SELECT person.id, person.surname, COUNT(phone.id), GROUP_CONCAT(phone.phone) FROM person LEFT JOIN person_to_phone ON (person.id = person_to_phone.person_id) LEFT JOIN phone ON (person_to_phone.phone_id = phone.id) GROUP BY person.id; ``` Или подсчитать средний возраст пользователей тех или иных типов телефонов: ```mysql SELECT phone.phone_type, AVG(TIMESTAMPDIFF(YEAR, person.date_of_birth, CURDATE())) AS averageAge FROM phone LEFT JOIN person_to_phone ON (phone.id = person_to_phone.phone_id) LEFT JOIN person ON (person_to_phone.person_id = person.id) GROUP BY phone_type; ``` Для понимания, как именно работает `GROUP BY`, попробуйте его убрать и отсортировать выборку по полю, которое было в GROUP BY. Таким образом вы увидите *все* записи сгруппированными благодаря сортировке. СУБД работает схожим образом. ### `AS` и древовидные структуры данных в реляционных СУБД. Как было показано выше, слово `AS` позволяет сделать псевдоним для поля выборки. Так же его можно использовать и для таблиц. Это особо полезно, когда нужно воспользоваться одной и той же таблицей в выборке несколько раз. В реляционных СУБД часто хранят древовидные структуры данных с помощью отношения **один-ко-многим** внутри одной таблицы. Для представления разных уровней дерева в одной и той же таблице используются псевдонимы: ```mysql SELECT c1.id, c1.name, c2.id, c2.name, c2.parent_id FROM category AS c1, category AS c2 WHERE c2.parent_id = c1.id ORDER BY c1.id, c2.id ``` ### Индексы. А как же индексы? Индексы не обязательны, почти все запросы на сортировку и/или фильтрацию работают одинаково успешно с ними и без них. Однако на мало-мальски крупных объемах данных скорость выполнения запросов сильно падает. Зачастую **создать индекс и сделать запрос *быстрее* чем сделать этот же запрос без индекса**. ## Задание