Справочное руководство по MySQL версии 4.1.1-alpha. - 3 Учебное пособие по MySQL

Go to the first, previous, next, last section, table of contents.

# 3 Учебное пособие по MySQL

Эта глава представляет собой обучающий курс по MySQL: в ней демонстрируется использование клиентской программы `mysql` для создания несложной базы данных и работы с ней. Утилита `mysql` (иногда называемая также ``терминальным монитором'' или просто ``монитором'') представляет собой интерактивную программу, позволяющую подсоединяться к MySQL-серверу, запускать запросы, и просматривать результаты. Программа `mysql` может работать и в пакетном режиме: для этого необходимо записать все запросы в файл, а затем передать его содержимое на исполнение `mysql`. Ниже описаны оба способа использования `mysql`.

Увидеть список команд программы `mysql` можно, запустив ее с параметром `--help`:

    shell> mysql --help

Текст главы построен с расчетом на то, что на вашем компьютере установлен `mysql` и существует связь с сервером MySQL. Если это не так, свяжитесь со своим администратором MySQL (если вы и есть администратор, обратитесь к другим разделам настоящего руководства).

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

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

## 3.1 Подсоединение к серверу и отсоединение от него

При подключении к серверу с помощью `mysql` обычно нужно ввести имя пользователя MySQL и, в большинстве случаев, пароль. Если сервер запущен не на том компьютере, с которого вы вошли в систему, необходимо также указать имя хоста. Параметры соединения (а именно - соответствующее имя хоста, пользователя и пароль) вы сможете узнать у администратора. Получив соответствующие параметры, подсоединиться к серверу можно следующим образом:

    shell> mysql -h host -u user -p
    Enter password: ********

Символы ******** обозначают ваш пароль; введите его, когда `mysql` выведет на экран запрос `Enter password:`.

Если все сработает, на экране должна появиться следующая информация и метка командной строки `mysql>`:

    shell> mysql -h host -u user -p
    Enter password: ********

    Welcome to the MySQL monitor. Commands end with ; or \g.
    Your MySQL connection id is 459 to server version: 3.22.20a-log

    Type 'help' for help.

    mysql>

Метка обозначает, что программа `mysql` готова к вводу команд.

В некоторых вариантах установки MySQL возможно подсоединение к запущенному на локальном хосте серверу без ввода имени пользователя (пользователь anonymous). Если ваша система настроена именно так, подсоединиться к серверу вы сможете, запустив `mysql` со следующими параметрами:

    shell> mysql

После установки соединения можно в любой момент отключиться от сервера, набрав в командной строке `mysql>` команду `QUIT`:

    mysql> QUIT
    Bye

Отсоединиться от сервера можно и при помощи сочетания клавиш Control-D.

Большая часть приведенных ниже примеров построена с учетом того, что соединение с сервером уже установлено. Это видно по наличию в них командной строки `mysql>`.

## 3.2 Ввод запросов

Подсоединитесь к серверу, как было описано выше. Таким образом никакая база выбрана не будет, но это не страшно. На данном этапе нам гораздо важней разобраться с созданием запросов, нежели сразу усложнять себе жизнь созданием таблиц, загрузкой в них данных и извлечением их оттуда. В этом разделе разъясняются основные принципы ввода команд; на примере нескольких запросов вы можете поближе познакомиться с работой `mysql`.

Ниже приведена простая команда, запрашивающая у сервера информацию об его версии и текущей дате. Введите ее в командной строке `mysql>` и нажмите Enter:

    mysql> SELECT VERSION(), CURRENT_DATE;

    +--------------+--------------+
    | VERSION()    | CURRENT_DATE |
    +--------------+--------------+
    | 3.22.20a-log | 1999-03-19   |
    +--------------+--------------+
    1 row in set (0.01 sec)
    mysql>

Этот запрос иллюстрирует следующие особенности `mysql`:

- Команда обычно состоит из SQL-выражения, за которым следует точка с запятой. (Из этого правила есть и исключения - команды без точки с запятой. Одним из них является упомянутая выше команда `QUIT`, остальные мы рассмотрим позднее.)
- Когда пользователь вводит команду, `mysql` отправляет ее серверу для выполнения и выводит на экран сначала результаты, а затем - новую строку `mysql>`, что означает готовность к выполнению новых команд.
- mysql выводит результаты работы запроса в виде таблицы (строк и столбцов). В первой строке этой таблицы содержатся заголовки столбцов, а в следующих строках - собственно результаты. Обычно заголовками столбцов становятся имена, полученные из таблиц базы. Если же извлекается не столбец таблицы, а значение выражения (как это происходит в приведенном выше примере), `mysql` дает столбцу имя запрашиваемого выражения.
- mysql сообщает количество возвращаемых строк и время выполнения запроса, что позволяет в некоторой степени составить представление о производительности сервера. Эти значения обычно весьма впечатляют, так как представляют обычное (а не машинное время), кроме того, на них оказывает влияние загрузка сервера и скорость работы сети (для сокращения размеров листингов в остальных примерах этой главы строка "rows in set" удалена).

Для ввода ключевых слов можно использовать любой регистр символов. Приведенные ниже запросы абсолютно идентичны:

    mysql> SELECT VERSION(), CURRENT_DATE;
    mysql> select version(), current_date;
    mysql> SeLeCt vErSiOn(), current_DATE;

А это - еще один запрос. В нем демонстрируется использование `mysql` в качестве несложного калькулятора:

    mysql> SELECT SIN(PI()/4), (4+1)*5;
    +-------------+---------+
    | SIN(PI()/4) | (4+1)*5 |
    +-------------+---------+
    |    0.707107 |      25 |
    +-------------+---------+

Все команды, представленные выше, были относительно короткими и состояли из одной строки. В одну строку можно поместить и несколько команд. Но каждая из них должна заканчиваться точкой с запятой:

    mysql> SELECT VERSION(); SELECT NOW();
    +--------------+
    | VERSION()    |
    +--------------+
    | 3.22.20a-log |
    +--------------+

    +---------------------+
    | NOW()               |
    +---------------------+
    | 1999-03-19 00:15:33 |
    +---------------------+

Втискивать все команды в одну строку совсем не обязательно, так что создание длинных команд, занимающих несколько строк, никаких проблем не вызывает. Для `mysql` признаком завершения выражения является точка с запятой, а не конец строки (другими словами, `mysql` принимает команды без форматирования: строки с командами собираются, но не исполняются до тех пор, пока программа не обнаружит точку с запятой).

Вот пример несложного выражения, занимающего несколько строк:

    mysql> SELECT
        -> USER()
        -> ,
        -> CURRENT_DATE;
    +--------------------+--------------+
    | USER()             | CURRENT_DATE |
    +--------------------+--------------+
    | joesmith@localhost | 1999-03-18   |
    +--------------------+--------------+

Обратите внимание на то, как изменилась метка командной строки (с `mysql>` на `->`) после ввода первой строки этого запроса. Таким образом программа mysql показывает, что завершенного выражения она пока что не получила и ожидает его полного ввода. Эта метка очень полезна, так как предоставляет весьма ценную информацию о состоянии программы. С ее помощью всегда можно узнать, чего ждет `mysql`.

Если вы решите отменить исполнение набираемой команды, наберите `\c`:

    mysql> SELECT
        -> USER()
        -> \c
    mysql>

Обратите внимание на метку: после ввода команды `\c` она снова принимает вид mysql>, показывая, что программа `mysql` перешла в режим ожидания указаний.

В этой таблице приведены все возможные варианта вида метки командной строки и соответствующие им состояния `mysql`:

