Поиск

Синтаксис подзапросов

Подзапрос - это оператор SELECT внутри другого оператора.
Начиная с MySQL 4.1, поддерживаются все формы подзапросов, которых требует стандарт SQL, равно как и некоторые средства, специфичные для MySQL.
В ранних версиях MySQL приходилось искать избегать подзапросов вообще либо ис­кать обходные пути, но разработчики, которые начинают писать новый код сейчас, об­наружат, что подзапросы - очень удобная часть инструментального набора MySQL.
В MySQL до 4.1 большинство подзапросов могут быть успешно реализовано в виде соединений или другими способами. См. раздел Замена подзапросов соединениями для ранних версий MySQL.
Вот пример подзапроса:
SELECT * FROM tl WHERE columnl = (SELECT columnl FROM t2);
В этом примере SELECT * FROM tl является внешним запросом (или внешним опера­тором),a (SELECT columnl FROM t2) - подзапросом. Говорят, что подзапрос вложен во внешний запрос. Фактически, можно вкладывать подзапросы в подзапросы на большую глубину. Подзапрос всегда должен появляться в скобках.
Основные преимущества подзапросов:

  • Они позволяют писать структурированные запросы таким образом, что можно изолировать части оператора.

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

Ниже приведен пример оператора, который демонстрирует основные понятия о синтаксисе подзапросов, поддерживаемых MySQL, как того требует стандарт SQL:
DELETE FROM tl WHERE sll > ANY
(SELECT COUNT(*) /* без подсказок */ FROM t2 WHERE NOT EXISTS
(SELECT * FROM t3 WHERE ROW(5*t2.sl,77) =
(SELECT 50,11*51 FROM t4 UNION SELECT 50,77 FROM (SELECT * FROM t5) AS t5)));

Подзапрос, как скалярный операнд
В своей простейшей форме {скалярный подзапрос представляет собой противопо­ложность строчным или табличным подзапросам, описанным ниже), подзапрос - это простой операнд. То есть вы можете использовать его в любом месте, где допустимо значение столбца или литерал, и вы можете ожидать, что он имеет те же характеристики, которые имеют все операнды: тип данных, длину, признак того, может ли он принимать значение NULL, и так далее. Например:
CREATE TABLE tl (si INT, s2 CHAR(5) NOT NULL);
SELECT (SELECT s2 FROM tl);
Подзапрос в этом запросе имеет тип данных CHAR, длину 5, набор символов и порядок сопоставления по умолчанию такие, как были при выполнении CREATE TABLE, и признак того, что значение столбца допускает значение NULL. Фактически почти все подзапросы могут быть NULL, поскольку если таблица пуста, как в этом примере, значение подзапро­са будет равно NULL. Существует несколько ограничений.

  1. Внешний оператор подзапроса может быть одним из следующих: SELECT, INSERT, UPDATE, DELETE, SET ИЛИ DO.
  2. Подзапрос может содержать любые ключевые слова и конструкции, которые допустимы В обычном операторе SELECT: DISTINCT, GROUP BY, ORDER BY, LIMIT, Kонструкции JOIN, UNION, подсказки, комментарии, функции и так далее.

Поэтому, когда вы видите примеры, приведенные ниже, которые включают довольно краткие конструкции подзапросов (SELECT columnl FROM tl), то представляйте себе свой собственный код, который будет содержать куда более разнообразные и сложные конструкции.
Например, представим, что созданы две таблицы:
CREATE TABLE tl (si INT); INSERT INTO tl VALUES (1); CREATE TABLE t2 (si INT); INSERT INTO t2 VALUES (2);
Затем выполняется такой запрос:

SELECT (SELECT si FROM t2) FROM tl;
Результатом будет 2, потому что существует строка в t2, содержащая столбец si, который имеет значение 2.
Подзапрос может быть частью выражения. Если это операнд функции, не забудьте указать скобки. Например:
SELECT UPPER((SELECT si FROM tl)) FROM t2;