Метка | Значение
`mysql>` | Ожидание новой команды.
`->` | Ожидание следующей строки многострочной команды.
`'>` | Ожидание следующей строки, сбор строкового выражения, начинающегося с одиночной кавычки (``''`).
`">` | Ожидание следующей строки, сбор строкового выражения, начинающегося с двойной кавычки (``"'`).

Обычно многострочные команды получаются случайно, когда хочешь создать обычную команду, но забываешь поставить завершающую точку с запятой. В таком случае `mysql` ожидает продолжения:

    mysql> SELECT USER()
        ->

Если с вами произошло подобное (вы думаете, что завершили команду, но программа выдает только метку `->`), то `mysql`, вероятнее всего, ждет точки с запятой. Не обратив внимание на метку командной строки, можно довольно долго ждать выполнения команды, не понимая в чем дело. А достаточно лишь поставить точку с запятой, завершив команду, которую `mysql` и выполнит:

    mysql> SELECT USER()
        -> ;
    +--------------------+
    | USER()             |
    +--------------------+
    | joesmith@localhost |
    +--------------------+

Метки `'>` и `">` используются при сборе строк. В MySQL строки можно заключать как в одинарные (``''`), так и в двойные (``"'`) кавычки (можно, например, написать `'hello'` или `"goodbye"`), к тому же, `mysql` позволяет вводить строковые выражения, состоящие из нескольких строчек текста. Метка `'>` или `">` обозначает, что вы ввели строку, открывающуюся символом кавычек ``''` или ``"'`, но еще не ввели завершающую строковое выражение закрывающую кавычку.

Это, конечно, нормально, если вы собираетесь создать большое строковое выражение из нескольких строчек. Но это не слишком частый случай. Гораздо чаще оказывается, что вы просто забыли поставить закрывающую кавычку. Например:

    mysql> SELECT * FROM my_table WHERE name = "Smith AND age < 30;
        ">

Если ввести такую команду `SELECT`, нажать Enter и подождать результатов, ничего не произойдет. Тут-то и нужно обратить внимание на метку командной строки, выглядящую вот так: `">`. Это значит, что `mysql` ждет ввода завершающей части строки. (Теперь заметили ошибку в команде? В строке `"Smith` нет закрывающей кавычки.)

Что делать в этом случае? Проще всего было бы отменить команду. Однако теперь просто набрать `\c` нельзя, так как `mysql` примет эти символы за часть собираемой строки! Вместо этого нужно ввести закрывающие кавычки (тем самым дав `mysql` понять, что строка закончилась) и лишь затем набрать `\c`:

    mysql> SELECT * FROM my_table WHERE name = "Smith AND age < 30;
        "> "\c
    mysql>

Метка командной строки снова примет вид `mysql>`, показывая готовность mysql к выполнению команд.

Знать значение меток `'>` и `">` необходимо, так как при вводе незавершенной строки все последующие строки будут игнорироваться `mysql` - включая строку с командой `QUIT`! Это может основательно сбить с толку, особенно если не знать, что для отмены команды перед соответствующей последовательностью символов необходимо поставить закрывающую кавычку.

## 3.3 Создание и использование базы данных

Теперь, когда вы знаете, как вводить команды, пора начать работу с базой данных.

Предположим, у вас дома (или в вольере) живет несколько домашних любимцев и вы хотите создать систему для хранения разнообразной информации о своем живом уголке. Для того чтобы это сделать, нужно просто создать таблицы и записать в них информацию. После этого любые данные можно будет получить, извлекая данные из таблиц. В этом разделе вы научитесь:

- создавать базу данных
- создавать таблицу
- записывать в таблицу данные
- извлекать данные из таблицы различными способами
- работать с несколькими таблицами сразу

Наша база данных будет чрезвычайно проста (это мы сделали намеренно), но совсем несложно придумать реальные задачи, решение которых потребовало бы применения подобной базы данных. Такая база, например, может пригодиться скотоводу для хранения информации о поголовье или ветеринару - для учета пациентов. Готовая база данных `menagerie`, в которой содержатся некоторые из запросов и данные из приведенных ниже разделов, находится на web-сайте MySQL. Ее можно загрузить в виде сжатого `tar`-файла (http://www.mysql.com/Downloads/Contrib/Examples/menagerie.tar.gz) или Zip (http://www.mysql.com/Downloads/Contrib/Examples/menagerie.zip).

Узнать, какие базы существуют в настоящее время на сервере, можно при помощи команды `SHOW`:

    mysql> SHOW DATABASES;
    +----------+
    | Database |
    +----------+
    | mysql    |
    | test     |
    | tmp      |
    +----------+

На вашем компьютере список баз, вероятно, будет другим, но в нем все равно, скорее всего, будут присутствовать базы `mysql` и `test`. База данных `mysql` просто необходима, так как в ней она описываются пользовательские права доступа. База `test` часто применяется для экспериментов.

Впрочем, всех баз вы можете и не увидеть, если у вас нет привилегии `SHOW DATABASES`. See section 4.3.1 Синтаксис команд `GRANT` и `REVOKE`.

Если база данных `test` существует, попробуйте обратиться к ней:

    mysql> USE test
    Database changed

В команде `USE`, как и `QUIT`, точка с запятой не нужна (конечно, данные команды тоже можно завершать точкой с запятой - никакого вреда от этого не будет). Команда `USE` отличается от остальных и кое-чем еще: она должна задаваться одной строкой.

Базу данных `test` (если, конечно, у вас есть доступ к ней) можно использовать для работы с приведенными ниже примерами, но все созданное в ней может быть уничтожено любым другим пользователем, имеющим к ней доступ. Поэтому вам лучше попросить у своего администратора MySQL разрешение на создание собственной базы. Предположим, вы захотите назвать ее menagerie (``зверинец''). В таким случае администратору нужно будет набрать примерно такую команду:

    mysql> GRANT ALL ON menagerie.* TO your_mysql_name;

где your_mysql_name - присвоенное вам имя MySQL.

### 3.3.1 Создание и выбор базы данных

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

    mysql> CREATE DATABASE menagerie;

В Unix имеет значение регистр символов в именах баз данных (в отличие от ключевых слов SQL), так что в этой ОС вам всегда придется называть свою базу `menagerie`, а не `Menagerie`, `MENAGERIE` или еще как-нибудь. Это же правило распространяется и на имена таблиц (в Windows данное ограничение не действует, однако при обращении к базам и таблицам в пределах одного запроса, тем не менее, можно использовать только один регистр).

При создании базы данных она автоматически не выбирается; выбирать ее нужно отдельно. Сделать menagerie текущей базой можно с помощью следующей команды:

    mysql> USE menagerie
    Database changed

Создавать базу нужно только однажды, но выбирать ее приходится в каждом сеансе работы с `mysql`. Делать это можно с помощью команды USE, представленной выше. А можно выбирать базу и из командной строки при запуске `mysql`. Для этого достаточно лишь ввести ее имя после параметров соединения, которые нужно вводить в любом случае. Например:

    shell> mysql -h host -u user -p menagerie
    Enter password: ********

Обратите внимание: в вышеприведенной команде `menagerie` не является паролем. Ввести пароль в командной строке после параметра -p можно без пробела (например, `-pmypassword`, а не `-p mypassword`). Впрочем, пароль в командной строке все равно лучше не вводить, так как таким образом его могут и подсмотреть.

### 3.3.2 Создание таблицы

Как вы уже успели убедиться, создать базу данных было просто. Однако пока что в ней ничего нет - в этом можно удостовериться при помощи команды `SHOW TABLES`:

    mysql> SHOW TABLES;
    Empty set (0.00 sec)

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

Вам обязательно будет нужна таблица, содержащая по записи на каждое из животных. Назвать ее можно `pet`, и храниться в ней будут, как минимум, имена. Но так как само по себе имя неинформативно, в таблице должны будут присутствовать и другие данные. Например, если домашние животные есть более чем у одного члена вашей семьи, в таблицу можно добавить и имя владельца каждого животного. Кроме того, в базу стоит внести и описательную информацию - например, вид и пол животного.

Но вот как быть с возрастом? Эта информация тоже может оказаться полезной, но хранить такие данные в базе неудобно. Возраст со временем меняется, а это значит, что придется довольно часто обновлять записи. Значительно удобнее хранить фиксированные значения - например, даты рождения. В таком случае возраст всегда можно получить, вычислив разницу между текущей датой и датой рождения. В MySQL есть функции для арифметических действий над данными, так что это совсем несложно. Хранение даты рождения имеет и другие преимущества:

- Базу данных можно использовать для выдачи напоминаний о приближающихся днях рождения животных (не спешите улыбаться: та же задача может возникнуть и при работе с деловой базой данных, которая должна уметь напоминать о днях рождения клиентов, облегчая таким образом рассылку поздравлений).
- Возраст можно подсчитывать относительно любой даты, а не только для текущей. Например, если записать в базу и дату смерти животного, всегда можно будет узнать, сколько лет ему было на момент смерти.

Можно было бы придумать и еще какие-нибудь данные, которые неплохо было бы хранить в таблице pet, но пока что мы ограничимся уже выбранными: именем (`name`), именем владельца (`owner`), видом (`species`), полом (`sex`), датой рождения (`birth`) и датой смерти (`death`).

При помощи команды `CREATE TABLE` определим структуру новой таблицы:

    mysql> CREATE TABLE pet (name VARCHAR(20), owner VARCHAR(20),
        -> species VARCHAR(20), sex CHAR(1), birth DATE, death DATE);

Тип `VARCHAR` отлично подойдет для хранения имени животного, имени владельца и названия вида, так как длина данных этого типа может варьироваться. Конечно, длины таких столбцов вовсе не должны совпадать и не должны быть равны 20 - можно выбрать любое значение в пределах от 1 до 255 (если при выборе длины столбца вы ошибетесь, и при работе с базой окажется, что столбец маловат, можно будет исправить ошибку при помощи команды `ALTER TABLE`).

Пол животного можно обозначать несколькими способами, например буквами "m" и "f", или словами male (мужской) и female (женский). С буквами "m" и "f" будет проще.

Применение типа данных `DATE` для хранения дат рождения и смерти вполне очевидно.

Теперь, когда таблица создана, команда `SHOW TABLES` должна вывести следующее:

    mysql> SHOW TABLES;
    +---------------------+
    | Tables in menagerie |
    +---------------------+
    | pet                 |
    +---------------------+

Проверить, правильно была ли таблица создана в соответствии с планом, можно при помощи команды `DESCRIBE`:

    mysql> DESCRIBE pet;
    +---------+-------------+------+-----+---------+-------+
    | Field   | Type        | Null | Key | Default | Extra |
    +---------+-------------+------+-----+---------+-------+
    | name    | varchar(20) | YES  |     | NULL    |       |
    | owner   | varchar(20) | YES  |     | NULL    |       |
    | species | varchar(20) | YES  |     | NULL    |       |
    | sex     | char(1)     | YES  |     | NULL    |       |
    | birth   | date        | YES  |     | NULL    |       |
    | death   | date        | YES  |     | NULL    |       |
    +---------+-------------+------+-----+---------+-------+

Использовать команду `DESCRIBE` можно в любое время, например, если вы забудете имена столбцов или типы, к которым они относятся.

### 3.3.3 Загрузка данных в таблицу

Создав таблицу, нужно позаботиться об ее заполнении. Для этого предназначены команды `LOAD DATA` и `INSERT`.

Предположим, ваши записи соответствуют приведенным в этой таблице (обратите внимание: MySQL принимает даты в формате `ГГГГ-ММ-ДД`; возможно, к такой записи вы не привыкли).

name | owner | species | sex | birth | death
Fluffy | Harold | cat | f | 1993-02-04 |
Claws | Gwen | cat | m | 1994-03-17 |
Buffy | Harold | dog | f | 1989-05-13 |
Fang | Benny | dog | m | 1990-08-27 |
Bowser | Diane | dog | m | 1998-08-31 | 1995-07-29
Chirpy | Gwen | bird | f | 1998-09-11 |
Whistler | Gwen | bird |  | 1997-12-09 |
Slim | Benny | snake | m | 1996-04-29 |

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

Создайте текстовый файл с именем ``pet.txt'`, содержащий по одной записи в каждой строке (значения столбцов должны быть разделены символами табуляции и даны в том порядке, который был определен командой `CREATE TABLE`). Незаполненным полям (например, неизвестный пол или даты смерти живых на сегодняшний день животных), можно присвоить значение `NULL`. В текстовом файле это значение представляется символами `\N`. Например, запись для птицы `Whistler` должна выглядеть примерно так (между значениями должны располагаться одиночные символы табуляции):

name | owner | species | sex | birth | death
`Whistler` | `Gwen` | `bird` | `\N` | `1997-12-09` | `\N`

Загрузить файл ``pet.txt'` в таблицу можно с помощью следующей команды:

    mysql> LOAD DATA LOCAL INFILE "pet.txt" INTO TABLE pet;

Маркер конца строки и символ, разделяющий значения столбцов, можно специально задать в команде `LOAD DATA`, но по умолчанию используются символы табуляции и перевода строки. Воспринимая их, команда сможет корректно прочитать файл ``pet.txt'`.

При добавлении одиночных записей используется команда `INSERT`. В самом простом варианте ее применения необходимо задать значения каждого столбца, в том порядке, в каком они были перечислены в команде `CREATE TABLE`. Предположим, Диана (Diane) купила хомячка по имени Puffball. Соответствующую запись в таблицу с можно внести с помощью команды `INSERT` примерно так:

    mysql> INSERT INTO pet
        -> VALUES ('Puffball','Diane','hamster','f','1999-03-30',NULL);

Обратите внимание на то, что здесь строковые выражения и даты представлены в виде ограниченных кавычками строк. Кроме того, в команде `INSERT` отсутствующие данные можно прямо заменять на `NULL`. Пользоваться эвфемизмом `\N`, как в команде `LOAD DATA`, нужды нет.

Этот пример наглядно показывает, что если бы с самого начала все данные вносились в базу при помощи нескольких команд `INSERT`, а не одной команды `LOAD DATA`, то набирать пришлось бы гораздо больше текста.

### 3.3.4 Выборка информации из таблицы

Информация извлекается из таблиц при помощи команды `SELECT`. Вызывается она так:

    SELECT what_to_select
    FROM which_table
    WHERE conditions_to_satisfy

где `what_to_select` обозначает нужные данные. Это может быть список столбцов или символ `*` (``все столбцы''). which_table указывает таблицу, из которой должны быть извлечены данные. Условие `WHERE` использовать необязательно, но если оно все же присутствует в вызове команды, то параметр `conditions_to_satisfy` задает условия, которым должны соответствовать нужные строки.

#### 3.3.4.1 Выборка всех данных

В самом простом варианте вызова `SELECT` из таблицы извлекаются сразу все данные:

    mysql> SELECT * FROM pet;
    +----------+--------+---------+------+------------+------------+
    | name     | owner  | species | sex  | birth      | death      |
    +----------+--------+---------+------+------------+------------+
    | Fluffy   | Harold | cat     | f    | 1993-02-04 | NULL       |
    | Claws    | Gwen   | cat     | m    | 1994-03-17 | NULL       |
    | Buffy    | Harold | dog     | f    | 1989-05-13 | NULL       |
    | Fang     | Benny  | dog     | m    | 1990-08-27 | NULL       |
    | Bowser   | Diane  | dog     | m    | 1998-08-31 | 1995-07-29 |
    | Chirpy   | Gwen   | bird    | f    | 1998-09-11 | NULL       |
    | Whistler | Gwen   | bird    | NULL | 1997-12-09 | NULL       |
    | Slim     | Benny  | snake   | m    | 1996-04-29 | NULL       |
    | Puffball | Diane  | hamster | f    | 1999-03-30 | NULL       |
    +----------+--------+---------+------+------------+------------+

Использовать `SELECT` таким образом удобно, когда нужно просмотреть всю таблицу, например, после того, как в нее была загружена первая порция данных. Как часто случается, в выведенных на экран данных сразу можно увидеть ошибку в таблице: `Bowser`, оказывается, успел умереть еще до того, как родился! Заглянув в его родословную обнаруживаем, что пес родился в 1989, а не в 1998 году.

Исправить ошибку можно как минимум двумя способами:

- Отредактировать файл pet.txt, затем очистить таблицу и снова заполнить ее командами DELETE и LOAD DATA:

      mysql> SET AUTOCOMMIT=1;  # Used for quick re-create of the table
      mysql> DELETE FROM pet;
      mysql> LOAD DATA LOCAL INFILE "pet.txt" INTO TABLE pet;

  Однако в таком случае придется снова ввести запись о Puffball.
- Исправить только ошибочно введенные данные при помощи команды `UPDATE`:

      mysql> UPDATE pet SET birth = "1989-08-31" WHERE name = "Bowser";

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

#### 3.3.4.2 Выборка определенных строк

Из таблицы можно выбрать и только нужные строки. Например, если вы хотите проверить правильность внесенных в дату рождения собаки Bowser изменений, соответствующую запись можно получить следующим способом:

    mysql> SELECT * FROM pet WHERE name = "Bowser";
    +--------+-------+---------+------+------------+------------+
    | name   | owner | species | sex  | birth      | death      |
    +--------+-------+---------+------+------------+------------+
    | Bowser | Diane | dog     | m    | 1989-08-31 | 1995-07-29 |
    +--------+-------+---------+------+------------+------------+

Теперь видно, что год рождения теперь записан правильно -1989, а не 1998.

В операции сравнения строк обычно не учитывается регистр символов, так что имя можно записать как `"bowser"`, `"BOWSER"` и т.п. Результаты запросов будут идентичными.

В условиях может указываться любой из столбцов, а не только `name`. Если, например, вам нужно узнать, какие их животных родились после 1998 года, в условие вводится значение столбца `birth`:

    mysql> SELECT * FROM pet WHERE birth >= "1998-1-1";
    +----------+-------+---------+------+------------+-------+
    | name     | owner | species | sex  | birth      | death |
    +----------+-------+---------+------+------------+-------+
    | Chirpy   | Gwen  | bird    | f    | 1998-09-11 | NULL  |
    | Puffball | Diane | hamster | f    | 1999-03-30 | NULL  |
    +----------+-------+---------+------+------------+-------+

Условия можно и комбинировать, например для того, чтобы выделить всех собак женского пола:

    mysql> SELECT * FROM pet WHERE species = "dog" AND sex = "f";
    +-------+--------+---------+------+------------+-------+
    | name  | owner  | species | sex  | birth      | death |
    +-------+--------+---------+------+------------+-------+
    | Buffy | Harold | dog     | f    | 1989-05-13 | NULL  |
    +-------+--------+---------+------+------------+-------+

В предыдущем запросе использован оператор `AND`. Существует еще и оператор `OR`:

    mysql> SELECT * FROM pet WHERE species = "snake" OR species = "bird";
    +----------+-------+---------+------+------------+-------+
    | name     | owner | species | sex  | birth      | death |
    +----------+-------+---------+------+------------+-------+
    | Chirpy   | Gwen  | bird    | f    | 1998-09-11 | NULL  |
    | Whistler | Gwen  | bird    | NULL | 1997-12-09 | NULL  |
    | Slim     | Benny | snake   | m    | 1996-04-29 | NULL  |
    +----------+-------+---------+------+------------+-------+

Операторы `AND` и `OR` можно использовать совместно. В таком случае с помощью скобок можно указать порядок группировки условий:

    mysql> SELECT * FROM pet WHERE (species = "cat" AND sex = "m")
        -> OR (species = "dog" AND sex = "f");
    +-------+--------+---------+------+------------+-------+
    | name  | owner  | species | sex  | birth      | death |
    +-------+--------+---------+------+------------+-------+
    | Claws | Gwen   | cat     | m    | 1994-03-17 | NULL  |
    | Buffy | Harold | dog     | f    | 1989-05-13 | NULL  |
    +-------+--------+---------+------+------------+-------+

#### 3.3.4.3 Выборка определенных столбцов

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

    mysql> SELECT name, birth FROM pet;
    +----------+------------+
    | name     | birth      |
    +----------+------------+
    | Fluffy   | 1993-02-04 |
    | Claws    | 1994-03-17 |
    | Buffy    | 1989-05-13 |
    | Fang     | 1990-08-27 |
    | Bowser   | 1989-08-31 |
    | Chirpy   | 1998-09-11 |
    | Whistler | 1997-12-09 |
    | Slim     | 1996-04-29 |
    | Puffball | 1999-03-30 |
    +----------+------------+

Получить имена владельцев животных можно с помощью следующего запроса:

    mysql> SELECT owner FROM pet;
    +--------+
    | owner  |
    +--------+
    | Harold |
    | Gwen   |
    | Harold |
    | Benny  |
    | Diane  |
    | Gwen   |
    | Gwen   |
    | Benny  |
    | Diane  |
    +--------+

Однако этот запрос просто загружает поля с именем владельца из каждой записи, а некоторые имена встречаются более одного раза. Сократить количество выводимых строк можно, воспользовавшись ключевым словом `DISTINCT` - тогда будут выводиться только уникальные записи:

    mysql> SELECT DISTINCT owner FROM pet;
    +--------+
    | owner  |
    +--------+
    | Benny  |
    | Diane  |
    | Gwen   |
    | Harold |
    +--------+

При помощи выражения `WHERE` можно комбинировать выбор строк и столбцов. Например для того, чтобы загрузить даты рождения только кошек и собак, можно воспользоваться следующим запросом:

    mysql> SELECT name, species, birth FROM pet
        -> WHERE species = "dog" OR species = "cat";
    +--------+---------+------------+
    | name   | species | birth      |
    +--------+---------+------------+
    | Fluffy | cat     | 1993-02-04 |
    | Claws  | cat     | 1994-03-17 |
    | Buffy  | dog     | 1989-05-13 |
    | Fang   | dog     | 1990-08-27 |
    | Bowser | dog     | 1989-08-31 |
    +--------+---------+------------+

#### 3.3.4.4 Сортировка строк

Вы уже, наверное, заметили, что результаты, выдававшиеся запросами из предыдущих примеров, выводились без какой-либо сортировки. Но ведь часто разобраться в результатах легче, если они отсортированы. Для этого используется выражение `ORDER BY`.

Так выглядят даты рождения животных в отсортированном виде:

    mysql> SELECT name, birth FROM pet ORDER BY birth;
    +----------+------------+
    | name     | birth      |
    +----------+------------+
    | Buffy    | 1989-05-13 |
    | Bowser   | 1989-08-31 |
    | Fang     | 1990-08-27 |
    | Fluffy   | 1993-02-04 |
    | Claws    | 1994-03-17 |
    | Slim     | 1996-04-29 |
    | Whistler | 1997-12-09 |
    | Chirpy   | 1998-09-11 |
    | Puffball | 1999-03-30 |
    +----------+------------+

Над столбцами с символьными значениями операция сортировки - как и все другие операции сравнения - обычно проводится без учета регистра символов. Это значит, что порядок расположения столбцов, совпадающих во всем, кроме регистра символов, относительно друг друга будет не определен. Провести сортировку с учетом регистра символов можно при помощи команды `BINARY`: `ORDER BY BINARY(поле)`.

Для сортировки в обратном порядке к имени столбца следует добавить ключевое слово `DESC` (по убыванию):

    mysql> SELECT name, birth FROM pet ORDER BY birth DESC;
    +----------+------------+
    | name     | birth      |
    +----------+------------+
    | Puffball | 1999-03-30 |
    | Chirpy   | 1998-09-11 |
    | Whistler | 1997-12-09 |
    | Slim     | 1996-04-29 |
    | Claws    | 1994-03-17 |
    | Fluffy   | 1993-02-04 |
    | Fang     | 1990-08-27 |
    | Bowser   | 1989-08-31 |
    | Buffy    | 1989-05-13 |
    +----------+------------+

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

    mysql> SELECT name, species, birth FROM pet ORDER BY species, birth DESC;
    +----------+---------+------------+
    | name     | species | birth      |
    +----------+---------+------------+
    | Chirpy   | bird    | 1998-09-11 |
    | Whistler | bird    | 1997-12-09 |
    | Claws    | cat     | 1994-03-17 |
    | Fluffy   | cat     | 1993-02-04 |
    | Fang     | dog     | 1990-08-27 |
    | Bowser   | dog     | 1989-08-31 |
    | Buffy    | dog     | 1989-05-13 |
    | Puffball | hamster | 1999-03-30 |
    | Slim     | snake   | 1996-04-29 |
    +----------+---------+------------+

Обратите внимание: действие ключевого слова `DESC` распространяется только на тот столбец, имя которого располагается в запросе прямо перед этим словом (в данном случае - `birth`); значения поля species по-прежнему отсортированы в возрастающем порядке.

#### 3.3.4.5 Вычисление дат

В MySQL имеется несколько функций, реализующих арифметические операции над датами. Эти функции позволяют, например, вычислять возраст или получать части даты.

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

    mysql> SELECT name, birth, CURRENT_DATE,
        -> (YEAR(CURRENT_DATE)-YEAR(birth))
        -> - (RIGHT(CURRENT_DATE,5)<RIGHT(birth,5))
        -> AS age
        -> FROM pet;
    +----------+------------+--------------+------+
    | name     | birth      | CURRENT_DATE | age  |
    +----------+------------+--------------+------+
    | Fluffy   | 1993-02-04 | 2001-08-29   |    8 |
    | Claws    | 1994-03-17 | 2001-08-29   |    7 |
    | Buffy    | 1989-05-13 | 2001-08-29   |   12 |
    | Fang     | 1990-08-27 | 2001-08-29   |   11 |
    | Bowser   | 1989-08-31 | 2001-08-29   |   11 |
    | Chirpy   | 1998-09-11 | 2001-08-29   |    2 |
    | Whistler | 1997-12-09 | 2001-08-29   |    3 |
    | Slim     | 1996-04-29 | 2001-08-29   |    5 |
    | Puffball | 1999-03-30 | 2001-08-29   |    2 |
    +----------+------------+--------------+------+

В этом примере функция `YEAR()` выделяет из даты год, а `RIGHT()` - пять крайних справа символов, представляющих календарный день (MM-DD). Часть выражения, сравнивающая даты, выдает 1 или 0, что позволяет уменьшить результат на единицу, если текущий день (`CURRENT_DATE`) находится к началу календаря ближе, нежели день рождения животного. Все выражение смотрится несколько неуклюже, поэтому вместо него в заголовке соответствующего столбца результатов выводится псевдоним (`age` - "возраст").

Запрос неплохо работает, но разобраться в результатах было бы проще, если бы строки располагались в определенном порядке. Этого можно достичь, добавив в запрос выражение `ORDER BY` name и отсортировав таким образом результаты по имени:

    mysql> SELECT name, birth, CURRENT_DATE,
        -> (YEAR(CURRENT_DATE)-YEAR(birth))
        -> - (RIGHT(CURRENT_DATE,5)<RIGHT(birth,5))
        -> AS age
        -> FROM pet ORDER BY name;
    +----------+------------+--------------+------+
    | name     | birth      | CURRENT_DATE | age  |
    +----------+------------+--------------+------+
    | Bowser   | 1989-08-31 | 2001-08-29   |   11 |
    | Buffy    | 1989-05-13 | 2001-08-29   |   12 |
    | Chirpy   | 1998-09-11 | 2001-08-29   |    2 |
    | Claws    | 1994-03-17 | 2001-08-29   |    7 |
    | Fang     | 1990-08-27 | 2001-08-29   |   11 |
    | Fluffy   | 1993-02-04 | 2001-08-29   |    8 |
    | Puffball | 1999-03-30 | 2001-08-29   |    2 |
    | Slim     | 1996-04-29 | 2001-08-29   |    5 |
    | Whistler | 1997-12-09 | 2001-08-29   |    3 |
    +----------+------------+--------------+------+

Отсортировать результаты по возрасту также можно при помощи выражения `ORDER BY`:

    mysql> SELECT name, birth, CURRENT_DATE,
        -> (YEAR(CURRENT_DATE)-YEAR(birth))
        -> - (RIGHT(CURRENT_DATE,5)<RIGHT(birth,5))
        -> AS age
        -> FROM pet ORDER BY age;
    +----------+------------+--------------+------+
    | name     | birth      | CURRENT_DATE | age  |
    +----------+------------+--------------+------+
    | Chirpy   | 1998-09-11 | 2001-08-29   |    2 |
    | Puffball | 1999-03-30 | 2001-08-29   |    2 |
    | Whistler | 1997-12-09 | 2001-08-29   |    3 |
    | Slim     | 1996-04-29 | 2001-08-29   |    5 |
    | Claws    | 1994-03-17 | 2001-08-29   |    7 |
    | Fluffy   | 1993-02-04 | 2001-08-29   |    8 |
    | Fang     | 1990-08-27 | 2001-08-29   |   11 |
    | Bowser   | 1989-08-31 | 2001-08-29   |   11 |
    | Buffy    | 1989-05-13 | 2001-08-29   |   12 |
    +----------+------------+--------------+------+

подобный же запрос поможет определить возраст, которого достигли умершие животные на момент смерти. Выделить умерших животных можно, проверив значение поля `death` на предмет равенства `NULL`. Затем для записей, значения поля `death` которых не равно `NULL`, можно вычислить разницу между датами смерти и рождения:

    mysql> SELECT name, birth, death,
        -> (YEAR(death)-YEAR(birth)) - (RIGHT(death,5)<RIGHT(birth,5))
        -> AS age
        -> FROM pet WHERE death IS NOT NULL ORDER BY age;
    +--------+------------+------------+------+
    | name   | birth      | death      | age  |
    +--------+------------+------------+------+
    | Bowser | 1989-08-31 | 1995-07-29 |    5 |
    +--------+------------+------------+------+

В этом запросе используется выражение `death IS NOT NULL`, а не death <> `NULL`, так как `NULL` - особое значение (более подробные пояснения приведены в разделе see section 3.3.4.6 Работа с значениями NULL).

А как поступать, если потребуется определить, дни рождения каких животных наступят в следующем месяце? Для таких расчетов день и год значения не имеют; из столбца, содержащего дату рождения, нас интересует только месяц. В MySQL предусмотрено несколько функций для получения частей дат - `YEAR()`, `MONTH()`, и `DAYOFMONTH()`. В данном случае нам подойдет функция `MONTH()`. Увидеть работу этой функции можно с помощью простого запроса, выводящего дату рождения `birth` и `MONTH(birth)`:

    mysql> SELECT name, birth, MONTH(birth) FROM pet;
    +----------+------------+--------------+
    | name     | birth      | MONTH(birth) |
    +----------+------------+--------------+
    | Fluffy   | 1993-02-04 |            2 |
    | Claws    | 1994-03-17 |            3 |
    | Buffy    | 1989-05-13 |            5 |
    | Fang     | 1990-08-27 |            8 |
    | Bowser   | 1989-08-31 |            8 |
    | Chirpy   | 1998-09-11 |            9 |
    | Whistler | 1997-12-09 |           12 |
    | Slim     | 1996-04-29 |            4 |
    | Puffball | 1999-03-30 |            3 |
    +----------+------------+--------------+

Найти животных, дни рождения которых наступят в следующем месяце, тоже несложно. Предположим, что сейчас на дворе апрель. Тогда номер текущего месяца - 4, а искать надо животных, родившихся в мае (5-м месяце), таким образом:

    mysql> SELECT name, birth FROM pet WHERE MONTH(birth) = 5;
    +-------+------------+
    | name  | birth      |
    +-------+------------+
    | Buffy | 1989-05-13 |
    +-------+------------+

Конечно, в декабре возникают некоторые осложнения. Если просто добавить единицу к номеру месяца (12) и поискать животных, родившихся в тринадцатом месяце, найти что-нибудь вряд ли удастся. Вместо этого нужно искать животных, родившихся в январе (1-м месяце).

Можно даже написать небольшой запрос, который будет работать вне зависимости от того, какой нынче месяц. Функция `DATE_ADD()` позволяет прибавить к дате некоторый интервал времени. Если добавить к значению, возвращаемому функцией `NOW()`, месяц, а затем извлечь из получившейся даты номер месяца при помощи функции `MONTH()`, мы получим именно тот месяц, который нам нужен:

    mysql> SELECT name, birth FROM pet
        -> WHERE MONTH(birth) = MONTH(DATE_ADD(NOW(), INTERVAL 1 MONTH));

Ту же задачу можно решить и другим методом - для этого нужно прибавить единицу к номеру месяца, следующего за текущим (воспользовавшись функцией расчета по модулю (MOD) для перехода к 0, если номер текущего месяца равен 12):

    mysql> SELECT name, birth FROM pet
        -> WHERE MONTH(birth) = MOD(MONTH(NOW()), 12) + 1;

Функция `MONTH` возвращает число от 1 до 12, а выражение `MOD(число,12)` - число от 0 до 11. Поэтому операцию сложения нужно проводить после `MOD()`, иначе результат перепрыгнет с ноября (11) сразу на январь (1).

#### 3.3.4.6 Работа с значениями NULL

К `NULL`-значениям нужно привыкнуть. По идее, `NULL` обозначает отсутствующее или неизвестное значение и обрабатывается отличным от других значений образом. Проверить значение на равенство `NULL` с помощью обычных арифметических операторов сравнения (=, < или <>) нельзя. Это отлично иллюстрирует следующий запрос:

    mysql> SELECT 1 = NULL, 1 <> NULL, 1 < NULL, 1 > NULL;
    +----------+-----------+----------+----------+
    | 1 = NULL | 1 <> NULL | 1 < NULL | 1 > NULL |
    +----------+-----------+----------+----------+
    |     NULL |      NULL |     NULL |     NULL |
    +----------+-----------+----------+----------+

Очевидно, что от таких сравнений значащих результатов ожидать нечего. Вместо этого нужно пользоваться операторами `IS NULL` и `IS NOT NULL`:

    mysql> SELECT 1 IS NULL, 1 IS NOT NULL;
    +-----------+---------------+
    | 1 IS NULL | 1 IS NOT NULL |
    +-----------+---------------+
    |         0 |             1 |
    +-----------+---------------+

Отметим: в MySQL 0 или `NULL` приравнивается к логическому false, а все остальное - к true. По умолчанию значение "истина" для булевого оператора равно 1.

Именно из-за того, что при работе с `NULL` действуют особые правила, в предыдущем разделе для поиска умерших животных использовалось выражение `death IS NOT NULL`, а не `death <> NULL`.

Два `NULL`-значения считаются одинаковыми в `GROUP BY`.

При выполнении `ORDER BY`, `NULL`-значения идут в первую очередь если вы выполняете `ORDER ... ASC` и в последнюю - если `ORDER BY ... DESC`.

Обратите внимание, что в MySQL 4.0.2 - 4.0.10, `NULL`-значения всегда возвращались в первую очередь, вне зависимости от сортировки.

#### 3.3.4.7 Сравнение по шаблонам

В MySQL реализовано стандартное для SQL сравнение по шаблонам, а также особый тип такого сравнения - он основан на использовании выражений, подобных применяющимся в служебных программах Unix (таких, как vi, grep и sed).

В SQL при сравнении по шаблону символ ``_'` обозначает любой одиночный символ, а ``%'` - определенное количество символов (включая ноль символов). В MySQL в SQL-шаблонах по умолчанию не учитывается регистр символов. При работе с шаблонами SQL использование операторов `=` или `<>` не допускается, вместо этого применяются операторы сравнения `LIKE` или `NOT LIKE`.

Найти все имена, начинающиеся с ``b'`, можно следующим образом:

    mysql> SELECT * FROM pet WHERE name LIKE "b%";
    +--------+--------+---------+------+------------+------------+
    | name   | owner  | species | sex  | birth      | death      |
    +--------+--------+---------+------+------------+------------+
    | Buffy  | Harold | dog     | f    | 1989-05-13 | NULL       |
    | Bowser | Diane  | dog     | m    | 1989-08-31 | 1995-07-29 |
    +--------+--------+---------+------+------------+------------+

Найти все имена, заканчивающиеся на ``fy'`, можно следующим образом:

    mysql> SELECT * FROM pet WHERE name LIKE "%fy";
    +--------+--------+---------+------+------------+-------+
    | name   | owner  | species | sex  | birth      | death |
    +--------+--------+---------+------+------------+-------+
    | Fluffy | Harold | cat     | f    | 1993-02-04 | NULL  |
    | Buffy  | Harold | dog     | f    | 1989-05-13 | NULL  |
    +--------+--------+---------+------+------------+-------+

Найти все имена, содержащие ``w'`, можно следующим образом:

    mysql> SELECT * FROM pet WHERE name LIKE "%w%";
    +----------+-------+---------+------+------------+------------+
    | name     | owner | species | sex  | birth      | death      |
    +----------+-------+---------+------+------------+------------+
    | Claws    | Gwen  | cat     | m    | 1994-03-17 | NULL       |
    | Bowser   | Diane | dog     | m    | 1989-08-31 | 1995-07-29 |
    | Whistler | Gwen  | bird    | NULL | 1997-12-09 | NULL       |
    +----------+-------+---------+------+------------+------------+

Найти все имена, содержащие ровно пять символов, можно при помощи шаблонного символа ``_'`:

    mysql> SELECT * FROM pet WHERE name LIKE "_____";
    +-------+--------+---------+------+------------+-------+
    | name  | owner  | species | sex  | birth      | death |
    +-------+--------+---------+------+------------+-------+
    | Claws | Gwen   | cat     | m    | 1994-03-17 | NULL  |
    | Buffy | Harold | dog     | f    | 1989-05-13 | NULL  |
    +-------+--------+---------+------+------------+-------+

Во втором типе шаблонов, предусмотренных в MySQL, используются расширенные регулярные выражения. При поиске совпадений на основе такого шаблона шаблоном нужно пользоваться операторами `REGEXP` и `NOT REGEXP` (или их синонимами - `RLIKE` и `NOT RLIKE`).

Ниже приведены некоторые характеристики расширенных регулярных выражений:

- ``.'` обозначает любой символ.
- Класс символов ``[...]'` обозначает любой из символов в скобках. Например, ``[abc]'` обозначает ``a'`, ``b'` или ``c'`. Набор символов можно обозначить с помощью дефиса. ``[a-z]'` обозначает любую букву нижнего регистра, а ``[0-9]'` - любую цифру.
- ``*'` обозначает ноль или более экземпляров символа, стоящего перед ним. Например, ``x*'` обозначает любое количество символов ``x'`, ``[0-9]*'` обозначает любое количество цифр, а ``.*'` - любое количество любых символов.
- Для шаблона выдается совпадение, если поисковый контекст обнаружен в любой из частей значения, в котором производится поиск (для шаблонов SQL совпадение выдается только в случае, если совпадает все значение).
- ``Закрепить'' шаблон так, чтобы проверять совпадения с началом или концом значения можно с помощью символов ``^'` (начало) или ``$'` (конец), которые располагаются в начале или в конце шаблона соответственно.

Чтобы продемонстрировать работу регулярных выражений, приведенные выше запросы `LIKE` здесь переписаны с использованием `REGEXP`.

Найти все имена, начинающиеся с ``b'`, можно при помощи символа ``^'`, привязывающего шаблон к началу имени:

    mysql> SELECT * FROM pet WHERE name REGEXP "^b";
    +--------+--------+---------+------+------------+------------+
    | name   | owner  | species | sex  | birth      | death      |
    +--------+--------+---------+------+------------+------------+
    | Buffy  | Harold | dog     | f    | 1989-05-13 | NULL       |
    | Bowser | Diane  | dog     | m    | 1989-08-31 | 1995-07-29 |
    +--------+--------+---------+------+------------+------------+

В версиях MySQL до 3.23.4 `REGEXP` учитывает регистр символов, и приведенный запрос не возвратит никаких результатов. Для поиска символов ``b'` верхнего или нижнего регистра воспользуйтесь следующим запросом:

    mysql> SELECT * FROM pet WHERE name REGEXP "^[bB]";

Начиная с версии MySQL 3.23.4, заставить `REGEXP` учитывать регистр символов можно с помощью ключевого слова `BINARY`. В этом запросе положительный результат поиска будет получен только при обнаружении символа 'b' нижнего регистра в начале имени:

    mysql> SELECT * FROM pet WHERE name REGEXP BINARY "^b";

Найти все имена, заканчивающиеся на ``fy'`, можно при помощи символа ``$'`, привязывающего шаблон к концу имени:

    mysql> SELECT * FROM pet WHERE name REGEXP "fy$";
    +--------+--------+---------+------+------------+-------+
    | name   | owner  | species | sex  | birth      | death |
    +--------+--------+---------+------+------------+-------+
    | Fluffy | Harold | cat     | f    | 1993-02-04 | NULL  |
    | Buffy  | Harold | dog     | f    | 1989-05-13 | NULL  |
    +--------+--------+---------+------+------------+-------+

Найти все имена, содержащие символ ``w'` любого регистра, можно так:

    mysql> SELECT * FROM pet WHERE name REGEXP "w";
    +----------+-------+---------+------+------------+------------+
    | name     | owner | species | sex  | birth      | death      |
    +----------+-------+---------+------+------------+------------+
    | Claws    | Gwen  | cat     | m    | 1994-03-17 | NULL       |
    | Bowser   | Diane | dog     | m    | 1989-08-31 | 1995-07-29 |
    | Whistler | Gwen  | bird    | NULL | 1997-12-09 | NULL       |
    +----------+-------+---------+------+------------+------------+

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

Найти все имена, содержащие ровно пять символов, можно, если привязать поиск к началу и концу имени с помощью символов ``^'` и ``$'` и поставить пять символов ``.'` между ними:

    mysql> SELECT * FROM pet WHERE name REGEXP "^.....$";
    +-------+--------+---------+------+------------+-------+
    | name  | owner  | species | sex  | birth      | death |
    +-------+--------+---------+------+------------+-------+
    | Claws | Gwen   | cat     | m    | 1994-03-17 | NULL  |
    | Buffy | Harold | dog     | f    | 1989-05-13 | NULL  |
    +-------+--------+---------+------+------------+-------+

Предыдущий запрос можно записать и при помощи оператора ``{n}'` (``повторить-`n`-раз''):

    mysql> SELECT * FROM pet WHERE name REGEXP "^.{5}$";
    +-------+--------+---------+------+------------+-------+
    | name  | owner  | species | sex  | birth      | death |
    +-------+--------+---------+------+------------+-------+
    | Claws | Gwen   | cat     | m    | 1994-03-17 | NULL  |
    | Buffy | Harold | dog     | f    | 1989-05-13 | NULL  |
    +-------+--------+---------+------+------------+-------+

#### 3.3.4.8 Подсчет строк

Базы данных часто используются для получения ответа на вопросы типа: ``как часто данные определенного типа встречаются в таблице?'' Вам, например, может понадобиться узнать общее количество животных, или то, сколько животных имеется у каждого из владельцев, или провести статистические исследования на базе хранящейся информации.

Процедура подсчета количества животных в сущности идентична подсчету количества строк в таблице, так как на каждое животное приходится по одной записи. Функция `COUNT()` подсчитает количество непустых результатов, и с ее помощью можно составить следующий запрос для определения числа животных:

    mysql> SELECT COUNT(*) FROM pet;
    +----------+
    | COUNT(*) |
    +----------+
    |        9 |
    +----------+

Ранее мы уже извлекали из таблицы имена владельцев животных. При помощи функции `COUNT()` можно узнать, сколько животных принадлежит каждому из владельцев:

    mysql> SELECT owner, COUNT(*) FROM pet GROUP BY owner;
    +--------+----------+
    | owner  | COUNT(*) |
    +--------+----------+
    | Benny  |        2 |
    | Diane  |        2 |
    | Gwen   |        3 |
    | Harold |        2 |
    +--------+----------+

Обратите внимание на использование команды `GROUP BY` для объединения всех записей по каждому из владельцев. Без этой команды запрос выдал бы только сообщение об ошибке:

    mysql> SELECT owner, COUNT(owner) FROM pet;
    ERROR 1140 at line 1: Mixing of GROUP columns (MIN(),MAX(),COUNT()...)
    with no GROUP columns is illegal if there is no GROUP BY clause

Команды `COUNT()` и `GROUP BY` очень помогают характеризовать данные различными способами. В примерах, приведенных ниже, вы увидите и другие способы проведения статистических подсчетов.

Количество животных каждого вида:

    mysql> SELECT species, COUNT(*) FROM pet GROUP BY species;
    +---------+----------+
    | species | COUNT(*) |
    +---------+----------+
    | bird    |        2 |
    | cat     |        2 |
    | dog     |        3 |
    | hamster |        1 |
    | snake   |        1 |
    +---------+----------+

Количество животных каждого пола:

    mysql> SELECT sex, COUNT(*) FROM pet GROUP BY sex;
    +------+----------+
    | sex  | COUNT(*) |
    +------+----------+
    | NULL |        1 |
    | f    |        4 |
    | m    |        4 |
    +------+----------+

(в этой таблице результатов `NULL` обозначает, что пол животного неизвестен)

Количество животных каждого вида с учетом пола:

    mysql> SELECT species, sex, COUNT(*) FROM pet GROUP BY species, sex;
    +---------+------+----------+
    | species | sex  | COUNT(*) |
    +---------+------+----------+
    | bird    | NULL |        1 |
    | bird    | f    |        1 |
    | cat     | f    |        1 |
    | cat     | m    |        1 |
    | dog     | f    |        1 |
    | dog     | m    |        2 |
    | hamster | f    |        1 |
    | snake   | m    |        1 |
    +---------+------+----------+

При использовании функции `COUNT()` вовсе не обязательно загружать всю таблицу. Например, предыдущий запрос, в котором учитываются только кошки и собаки, выглядит следующим образом:

    mysql> SELECT species, sex, COUNT(*) FROM pet
        -> WHERE species = "dog" OR species = "cat"
        -> GROUP BY species, sex;
    +---------+------+----------+
    | species | sex  | COUNT(*) |
    +---------+------+----------+
    | cat     | f    |        1 |
    | cat     | m    |        1 |
    | dog     | f    |        1 |
    | dog     | m    |        2 |
    +---------+------+----------+

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

    mysql> SELECT species, sex, COUNT(*) FROM pet
        -> WHERE sex IS NOT NULL
        -> GROUP BY species, sex;
    +---------+------+----------+
    | species | sex  | COUNT(*) |
    +---------+------+----------+
    | bird    | f    |        1 |
    | cat     | f    |        1 |
    | cat     | m    |        1 |
    | dog     | f    |        1 |
    | dog     | m    |        2 |
    | hamster | f    |        1 |
    | snake   | m    |        1 |
    +---------+------+----------+

#### 3.3.4.9 Использование нескольких таблиц

В таблице `pet` хранятся только основные данные о животных. Если же нужно держать в базе какую-либо дополнительную информацию о них (скажем, записи о событиях наподобие посещения ветеринара или рождения потомства), понадобится еще одна таблица. Давайте определимся с ее структурой. Эта таблица должна содержать:

- имена животных, чтобы не путаться с тем, к какому животному относится какое событие
- дата события
- поле для описания события
- поле, отражающее тип события, для того, чтобы можно было распределить их по категориям

С учетом всех приведенных выше требований можно составить примерно такую команду `CREATE TABLE`:

    mysql> CREATE TABLE event (name VARCHAR(20), date DATE,
        -> type VARCHAR(15), remark VARCHAR(255));

Как и в случае с таблицей `pet`, начальные данные в таблицу проще всего загрузить, создав текстовый файл с информацией, разделенной символами табуляции:

name | date | type | remark
Fluffy | 1995-05-15 | litter | 4 kittens, 3 female, 1 male
Buffy | 1993-06-23 | litter | 5 puppies, 2 female, 3 male
Buffy | 1994-06-19 | litter | 3 puppies, 3 female
Chirpy | 1999-03-21 | vet | needed beak straightened
Slim | 1997-08-03 | vet | broken rib
Bowser | 1991-10-12 | kennel |
Fang | 1991-10-12 | kennel |
Fang | 1998-08-28 | birthday | Gave him a new chew toy
Claws | 1998-03-17 | birthday | Gave him a new flea collar
Whistler | 1998-12-09 | birthday | First birthday

Загрузите записи с помощью следующей команды:

    mysql> LOAD DATA LOCAL INFILE "event.txt" INTO TABLE event;

Используя знания, усвоенные при работе с таблицей `pet`, вы сможете загружать данные из таблицы `event`; принципы здесь те же. Но что если самой по себе таблицы `event` перестанет хватать для получения нужных вам ответов?

Предположим, нужно узнать, в каком возрасте животные давали приплод. В таблице `event` указаны даты родов, но для того, чтобы рассчитать возраст матери, нужно знать и дату ее рождения. Так как даты рождения хранятся в таблице `pet`, в этом запросе нужно использовать обе таблицы:

    mysql> SELECT pet.name,
        -> (TO_DAYS(date) - TO_DAYS(birth))/365 AS age,
        -> remark
        -> FROM pet, event
        -> WHERE pet.name = event.name AND type = "litter";
    +--------+------+-----------------------------+
    | name   | age  | remark                      |
    +--------+------+-----------------------------+
    | Fluffy | 2.27 | 4 kittens, 3 female, 1 male |
    | Buffy  | 4.12 | 5 puppies, 2 female, 3 male |
    | Buffy  | 5.10 | 3 puppies, 3 female         |
    +--------+------+-----------------------------+

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

- В выражении `FROM` указаны две таблицы, так как запрос будет получать информацию из обеих.
- При комбинировании (объединении) информации из нескольких таблиц необходимо указать, как строки одной таблицы связываются с записями другой. Это просто, так как в обеих таблицах есть столбец с именами. В этом запросе выражение `WHERE` используется для сопоставления записей из двух таблиц по значениям имен.
- Так как столбец name присутствует в обеих таблицах, нужно явно указать, какую именно таблицу вы имеете в виду, ссылаясь на данный столбец. Это можно сделать, связав имя таблицы с именем столбца.

Для объединения не обязательно иметь две отдельные таблицы; иногда можно объединить таблицу с самой собой - если нужно сравнить одни записи таблицы с другими записями той же таблицы. Например, для того, чтобы обнаружить среди животных ``семейные пары'', можно объединить таблицу `pet` с ней самой, составив пары животных разного пола, но одного вида:

    mysql> SELECT p1.name, p1.sex, p2.name, p2.sex, p1.species
        -> FROM pet AS p1, pet AS p2
        -> WHERE p1.species = p2.species AND p1.sex = "f" AND p2.sex = "m";
    +--------+------+--------+------+---------+
    | name   | sex  | name   | sex  | species |
    +--------+------+--------+------+---------+
    | Fluffy | f    | Claws  | m    | cat     |
    | Buffy  | f    | Fang   | m    | dog     |
    | Buffy  | f    | Bowser | m    | dog     |
    +--------+------+--------+------+---------+

В этом запросе мы указываем псевдонимы имен таблицы, для обращения к столбцам и определения, к какой из таблиц относится каждая ссылка на столбец.

## 3.4 Получение информации о базах данных и таблицах

Как быть, если вы забыли имя базы или таблицы, или структуру какой-либо из таблиц (например имена столбцов)? В MySQL эта проблема решается при помощи нескольких команд, выводящих информацию о базе данных и содержащихся в ней таблицах.

Вы уже познакомились с командой `SHOW DATABASES`, выводящей список управляемых сервером баз данных. Определить, какая из них выбрана в данный момент, можно с помощью функции `DATABASE()`:

    mysql> SELECT DATABASE();
    +------------+
    | DATABASE() |
    +------------+
    | menagerie  |
    +------------+

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

Выяснить, какие таблицы содержит текущая база данных (что необходимо, если, например, никак не получается вспомнить имя нужной таблицы), можно при помощи следующей команды:

    mysql> SHOW TABLES;
    +---------------------+
    | Tables in menagerie |
    +---------------------+
    | event               |
    | pet                 |
    +---------------------+

Узнать структуру таблицы можно при помощи команды `DESCRIBE`, которая выводит информацию о каждом из столбцов таблицы:

    mysql> DESCRIBE pet;
    +---------+-------------+------+-----+---------+-------+
    | Field   | Type        | Null | Key | Default | Extra |
    +---------+-------------+------+-----+---------+-------+
    | name    | varchar(20) | YES  |     | NULL    |       |
    | owner   | varchar(20) | YES  |     | NULL    |       |
    | species | varchar(20) | YES  |     | NULL    |       |
    | sex     | char(1)     | YES  |     | NULL    |       |
    | birth   | date        | YES  |     | NULL    |       |
    | death   | date        | YES  |     | NULL    |       |
    +---------+-------------+------+-----+---------+-------+

`Field` - имя столбца, `Type` - тип данных, к которому относится этот столбец, `NULL` указывает, может ли данный столбец содержать значения `NULL`, Key - является ли этот столбец индексным, и, наконец, `Default` указывает значение данного столбца по умолчанию.

Если для таблицы созданы индексы, информацию о них можно получить с помощью команды `SHOW INDEX FROM tbl_name`.

## 3.5 Примеры стандартных запросов

Здесь представлены примеры решения некоторых стандартных задач средствами MySQL.

В некоторых из примеров используется таблица `shop` (магазин), в которой содержатся цены по каждому изделию (`item number`)для определенных продавцов (`dealer`). Предположим, что каждый продавец имеет одну фиксированную цену для каждого изделия; тогда пара изделие-продавец (`article, dealer`) является первичным ключом для записей таблицы.

Запустите клиента `mysql`:

    mysql имя-вашей-базы-данных

(Для большинства инсталляций MySQL можно использовать базу данных '`test`'.)

Таблицу примера можно создать таким образом:

    CREATE TABLE shop (
     article INT(4) UNSIGNED ZEROFILL DEFAULT '0000' NOT NULL,
     dealer  CHAR(20)                 DEFAULT ''     NOT NULL,
     price   DOUBLE(16,2)             DEFAULT '0.00' NOT NULL,
     PRIMARY KEY(article, dealer));

    INSERT INTO shop VALUES
    (1,'A',3.45),(1,'B',3.99),(2,'A',10.99),(3,'B',1.45),(3,'C',1.69),
    (3,'D',1.25),(4,'D',19.95);

Ну и, скажем, данные для примера будут такими:

    mysql> SELECT * FROM shop;

    +---------+--------+-------+
    | article | dealer | price |
    +---------+--------+-------+
    |    0001 | A      |  3.45 |
    |    0001 | B      |  3.99 |
    |    0002 | A      | 10.99 |
    |    0003 | B      |  1.45 |
    |    0003 | C      |  1.69 |
    |    0003 | D      |  1.25 |
    |    0004 | D      | 19.95 |
    +---------+--------+-------+

### 3.5.1 Максимальное значение столбца

"Как определить наибольшее значение в столбце?"

    SELECT MAX(article) AS article FROM shop

    +---------+
    | article |
    +---------+
    |       4 |
    +---------+

### 3.5.2 Строка, содержащая максимальное значение некоторого столбца

"Как определить номер, дилера и цену самого дорогого изделия?"

В ANSI SQL (и MySQL 4.1) это легко делается при помощи вложенного запроса:

    SELECT article, dealer, price
    FROM   shop
    WHERE  price=(SELECT MAX(price) FROM shop)

В версиях MySQL до 4.1 такая задача выполняется в два этапа:

1. Следует получить максимальное значение цены из таблицы при помощи оператора SELECT.
2. Используя это значение, необходимо составить следующий запрос:

       SELECT article, dealer, price
       FROM   shop
       WHERE  price=19.95

Существует еще одно решение: отсортировать все строки по убыванию цен и после этого получить первую строку, используя специальный оператор `LIMIT`:

    SELECT article, dealer, price
    FROM   shop
    ORDER BY price DESC
    LIMIT 1

Примечание: если существует несколько самых дорогих изделий (например, каждое из них стоит 19,95), запрос, использующий `LIMIT`, возвращает лишь одно из них!

### 3.5.3 Максимальное значение столбца для группы

"Как определить наибольшую цену по каждому изделию?"

    SELECT article, MAX(price) AS price
    FROM   shop
    GROUP BY article

    +---------+-------+
    | article | price |
    +---------+-------+
    |    0001 |  3.99 |
    |    0002 | 10.99 |
    |    0003 |  1.69 |
    |    0004 | 19.95 |
    +---------+-------+

### 3.5.4 Строка, содержащая максимальное значение некоторого столбца

"Для каждого изделия, как определить дилер(ов) с самыми высокими ценами?"

В ANSI SQL (и MySQL 4.1) это легко делается при помощи вложенного запроса:

    SELECT article, dealer, price
    FROM   shop s1
    WHERE  price=(SELECT MAX(s2.price)
                  FROM shop s2
                  WHERE s1.article = s2.article);

В MySQL до 4.1 такая задача выполняется в два этапа:

1. Следует получить список (изделие, максимальная цена)
2. Для каждого изделия, получить соответствующие записи, в которых цена соответствует максимальной.

Это легко делается с помощью временной таблицы:

    CREATE TEMPORARY TABLE tmp (
            article INT(4) UNSIGNED ZEROFILL DEFAULT '0000' NOT NULL,
            price   DOUBLE(16,2)             DEFAULT '0.00' NOT NULL);

    LOCK TABLES shop read;

    INSERT INTO tmp SELECT article, MAX(price) FROM shop GROUP BY article;

    SELECT shop.article, dealer, shop.price FROM shop, tmp
    WHERE shop.article=tmp.article AND shop.price=tmp.price;

    UNLOCK TABLES;

    DROP TABLE tmp;

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

"А можно ли это сделать одним запросом?"

Да, но только используя совершенно неэффективный трюк, который я называю "Трюк MAX-CONCAT":

    SELECT article,
           SUBSTRING( MAX( CONCAT(LPAD(price,6,'0'),dealer) ), 7) AS dealer,
      0.00+LEFT(      MAX( CONCAT(LPAD(price,6,'0'),dealer) ), 6) AS price
    FROM   shop
    GROUP BY article;

    +---------+--------+-------+
    | article | dealer | price |
    +---------+--------+-------+
    |    0001 | B      |  3.99 |
    |    0002 | A      | 10.99 |
    |    0003 | C      |  1.69 |
    |    0004 | D      | 19.95 |
    +---------+--------+-------+

Разумеется, последний пример можно сделать чуть эффективнее, если разбиение катенизированной строки делать на стороне клиента.

### 3.5.5 Использование пользовательских переменных

В MySQL для хранения результатов, чтобы не держать их во временных переменных на клиенте, можно применять пользовательские переменные (see section 6.1.4 Переменные пользователя).

Например, для того чтобы найти изделия с максимальной или минимальной ценой, можно выполнить следующие действия:

    mysql> SELECT @min_price:=MIN(price),@max_price:=MAX(price) FROM shop;
    mysql> SELECT * FROM shop WHERE price=@min_price OR price=@max_price;
    +---------+--------+-------+
    | article | dealer | price |
    +---------+--------+-------+
    |    0003 | D      |  1.25 |
    |    0004 | D      | 19.95 |
    +---------+--------+-------+

### 3.5.6 Использование внешних ключей

В MySQL 3.23.44 и выше в таблицах `InnoDB` осуществляется проверка ограничений целостности внешних ключей (обратитесь к разделам section 7.5 Таблицы `InnoDB` и section 1.9.4.5 Внешние ключи).

Фактически для соединения двух таблиц внешние ключи не нужны.

Единственное, что MySQL в настоящее время не осуществляет (в типах таблиц, отличных от `InnoDB`), это проверку (`CHECK`) что ключи, которые вы используете, действительно существуют в таблице(ах) на которые вы ссылаетесь, и не удаляет автоматически записи из таблиц с определением внешних ключей. Если же ключи используются обычным образом, все будет работать просто чудесно:

    CREATE TABLE person (
        id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
        name CHAR(60) NOT NULL,
        PRIMARY KEY (id)
    );

    CREATE TABLE shirt (
        id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
        style ENUM('t-shirt', 'polo', 'dress') NOT NULL,
        color ENUM('red', 'blue', 'orange', 'white', 'black') NOT NULL,
        owner SMALLINT UNSIGNED NOT NULL REFERENCES person(id),
        PRIMARY KEY (id)
    );

    INSERT INTO person VALUES (NULL, 'Antonio Paz');

    INSERT INTO shirt VALUES
    (NULL, 'polo', 'blue', LAST_INSERT_ID()),
    (NULL, 'dress', 'white', LAST_INSERT_ID()),
    (NULL, 't-shirt', 'blue', LAST_INSERT_ID());

    INSERT INTO person VALUES (NULL, 'Lilliana Angelovska');

    INSERT INTO shirt VALUES
    (NULL, 'dress', 'orange', LAST_INSERT_ID()),
    (NULL, 'polo', 'red', LAST_INSERT_ID()),
    (NULL, 'dress', 'blue', LAST_INSERT_ID()),
    (NULL, 't-shirt', 'white', LAST_INSERT_ID());

    SELECT * FROM person;
    +----+---------------------+
    | id | name                |
    +----+---------------------+
    |  1 | Antonio Paz         |
    |  2 | Lilliana Angelovska |
    +----+---------------------+

    SELECT * FROM shirt;
    +----+---------+--------+-------+
    | id | style   | color  | owner |
    +----+---------+--------+-------+
    |  1 | polo    | blue   |     1 |
    |  2 | dress   | white  |     1 |
    |  3 | t-shirt | blue   |     1 |
    |  4 | dress   | orange |     2 |
    |  5 | polo    | red    |     2 |
    |  6 | dress   | blue   |     2 |
    |  7 | t-shirt | white  |     2 |
    +----+---------+--------+-------+

    SELECT s.* FROM person p, shirt s
     WHERE p.name LIKE 'Lilliana%'
       AND s.owner = p.id
       AND s.color <> 'white';

    +----+-------+--------+-------+
    | id | style | color  | owner |
    +----+-------+--------+-------+
    |  4 | dress | orange |     2 |
    |  5 | polo  | red    |     2 |
    |  6 | dress | blue   |     2 |
    +----+-------+--------+-------+

### 3.5.7 Поиск по двум ключам

MySQL пока не осуществляет оптимизации, если поиск производится по двум различным ключам, которые связаны при помощи оператора `OR` (поиск по одному ключу с различными частями `OR` оптимизируется хорошо):

    SELECT field1_index, field2_index FROM test_table WHERE field1_index = '1'
    OR  field2_index = '1'

Причина заключается в том, что у нас не было времени, чтобы придумать эффективный способ обработки этого случая (сравните: обработка оператора `AND` теперь работает хорошо)

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

    CREATE TEMPORARY TABLE tmp
    SELECT field1_index, field2_index FROM test_table WHERE field1_index = '1';
    INSERT INTO tmp
    SELECT field1_index, field2_index FROM test_table WHERE field2_index = '1';
    SELECT * from tmp;
    DROP TABLE tmp;

Вышеупомянутый способ выполнения этого запроса - это фактически `UNION` (объединение) двух запросов. See section 6.4.1.2 Синтаксис оператора `UNION`.

### 3.5.8 Подсчет посещений за день

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

    CREATE TABLE t1 (year YEAR(4), month INT(2) UNSIGNED ZEROFILL,
                 day INT(2) UNSIGNED ZEROFILL);
    INSERT INTO t1 VALUES(2000,1,1),(2000,1,20),(2000,1,30),(2000,2,2),
                (2000,2,23),(2000,2,23);
    SELECT year,month,BIT_COUNT(BIT_OR(1<<day)) AS days FROM t1
           GROUP BY year,month;

    Which returns:

    +------+-------+------+
    | year | month | days |
    +------+-------+------+
    | 2000 |    01 |    3 |
    | 2000 |    02 |    2 |
    +------+-------+------+

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

### 3.5.9 Использование атрибута AUTO_INCREMENT

Атрибут `AUTO_INCREMENT` может использоваться для генерации уникального идентификатора для новых строк:

    CREATE TABLE animals (
                 id MEDIUMINT NOT NULL AUTO_INCREMENT,
                 name CHAR(30) NOT NULL,
                 PRIMARY KEY (id)
                 );
    INSERT INTO animals (name) VALUES ("dog"),("cat"),("penguin"),
                                      ("lax"),("whale");
    SELECT * FROM animals;

Что вернет:

    +----+---------+
    | id | name    |
    +----+---------+
    |  1 | dog     |
    |  2 | cat     |
    |  3 | penguin |
    |  4 | lax     |
    |  5 | whale   |
    +----+---------+

Вы можете получить `AUTO_INCREMENT` ключ с помощью функции SQL `LAST_INSERT_ID()` или с помощью функции `mysql_insert_id()` интерфейса C.

Для многострочной вставки, `LAST_INSERT_ID()`/`mysql_insert_id()` на самом деле вернут `AUTO_INCREMENT` значение для первой вставленной записи. Это сделано для того, чтобы многострочные вставки можно было повторить на других серверах.

В таблицах `MyISAM` и `BDB` можно определить `AUTO_INCREMENT` для вторичного столбца составного ключа. В этом случае значение, генерируемое для автоинкрементного столбца, вычисляется как `MAX(auto_increment_column)+1) WHERE prefix=given-prefix`. Столбец с атрибутом `AUTO_INCREMENT` удобно использовать, когда данные нужно помещать в упорядоченные группы.

    CREATE TABLE animals (
                 grp ENUM('fish','mammal','bird') NOT NULL,
                 id MEDIUMINT NOT NULL AUTO_INCREMENT,
                 name CHAR(30) NOT NULL,
                 PRIMARY KEY (grp,id)
                 );
    INSERT INTO animals (grp,name) VALUES("mammal","dog"),("mammal","cat"),
                      ("bird","penguin"),("fish","lax"),("mammal","whale");
    SELECT * FROM animals ORDER BY grp,id;

Что вернет:

    +--------+----+---------+
    | grp    | id | name    |
    +--------+----+---------+
    | fish   |  1 | lax     |
    | mammal |  1 | dog     |
    | mammal |  2 | cat     |
    | mammal |  3 | whale   |
    | bird   |  1 | penguin |
    +--------+----+---------+

Обратите внимание, что в этом случае значение `AUTO_INCREMENT` будет использоваться повторно, если в какой-либо группе удаляется строка, содержащая наибольшее значение `AUTO_INCREMENT`.

## 3.6 Использование `mysql` в пакетном режиме

В предыдущих разделах было показано, как использовать `mysql` в интерактивном режиме, вводя запросы и тут же просматривая результаты. Запускать `mysql` можно и в пакетном режиме. Для этого нужно собрать все команды в один файл и передать его на исполнение `mysql`:

    shell> mysql < batch-file

Если вы работаете с `mysql` в ОС Windows, и некоторые из специальных символов, содержащихся в пакетном файле, могут вызвать проблемы, воспользуйтесь следующей командой:

    dos> mysql -e "source batch-file"

Если необходимо указать параметры соединения в командной строке, она может иметь следующий вид:

    shell> mysql -h host -u user -p < batch-file
    Enter password: ********

Работая с `mysql` таким образом, вы, в сущности, создаете сценарий и затем исполняете его.

Если нужно продолжать обработку сценария даже при обнаружении в нем ошибок, воспользуйтесь параметром командной строки `--force`.

Зачем вообще нужны сценарии? Причин тому несколько:

- При необходимости частого (ежедневного или хотя бы еженедельного) запуска одного и того же запроса сценарий позволяет избавиться от многократного набора этого запроса.
- Можно создавать новые запросы, подобные уже существующим, просто копируя и затем изменяя файлы сценариев.
- Пакетный режим может пригодиться и при разработке особенно длинных запросов, а именно - многострочных команд или больших последовательностей команд. В таком деле одна допущенная ошибка может привести к необходимости повторного набора большого количества текста. Зато при работе со сценарием легко исправить ошибку и запустить запрос на повторное исполнение.
- Если ваш запрос выводит на экран много текста, его можно просмотреть постранично, не мучаясь догадками относительно убежавшей за край экрана части результатов:

      shell> mysql < batch-file | more

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

      shell> mysql < batch-file > mysql.out

- Свой сценарий вы можете дать кому-нибудь еще, чтобы он тоже мог воспользоваться содержащимися в сценарии командами.
- В некоторых случаях работать в интерактивном режиме просто не получается. И здесь без пакетного режима не обойтись.

По умолчанию при работе с `mysql` в пакетном режиме используется более сжатый формат вывода результатов, чем при интерактивной работе. В интерактивном режиме результаты работы запроса `SELECT DISTINCT species FROM pet` выглядят так:

    +---------+
    | species |
    +---------+
    | bird    |
    | cat     |
    | dog     |
    | hamster |
    | snake   |
    +---------+

А в пакетном - вот так:

    species
    bird
    cat
    dog
    hamster
    snake

Если вам нужно, чтобы в пакетном режиме программа выводила данные так же, как и в интерактивном, воспользуйтесь ключом `mysql -t`. Включить "эхо" исполняемых команд можно с помощью ключа `mysql -vvv`.

В командную строку `mysql` можно включать и сценарии - при помощи команды `source`:

    mysql> source filename;

## 3.7 Запросы проекта "Близнецы" (Twin Project)

В Analytikerna и Lentus мы проводили сбор и систематизацию данных в рамках крупного исследовательского проекта. Этот проект разрабатывается совместно Институтом экологической медицины Karolinska Institutet, Стокгольм и отделением клинических исследований в области старения и психологии университета Южной Калифорнии.

В проекте предусмотрен этап опросов, на котором происходит опрос по телефону всех проживающих в Швеции близнецов старше 65 лет. Близнецы, отвечающие критериям отбора, переходят на следующий этап. На этом этапе желающих участвовать в проекте близнецов посещает врач с медсестрой. Проводятся физические и нейропсихологические исследования, лабораторные анализы, неврологическое обследование, оценка психологического состояния, а также собираются данные по истории семьи. Кроме того, осуществляется сбор информации о медицинских и экологических факторах риска.

Дополнительную информацию о проекте вы можете получить по адресу: http://www.imm.ki.se/TWIN/TWINUKW.HTM

На поздних этапах администрирование проекта осуществляется с помощью web-интерфейса, написанного на `Perl` и `MySQL`.

Каждую ночь собранные во время интервью данные заносятся в базу данных MySQL.

### 3.7.1 Поиск нераспределенных близнецов

Этот запрос определяет, которые из близнецов переходят во второй этап проекта:

    SELECT
            CONCAT(p1.id, p1.tvab) + 0 AS tvid,
            CONCAT(p1.christian_name, " ", p1.surname) AS Name,
            p1.postal_code AS Code,
            p1.city AS City,
            pg.abrev AS Area,
            IF(td.participation = "Aborted", "A", " ") AS A,
            p1.dead AS dead1,
            l.event AS event1,
            td.suspect AS tsuspect1,
            id.suspect AS isuspect1,
            td.severe AS tsevere1,
            id.severe AS isevere1,
            p2.dead AS dead2,
            l2.event AS event2,
            h2.nurse AS nurse2,
            h2.doctor AS doctor2,
            td2.suspect AS tsuspect2,
            id2.suspect AS isuspect2,
            td2.severe AS tsevere2,
            id2.severe AS isevere2,
            l.finish_date
    FROM
            twin_project AS tp
            /* For Twin 1 */
            LEFT JOIN twin_data AS td ON tp.id = td.id
                      AND tp.tvab = td.tvab
            LEFT JOIN informant_data AS id ON tp.id = id.id
                      AND tp.tvab = id.tvab
            LEFT JOIN harmony AS h ON tp.id = h.id
                      AND tp.tvab = h.tvab
            LEFT JOIN lentus AS l ON tp.id = l.id
                      AND tp.tvab = l.tvab
            /* For Twin 2 */
            LEFT JOIN twin_data AS td2 ON p2.id = td2.id
                      AND p2.tvab = td2.tvab
            LEFT JOIN informant_data AS id2 ON p2.id = id2.id
                      AND p2.tvab = id2.tvab
            LEFT JOIN harmony AS h2 ON p2.id = h2.id
                      AND p2.tvab = h2.tvab
            LEFT JOIN lentus AS l2 ON p2.id = l2.id
                      AND p2.tvab = l2.tvab,
            person_data AS p1,
            person_data AS p2,
            postal_groups AS pg
    WHERE
            /* p1 gets main twin and p2 gets his/her twin. */
            /* ptvab is a field inverted from tvab */
            p1.id = tp.id AND p1.tvab = tp.tvab AND
            p2.id = p1.id AND p2.ptvab = p1.tvab AND
            /* Just the sceening survey */
            tp.survey_no = 5 AND
            /* Skip if partner died before 65 but allow emigration (dead=9) */
            (p2.dead = 0 OR p2.dead = 9 OR
             (p2.dead = 1 AND
              (p2.death_date = 0 OR
               (((TO_DAYS(p2.death_date) - TO_DAYS(p2.birthday)) / 365)
                >= 65))))
            AND
            (
            /* Twin is suspect */
            (td.future_contact = 'Yes' AND td.suspect = 2) OR
            /* Twin is suspect - Informant is Blessed */
            (td.future_contact = 'Yes' AND td.suspect = 1
                                       AND id.suspect = 1) OR
            /* No twin - Informant is Blessed */
            (ISNULL(td.suspect) AND id.suspect = 1
                                AND id.future_contact = 'Yes') OR
            /* Twin broken off - Informant is Blessed */
            (td.participation = 'Aborted'
             AND id.suspect = 1 AND id.future_contact = 'Yes') OR
            /* Twin broken off - No inform - Have partner */
            (td.participation = 'Aborted' AND ISNULL(id.suspect)
                                          AND p2.dead = 0))
            AND
            l.event = 'Finished'
            /* Get at area code */
            AND SUBSTRING(p1.postal_code, 1, 2) = pg.code
            /* Not already distributed */
            AND (h.nurse IS NULL OR h.nurse=00 OR h.doctor=00)
            /* Has not refused or been aborted */
            AND NOT (h.status = 'Refused' OR h.status = 'Aborted'
            OR h.status = 'Died' OR h.status = 'Other')
    ORDER BY
            tvid;

Дадим к этому запросу некоторые пояснения:

`CONCAT(p1.id, p1.tvab) + 0 AS tvid`
  Сортируем по взаимосвязи `id` и `tvab` в числовом порядке. Прибавление нуля к результату заставляет MySQL обращаться с результатом как с числом.
column `id`
  Определяет пару близнецов. Является ключевым полем всех таблиц.
column `tvab`
  Определяет близнеца в паре. Принимает значение 1 или 2.
column `ptvab`
  Отрицание `tvab`. Если значение `tvab` равно 1, значение этого поля - 2, и наоборот. Данное поле облегчает MySQL задачу оптимизации запроса и экономит время ввода данных.

Этот запрос иллюстрирует, помимо всего прочего, сравнение значений одной таблицы с помощью команды `JOIN (p1 и p2)`. В этом примере таким образом проверяется, не умер ли один из близнецов до достижения им 65-летнего возраста. Если так и произошло, строка не попадает в список возвращаемых.

Все вышеприведенные поля имеются во всех таблицах, в которых хранится относящаяся к близнецам информация. Ключевыми полями для ускорения работы запросов назначены `id,tvab` (во всех таблицах), а также `id,ptvab (person_data)`.

На нашем рабочем компьютере (200МГц UltraSPARC) этот запрос возвращает 150-200 строк, и его выполнение занимает менее секунды.

Текущее количество строк в таблицах, использовавшихся выше:

Таблица | Строки
`person_data` | 71074
`lentus` | 5291
`twin_project` | 5286
`twin_data` | 2012
`informant_data` | 663
`harmony` | 381
`postal_groups` | 100

### 3.7.2 Вывод таблицы состояний пар близнецов

Каждый опрос оканчивается кодом состояния, называющимся `event` (``событие''). Приведенный здесь запрос выводит данные обо всех парах близнецов, объединенные по полю `event`. Таблица наглядно показывает, с каким количеством пар близнецов работа полностью завершена, а в каком количестве пар работа с одним близнецом завершена, а с другим - нет и т.п.

    SELECT
            t1.event,
            t2.event,
            COUNT(*)
    FROM
            lentus AS t1,
            lentus AS t2,
            twin_project AS tp
    WHERE
            /* We are looking at one pair at a time */
            t1.id = tp.id
            AND t1.tvab=tp.tvab
            AND t1.id = t2.id
            /* Just the sceening survey */
            AND tp.survey_no = 5
            /* This makes each pair only appear once */
            AND t1.tvab='1' AND t2.tvab='2'
    GROUP BY
            t1.event, t2.event;

## 3.8 Использование MySQL совместно с Apache

Существуют программы, позволяющие проводить идентификацию пользователей с помощью базы данных MySQL, а также записывать журналы в таблицу MySQL.

Формат записи журналов Apache можно привести в легко понятную MySQL форму, введя в файл настроек Apache следующие строки:

    LogFormat \
            "\"%h\",%{%Y%m%d%H%M%S}t,%>s,\"%b\",\"%{Content-Type}o\",  \
            \"%U\",\"%{Referer}i\",\"%{User-Agent}i\""

В MySQL же можно сделать примерно следующее:

    LOAD DATA INFILE '/local/access_log' INTO TABLE table_name
    FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' ESCAPED BY '\\'

Go to the first, previous, next, last section, table of contents.