Сравнения с использованием подзапросов
Наиболее часто подзапросы применяются в такой форме: операнд_не_подзапроса операция_сравнения {подзапрос) Здесь операция_сравнения - это одна из следующих операций:
Например:
... 'а1 = (SELECT columnl FROM tl)
Единственное разрешенное место для подзапроса - в правой части выражения срав­нения, и вы можете найти некоторые старые системы управления базами данных, кото­рые настаивают на этом.
Ниже приведен пример общей формы сравнения с подзапросом, которую нельзя применять в соединении. Он найдет все значения таблицы tl, которые равны макси­мальному значению в таблице t2:
SELECT columnl FROM tl
WHERE columnl = (SELECT MAX(column2) FROM t2);
А вот другой пример, также невозможный в виде соединения, поскольку включает агрегатную функцию в одной из таблиц. Он найдет все строки таблицы tl, содержащие значение, которое встречается дважды:
SELECT * FROM tl
WHERE 2 = (SELECT COUNT(columnl) FROM tl);
Подзапросы с ANY, IN и SOME
Синтаксис:
операнд операция_сравнения ANY {подзапрос)
операнд IN {подзапрос)
операнд операция_сравнения SOME {подзапрос)
Ключевое слово ANY, которое должно следовать за операцией сравнения, означает "возвратить TRUE, если сравнение дает TRUE для ЛЮБОЙ из строк, которые возвращает подзапрос". Например:
SELECT si FROM tl WHERE si > ANY (SELECT si FROM t2);
Предположим, что в таблице tl есть строка, которая содержит (10). Выражение ис­тинно, если таблица t2 содержит (21,14,7), поскольку в t2 есть значение 7, которое меньше 10. Выражение ложно, если таблица t2 содержит (20,10), либо таблица t2 пус­та. Выражение равно UNKNOWN, если таблица t2 содержит значения (NULL, NULL, NULL).
Слово IN - это псевдоним для = ANY, поэтому следующие два оператора одинаковы:

SELECT si FROM tl WHERE si = ANY (SELECT si FROM t2) ; SELECT si FROM tl WHERE si IN (SELECT si FROM t2);
Слово SOME — это псевдоним для ANY, поэтому показанные ниже два оператора одинаковы:
SELECT si FROM tl WHERE si <> ANY (SELECT si FROM t2); SELECT si FROM tl WHERE si <> SOME (SELECT si FROM t2);
Слово SOME применяется редко, но предыдущий пример показывает, почему оно мо­жет оказаться удобным. Для слуха большинства людей английская фраза "a is not equal to any b" ("а не равно любому b") означает "there is no b which is equal to а" ("нет таких b, которые равны а"), но это не то, что подразумевает синтаксис SQL. Применение о SOME помогает всем правильно понимать действительный смысл запроса.

Подзапросы с ALL
Синтаксис:
операнд операция_сравнения ALL {подзапрос)
Слово ALL, которое должно следовать за операцией сравнения, означает "возвратить TRUE, если сравнение дает TRUE для всех строк, возвращаемых подзапросом". Например:
SELECT si FROM tl WHERE si > ALL (SELECT si FROM t2);
Предположим, что в таблице tl есть строка, которая содержит (10). Выражение ис­тинно, если таблица t2 содержит (-5, 0, +5), потому что 10 больше, чем все три значе­ния из таблицы t2. Выражение ложно, если таблица t2 содержит (12,б,NULL,-100), по­скольку только одно значение 12 в таблице t2 больше 10. Выражение неопределенно (UNKNOWN), если таблица t2 содержит (0,NULL,1).
Наконец, если таблица t2 пуста, результат равен TRUE. Вы можете подумать, что ре­зультат будет UNKNOWN, но, сожалеем, он будет именно TRUE. Поэтому, как ни странно, следующий оператор вернет TRUE, если таблица t2 пуста:
SELECT * FROM tl WHERE 1 > ALL (SELECT si FROM t2);
Однако следующий оператор вернет UNKNOWN, если таблица t2 пуста:
SELECT * FROM tl WHERE 1 > (SELECT si FROM t2);
И вдобавок следующий оператор также вернет UNKNOWN, если таблица t2 пуста:
SELECT * FROM tl WHERE 1 > ALL (SELECT MAX(si) FROM t2);
В общем случае, таблицы со значениями NULLи пустые таблицы — это крайние случаи. Поэтому при написании кода подзапросов всегда принимайте во внимание две упо­мянутых возможности.
Коррелированные подзапросы
Коррелированный подзапрос - это такой подзапрос, который содержит ссылку на столбец, который есть во внешнем запросе. Например:
SELECT * FROM tl WHERE columnl = ANY
(SELECT columnl FROM t2 WHERE t2.column2 = tl.column2);
Обратите внимание, что подзапрос содержит ссылку на столбец таблицы tl, даже не­смотря на то, что конструкция FROM подзапроса не упоминает таблицу tl. Таким образом, MySQL выполняет просмотр за пределами подзапроса и находит tl во внешнем запросе.
Предположим, что таблица tl содержит строку, в которой columnl = 5 и column2 = б, в то же время таблица t2 содержит строку, в которой columnl = 5 и column2 = 7. Про­стое выражение ... WHERE columnl=ANY (SELECT columnl FROM t2) будет TRUE, НО в ЭТОМ примере конструкция WHERE внутри подзапроса даст FALSE (поскольку 7 не равно 5), а поэтому и весь подзапрос вернет FALSE.
Правило видимости: MySQL вычисляет выражения от внутреннего к внешнему.
Например:
SELECT columnl FROM tl AS x
WHERE x.columnl = (SELECT columnl FROM t2 AS x WHERE x.columnl = (SELECT columnl FROM t3 WHERE x.column2 = t3.columnl));
В этом запросе x.column2 должен быть столбцом таблицы t2, потому что SELECT columnl FROM t2 AS x ... переименовывает t2. Это не столбец таблицы tl, так как SELECT columnl FROM tl ...- другой запрос, который находится во внешнем контексте.
Для подзапросов, находящихся в конструкциях having или ORDER BY, MySQL также ищет имена в списке столбцов внешнего запроса.
В некоторых случаях коррелированный подзапрос оптимизируется. Например:
значение IN (SELECT значение_ключа FROM имя_таблицы WHERE коррелированное_условие)
Иначе они могут оказаться неэффективными и, скорее всего, медленными. Если пе­реписать запрос в виде соединения, это может увеличить производительность.
EXISTS и NOT EXISTS
Если подзапрос вообще возвращает какие-нибудь значения, то EXISTS подзапрос возвращает TRUE, a NOT EXISTS подзапрос - FALSE, например:
SELECT columnl FROM tl WHERE EXISTS (SELECT * FROM t2);
Традиционно подзапрос в EXISTS начинается с SELECT *, но он может начинаться с SELECT 5 или SELECT columnl, либо с еще чего-нибудь. MySQL игнорирует список SELECT в таком подзапросе, потому это не важно.
Для предыдущего примера, если t2 содержит любые строки, даже строки, в которых нет ничего кроме значений NULL, то условие EXISTS истинно. Вообще это неправдопо­добный пример, поскольку почти всегда подзапрос [NOT] EXISTS содержит корреляцию. Ниже представлены более реалистичные примеры:
  • Какие типы магазинов есть в одном или более городов?
    SELECT DISTINCT store_type FROM Stores
    WHERE EXISTS (SELECT * FROM Cities_Stores
    WHERE Cities_Stores.store_type = Stores.store_type);
  • Каких типов магазинов нет ни в одном городе?
    SELECT DISTINCT store_type FROM Stores
    WHERE NOT EXISTS (SELECT * FROM Cities_Stores
    WHERE Cities_Stores.store_type = Stores.store_type);
  • Какой тип магазинов есть во всех городах?

SELECT DISTINCT store_type FROM Stores SI WHERE NOT EXISTS (
SELECT * FROM Cities WHERE NOT EXISTS ( SELECT * FROM Cities_Stores
WHERE Cities_Stores.city = Cities.city
AND Cities_Stores.store_type = Stores.store_type));
В последнем примере представлен дважды вложенный подзапрос NOT EXISTS. To есть конструкция NOT EXISTS содержится внутри другой конструкции NOT EXISTS. Формаль­но он отвечает на вопрос "есть ли город с магазином, которого нет в Stores?". Но проще сказать, что вложенный NOT EXISTS отвечает на вопрос "истинно ли х для всех у?".

Подзапросы, возвращающие строку
До сих пор мы обсуждали подзапросы, возвращающие столбец (скалярные), то есть подзапросы, возвращающие единственное значение столбца. Строчные подзапросы -это вариант подзапросов, которые возвращают более одного значения столбца. Ниже представлены два примера:
SELECT * FROM tl WHERE (1,2) = (SELECT columnl, column2 FROM t2); SELECT * FROM tl WHERE ROW(1,2) = (SELECT columnl, column2 FROM t2);
Оба эти запроса истинны, если в таблице t2 присутствует строка, в которой columnl = 1и column2 = 2.
Выражения (1,2) иROW(1,2) иногда называют конструктором строки. Эти два вы­ражения эквивалентны. Они вполне корректны и в других контекстах. Например, сле­дующие два оператора семантически эквивалентны (несмотря на то, что только второй из них может быть оптимизирован):
SELECT * FROM tl WHERE (columnl, column2) = (1,1);
SELECT * FROM tl WHERE columnl = 1 AND column2 = 1;
Как правило, конструкторы строки используются для сравнения с подзапросами, воз­вращающими два или более столбцов. Например, представленный ниже запрос выпол­няет следующую директиву: "найти все строки таблицы tl, которые есть также и в таб­лице t2":
SELECT columnl,column2,column3 FROM tl WHERE (columnl,column2,column3) IN
(SELECT columnl,column2,column3 FROM tl);
Подзапросы в конструкции FROM
Подзапросы разрешены и в конструкции FROM оператора SELECT. Их синтаксис вы­глядит следующим образом:
SELECT ... FROM {подзапрос) AS имя ...
Конструкция AS имя является обязательной, поскольку каждая таблица в конструк­ции FROM должна иметь имя. Все столбцы в списке подзапроса подзапрос также должны иметь уникальные имена.
Для того чтобы проиллюстрировать это, предположим, что имеется такая таблица:
CREATE TABLE tl (si INT, s2 CHAR(5), s3 FLOAT);

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

INSERT INTO tl VALUES (l,'l',1.0); INSERT INTO tl VALUES (2,'2',2.0); SELECT sbl,sb2,sb3
FROM (SELECT si AS sbl, s2 AS sb2, s3*2 AS sb3 FROM tl) AS sb
WHERE sbl > 1;
Результат: 2, '2', 4.0.
А вот другой пример. Предположим, что вы хотите знать среднее значение сумм в сгруппированной таблице. Следующий вариант работать не будет: SELECT AVG(SUM(columnl)) FROM tl GROUP BY columnl; Однако приведенный ниже запрос выдаст нужную информацию:
SELECT AVG(sum_columnl)
FROM (SELECT SUM(columnl) AS sum_columnl FROM tl GROUP BY columnl) AS tl;
Отметим, что имя столбца, используемое в подзапросе (sum_columnl) распознается во внешнем запросе.
На данный момент подзапросы в конструкции FROM не могут быть коррелированными подзапросами.
Подзапрос в конструкции FROM будет выполнен (то есть будет создана временная таблица) даже для оператора EXPLAIN, потому что запросы верхнего уровня нуждаются в информации обо всех таблицах на стадии оптимизации.

Ошибки подзапросов
Появился ряд новых типов ошибок, которые имеют отношение к подзапросам. В этом разделе они сгруппированы вместе, поскольку их просмотр поможет запомнить некоторые важные моменты.
  • Неподдерживаемый синтаксис подзапроса:
    ERROR 1235 (ER_NOT_SUPPORTED_YET)
    SQLSTATE = 42000
    Message = "This version of MySQL doesn't yet support
    'LIMIT & IN/ALL/ANY/SOME subquery'"
    Это означает, что операторы следующей формы работать не будут, хотя это и слу­чается только в ранних версиях, подобных MySQL 4.1.1:
    SELECT * FROM tl
    WHERE si IN (SELECT s2 FROM t2 ORDER BY si LIMIT 1)
  • Неверное число столбцов в подзапросе:
    ERROR 1241 (ER_OPERAND_COLUMNF)
    SQLSTATE = 21000
    Message = "Operand should contain 1 column(s)"
    Эта ошибка возникает в случаях наподобие следующего:
    SELECT (SELECT columnl, column2 FROM t2) FROM tl;
    Допустимо применять подзапросы, которые возвращают несколько столбцов с целью сравнения. См. раздел 6.1.8.7. Но в других контекстах подзапрос должен быть скалярным операндом.
  • Неверное количество строк в подзапросе:

    ERROR 1242 (ER_SUBSELECT_NO_1_ROW)
    SQLSTATE = 21000
    Message = "Subquery returns more than 1 row"
    Эта ошибка происходит в операторах вроде приведенного ниже, но только если в таблице t2 имеется более одной строки:
    SELECT * FROM tl WHERE columnl = (SELECT columnl FROM t2);
    Такая ошибка может вдруг обнаружиться в коде, который работал в течение не­скольких лет, по той причине, что кто-то внес модификацию, изменившую коли­чество строк, возвращаемых подзапросом. Помните, что если нужно найти любое число строк, а не только одну, то правильная форма запроса будет выглядеть так:
    SELECT * FROM tl WHERE columnl = ANY (SELECT columnl FROM t2) ;
  • Неправильно используется таблица в подзапросе:
    Error 1093 (ER_UPDATE_TABLE_USED)
    SQLSTATE = HY000
    Message = "You can't specify target table 'x'
    for update in FROM clause"
    Такая ошибка происходит в случае запроса, подобного представленному ниже:
    UPDATE tl SET column2 = (SELECT MAX(columnl) FROM tl) ;
    Вполне корректно использовать подзапрос для присвоения в операторе UPDATE, поскольку подзапросы разрешены в операторах UPDATE и DELETE, равно как и в операторе SELECT. Однако вы не можете использовать одну и ту же таблицу - в данном случае tl - и в конструкции FROM подзапроса, и в качестве объекта для манипуляции UPDATE. Обычно сбой подзапроса приводит к сбою всего внешнего оператора.
    Оптимизация подзапросов
    Когда идет разработка, то долгое время никаких подсказок оптимизации запросов не применяется. Но вы можете попробовать некоторые интересные трюки:
  • Использовать конструкции подзапросов, которые касаются количества или порядка строк в подзапросе, например:
    ELECT * FROM tl WHERE tl.columnl IN
    (SELECT columnl FROM t2 ORDER BY columnl); SELECT * FROM tl WHERE tl.columnl IN
    (SELECT DISTINCT columnl FROM t2); SELECT * FROM tl WHERE EXISTS
    (SELECT * FROM t2 LIMIT 1);
  • Заменить соединение подзапросом. Например, используйте такой запрос:
    SELECT DISTINCT columnl FROM tl WHERE tl.columnl IN ( SELECT columnl FROM t2);
    вместо такого:
    SELECT DISTINCT tl.columnl FROM tl, t2 WHERE tl.columnl = t2.columnl;

  • Переместить конструкции из внешнего запроса в подзапрос. Например, используйте такой запрос:
    SELECT * FROM tl
    WHERE Si IK [SELECT si FROM tl UNION ALL SELECT si FROM t2);
    вместо такого:
    SELECT * FROM tl
    WHERE si IN [SELECT si FROM tl) OR si IN (SELECT si FROM t2);
    И другой пример; используйте следующий запрос:
    SELECT [SELECT columnl + 5 FROM tl) FROM t2;
    вместо такого:
    SELECT [SELECT columnl FROM tl) + 5 FROM t2;
  • Применять строковый подзапрос вместо коррелированного, например, такой:
    SELECT * FROM tl
    WHERE (columnl,column2) IN (SELECT columnl,column2 FROM t2);
    вместо такого:
    SELECT * FROM tl
    WHERE EXISTS (SELECT * FROM t2 WHERE t2.columnl=tl. columnl AND t2.column2=tl.column2);
  • Применять NOT (a = ANY (...)) вместо а о ALL (...).
  • Применятьx = ANY{таблица_содержащая(1,2))
вместо х=1 OR x=2.
Использовать
= ANY вместо EXISTS.
Эти трюки могут заставить программы работать быстрее или медленнее. Используя средства MySQL, подобные функции BENCHMARK (), вы можете получить представление о том, что может помочь в вашей конкретной ситуации. Не беспокойтесь слишком о трансформации подзапросов в соединения, если только вам не нужно обеспечить со­вместимость с версиями MySQL до 4.1, которые подзапросы не поддерживали.
Ниже представлены некоторые меры по оптимизации, которые принимает сам MySQL.

  1. MySQL выполняет некоррелированные подзапросы только один раз. Используйте EXPLAIN, чтобы убедиться, что ваш запрос некоррелированный.
  2. MySQL перезаписывает подзапросы IN/ALL/ANY/SOME, пытаясь получить выигрыш от использования индексов.
  3. MySQL заменяет подзапросы следующей формы функциями просмотра индексов, которые конструкция EXPLAIN будет описывать как специальный тип соединения:

... IN (SELECT индексированный_столбец FROM одиночная таблица ...)
MySQL заменяет выражения следующей формы выражениями с применением MIN () или МАХ (), если только не вовлечены NULL-значения и пустые наборы:
значение {ALL|ANY|SOME} {> I < I >= I <=} {не-коррелированный-подзапрос)
например, следующая конструкция WHERE:
WHERE 5 > ALL (SELECT x FROM t)
может быть преобразована оптимизатором в такую:
WHERE 5 > (SELECT MAX(x) FROM t)

В руководстве по внутреннему устройству MySQL ("MySQL Internals Manual") име­ется глава "How MySQL Transforms Subqueries" ("Как MySQL трансформирует подза­просы"). Вы можете получить этот документ, выгрузив исходный дистрибутив MySQL и найдя файл internals. text в каталоге Docs.

Замена подзапросов соединениями для ранних версий MySQL
До MySQL 4.1 поддерживались только вложенные запросы для форм INSERT... SELECT... и replace. . .SELECT... Конструкция IN() может использоваться в других контекстах для проверки вхождения в множество значений.
Часто можно переписать запрос без подзапроса:
SELECT * FROM tl WHERE id IN (SELECT id FROM t2); Это можно заменить на:
SELECT DISTINCT tl.* FROM tl,t2 WHERE tl.id=t2.id; Следующие запросы:
SELECT * FROM tl WHERE id NOT IN (SELECT id FROM t2);
SELECT * FROM tl WHERE NOT EXISTS (SELECT id FROM t2 WHERE tl.id=t2.id);
могут быть переписаны так:
SELECT tablel.* FROM tablel LEFT JOIN table2 ON tablel.id=table2. id WHERE table2.id IS NULL;
LEFT [OUTER] JOIN может оказаться быстрее, чем эквивалентный подзапрос, по­скольку сервер может его лучше оптимизировать, и это касается не только одного MySQL. До выхода стандарта SQL-92 внешние соединения не допускались, поэтому подзапросы были единственным способом делать определенные вещи в эти прошедшие времена. Сегодня сервер MySQL и многие другие современные системы баз данных предлагают широкий диапазон типов внешних соединений.
Для более сложных подзапросов часто можно создавать временные таблицы, в кото­рых хранятся промежуточные результаты. Однако в некоторых случаях такой способ неприемлем. Наиболее часто это случается с операторами DELETE, для которых стандарт SQL не поддерживает соединений (кроме как в подзапросах). В этой ситуации возможны три пути:

  1. Первый путь — обновить сервер до версии MySQL 4.1, которая поддерживает подзапросы в операторах DELETE.
  2. Второй путь - использовать процедурный язык программирования (такой как Perl или РНР), чтобы выполнить запрос SELECT для получения значений первичных ключей записей, подлежащих удалению, а затем использовать эти значения для построения оператора DELETE (DELETE FROM . . . WHERE ключевой_столбец IN {ключ1,ключ2, ...)).
  3. Третий путь - использовать интерактивный SQL для автоматического построения набора операторов delete с использованием MySQL-расширения CONCATO (вместо стандартного оператора | |). Например:

SELECT
CONCAT('DELETE FROM tablel WHERE pkid = ', "'", tablel.pkid, ""', ';')
FROM tablel, table2
WHERE tablel.columnl = table2.column2;

Этот запрос можно поместить в файл сценария, указать его в качестве входного для одного экземпляра программы mysql и перенаправить его выходной поток на вход другому экземпляру mysql:
shell> mysql -skip-column-names mydb < my script. sql | mysql mydb

Сервер MySQL 4.0 поддерживает многотабличные операторы DELETE, которые могут использоваться для эффективного удаления строк на основе информации из одной или даже многих таблиц одновременно. Многотабличные операторы UPDATE также поддер­живаются в MySQL 4.O