Выборка данных: оператор SELECT
Для формирования запросов на выборку данных в SQL используется оператор SELECT. Его формат представлен ниже:
SELECT [ ALL | DISTINCT ] select_item_cominalist FROM table_reference_commalist [ WHERE conditional_expression ]
[ GROUP BY column_name_commalist ]
[ ORDER BY order_item_commalist ]
SELECT является достаточно сложным оператором, позволяющим выбирать данные из одной или нескольких таблиц, выполнять группировку, обработку данных с помощью агрегатных функций, формировать вложенные запросы и т.д. Выражение SELECT обрабатывается целиком, а не «построчно», как обычно бывает в языках программирования. В несколько обобщенном виде схема выполнения оператора SELECT выглядит следующим образом:
- 1) выполняется раздел FROM;
- 2) выполняется раздел WHERE (если есть);
- 3) выполняется GROUP BY (если есть);
- 4) выполняется HAVING (если есть);
- 5) выполняются определения в разделе SELECT;
- 6) выполняется ORDER BY (если есть).
Начнем с рассмотрения обязательного раздела SELECT. В нем указывается список элементов выборки select-item-commalist, который не должен быть пустым. Также может использоваться ключевое слово ALL или DISTINCT. Первое из них указывает, что в результате запроса могут быть повторяющиеся строки, второе – что повторения отбрасываются. Например, используется ключевое слово DISTINCT и есть три совпадающих строки, тогда в результате из них останется только одна. Когда явно ничего не указано, то подразумевается ALL.
Вернемся к списку элементов выборки. В нем через запятую могут указываться имена столбцов, константы, операции над столбцами или функции, возвращающие скалярное значение. Кроме того, может использоваться символ «*», обозначающий все столбцы, или .*, что указывает на все столбцы данной таблицы.
Перед тем как перейти к рассмотрению примеров, необходимо отметить, что в разделе FROM указываются таблицы, из которых делается выборка. Если через запятую перечислены несколько таблиц, это соответствует их декартову произведению. Следующий запрос позволит получить содержимое всей таблицы Т1:
SELECT * FROM T1 Аналогичный результат даст запрос SELECT Tl.* FROM Т1
Пусть имеется таблица Students, аналогичная представленной в табл. 7.2. Получить перечень номеров студенческих групп без повторения позволит следующий запрос:
SELECT DISTINCT [Group] FROM Students
По поводу этого запроса надо отметить следующее. Во-первых, предполагается, что номер группы может упоминаться в таблице несколько раз. Поэтому для отбрасывания повторений явно указывается ключевое слово DISTINCT. Если бы в перечне столбцов был первичный или альтернативный ключ, это обеспечило бы уникальность строк в результате запроса и DISTINCT можно было бы опустить. Во-вторых, название столбца Group совпадает с названием инструкции SQL. Поэтому в большинстве случаев потребуется явно указать в СУБД, что речь идет о названии столбца. В частности, для MS SQL Server надо будет использовать двойные кавычки или квадратные скобки: [Group].
Рассмотрим пример с заданием имени столбца и использованием текстовой константы в столбце. Если необходимо явно указать, как столбец будет называться в выводимых результатах запроса, это можно сделать в списке элементов выборки в разделе SELECT. Новое имя указывается после исходного названия столбца через пробел или после необязательного ключевого слова «as». Ниже приведен пример, в котором список фамилий и инициалов студентов сопровождается подписью «Фамилия и инициалы»:
SELECT DISTINCT ‘Фамилия и инициалы’ as Labell, FIO FROM Students
Подпись задается с помощью строковой константы, которые в SQL берутся в одинарные кавычки. Называться столбец с подписью будет Labell. Результат выполнения этого запроса для набора данных из табл. 7.2 представлен в табл. 7.6. Как отмечалось выше, ключевое слово «as» в SELECT можно пропустить, но иногда оно позволяет сделать текст на SQL более понятным.
Результат запроса
Пользовательские функции в операторах SQL SELECT
В списке выражений выборки данных Select_Item стандартного оператора SQL SELECT вы можете использовать пользовательские функции. Тем не менее, необходимо учитывать следующие рекомендации и ограничения по использованию пользовательских функций в операторах SQL SELECT :
- Вместо высокоуровневых пользовательских функций (VFP) рекомендуется использовать функции API или пользовательские функции, разработанные с помощью инструментария «C» или Ассемблера. Высокоуровневые пользовательские функции могут замедлять работу рассматриваемых операторов SQL SELECT .
- При использовании пользовательских функций в операторах SQL SELECT ничего не известно об операциях ввода/вывода системы Visual FoxPro (I/O) или о текущей Среде Данных. В общих чертах, вы не знаете, какая рабочая область является в настоящий момент текущей, имя открытой Таблицы данных, имена требуемых Полей указанной таблицы. Значения конкретных переменных зависят от точного положения указателя Записи, что существенно влияет на выполнение вызываемых функций.
- При выполнении пользовательских функций в операторах SQL SELECT не рекомендуется выполнять операции ввода/вывода Visual FoxPro (I/O) или изменять Среду Данных. В общих чертах, результаты могут быть непредсказуемыми .
- Единственным надежным способом параметризации пользовательских функций, вызываемых в операторах SQL SELECT, является передача списка параметров.
- Понимание термина «forbidden» — правильного разрешения ссылок, обеспечивает получение правильных результатов Запросов в одной версии Visual FoxPro, и совсем других результатов — в другой версии VFP.
Помимо перечисленных Замечаний по правильному использованию пользовательских функций в операторах SQL SELECT . Тем не менее, помните, что использование рассматриваемых функций, в общем случае, замедляет выполнение рассматриваемого оператора SQL SELECT.
Дополнительную информацию о построении пользовательских функций смотри в разделе: Пользовательские Процедуры и Функции .
Агрегатные Функции
Вы можете использовать Агрегатные функции: 1) в списке элементов Выборки Select_Item , как Поле или как Выражение, содержащее поле; 2) в составе условия Фильтрации ключевой секции HAVING . Тем не менее, вы не можете использовать вложенные агрегатные функции.
В следующей таблице приводится список агрегатных функций, которые можно использовать в операторах SQL SELECT .
Вычисление Среднего значения указанного столбца числового типа.
Вычисление количества выбранных элементов стобца. COUNT(*) вычисляет количество выбранных Записей Запроса.
Определяет минимальное значение столбца из списка Select_Item .
Определяет минимальное значение столбца из списка Select_Item .
Вычисление общей суммы указанного столбца числового типа.
В следующем примере создается Запрос, в котором имена столбцов определяются соответствующими вычисляемыми функциями.:
CLEAR ALLCLOSE DATABASESOPEN DATABASE (HOME(2) + 'Data\TestData')SELECT AVG(TAlias1.order_amt), MIN(TAlias1.order_amt) ; FROM Orders AS TAlias1
Дополнительная информация по использованию агрегатных функций в операторах SQL SELECT смотрите в разделах: MIN( ), функция , MAX( ), функция, а также: CALCULATE, команда, которые содержат также информацию о функциях: AVG( ), COUNT( ) , CNT( ), SUM( ).
Правила формирования Имен результирующих столбцов
Следующие правила относятся к случаям, когда Имя результирующего столбца Запроса указывается в опции AS :
- Если элемент Select_Item указывается как имя Поля, данное имя должно быть уникальным в результирующем наборе Запроса.
- Если несколько элементов Select_Item имеют одинаковое имя, то к имени добавляется символ подчеркивания (_) и некоторый символ (_a, _b,_c. ). Например, если в операторе SQL SELECT выполняется выборка поля Cust_ID из двух таблиц Customer и Orders, то имена новых столбцов формируются по шаблону: FieldName _a и FieldName _b, или точнее, Cust_ID_a и Cust_Id_b. В следующем примере имена результирующих столбцов формируются по указанным правилам:
CLEAR ALLCLOSE DATABASESOPEN DATABASE (HOME(2) + 'Data\TestData')SELECT TAlias1.cust_id, TAlias2.cust_id ; FROM Customer AS TAlias1, Orders AS TAlias2 ; WHERE TAlias1.cust_id = TAlias2.cust_id
CLEAR ALLCLOSE DATABASESOPEN DATABASE (HOME(2) + 'Data\TestData')SELECT TAlias1.postalcode+"-1234", TAlias2.postalcode+"-5678" ; FROM Customer AS TAlias1, Orders AS TAlias2 ; WHERE TAlias1.cust_id = TAlias2.cust_id
CLEAR ALLCLOSE DATABASESOPEN DATABASE (HOME(2) + 'Data\TestData')SELECT COUNT(order_id), SUM(order_net)FROM Orders
Операции Объединения (UNION)
При использовании операций Объединения ( UNION ) между несколькими операторами SQL SELECT , нужно учитывать следующие предупреждения и ограничения:
- Все списки выбираемых элементов в нескольких операторах выборки, объединенных ключевой секцией UNION должны содержать одинаковое количество выбираемых элементов (имен столбцов, некоторых выражений, агрегатных функций, и так далее. ).
- Соответствующие столбцы в результирующем наборе Запроса, полученного при объединении UNION , или столбцы из подзапроса в некотором из запросов, должны быть одного типа данных, или допускать преобразование одного типа данных в другой, или представлять из себя буквальное преобразование одного типа данных в требуемый тип. Например, если выполняется объединение (UNION ) между столбцом типа DateTime и столбцом одного из бинарных типов (binary data type), то данное объединение неосуществимо, без явного преобразования одного из указанных типов в другой. Тем не менее, объединение UNION будет работать между столбцом с типом данных (money) и столбцом типа (integer), так как данные типы взаимно конвертируемы. Столбцы типа (xml) должны бать эквивалентными. Все столбцы должны соответствовать одной схеме XML, или быть нетипизированными. Если они типизированы, то должны соответствовать одним типам данных в заданной схеме XML.
- Имена результирующих столбцов, полученных из нескольких Запросов, объединенных ключевой секцией UNION, определяются первым списком выборки, первым оператором SQL SELECT. Данные имена столбцов результирующего набора могут быть использованы в общих секциях Запроса (к примеру, в секции группировки ORDER BY), ссылка на имя столбца должна соотвентствовать первому оператору SELECT.
- Соответствующие столбцы в каждом отдельном операторе Выборки, которые объединяются с помощью ключевой секции UNION, должны быть размещены в одинаковом порядке; секция UNION выполняет сравнения столбцов один-к-одному по порядку расположения столбцов в каждом операторе Выборки. Когда происходит объединение различных типов данных с помощью секции UNION, расположенных в соответствующих столбцах, преобразование выполняется по правилам Приоритета типов данных. В предыдущем примере выполнялось преобразование типа данных int — в тип данных float , в силу того, что тип float имеет больший приоритет, чем int.
Приоритеты преобразования типов данных в операциях объединения (UNION)
До версии Visual FoxPro 8.0, разработчик был вынужден выполнять явное преобразование несоответствующих типов данных для соответствующих столбцов, объединяемых в секции UNION оператора выборки SQL SELECT . Тем не менее, в настоящей версии Visual FoxPro, существует поддержка автоматической конвертации несоответствующих типов данных.
Явное преобразование типов данных требует использования соответствующих конверсионных функций системы Visual FoxPro, таких как CTOD( ) , в тоже время, неявное преобразование типов данных не требует использования конверсионных функций.
Когда система Visual FoxPro выполняет объединение столбцов с различными типами данных в секции UNION , тип данных с меньшим приоритетом конвертируется в тип данных с большим приоритетом. Поля со свойством NULL имеют больший приоритет над полями со свойством NOT NULL .
В следующей таблице представлены все Явные и Неявные преобразования типов данных, выполняемые системой Visual FoxPro:
CTOD( ), CTOT( ), VAL( ), CTOBIN( )
Учебник по языку SQL (DDL, DML) на примере диалекта MS SQL Server. Часть вторая
Вступление и DDL – Data Definition Language (язык описания данных)
DML – Data Manipulation Language (язык манипулирования данными)
В первой части мы уже немного затронули язык DML, применяя почти весь набор его команд, за исключением команды MERGE.
Рассказывать про DML я буду по своей последовательности выработанной на личном опыте. По ходу, так же постараюсь рассказать про «скользкие» места, на которые стоит акцентировать внимание, эти «скользкие» места, схожи во многих диалектах языка SQL.
Т.к. учебник посвящается широкому кругу читателей (не только программистам), то и объяснение, порой будет соответствующее, т.е. долгое и нудное. Это мое видение материала, которое в основном получено на практике в результате профессиональной деятельности.
Основная цель данного учебника, шаг за шагом, выработать полное понимание сути языка SQL и научить правильно применять его конструкции. Профессионалам в этой области, может тоже будет интересно пролистать данный материал, может и они смогут вынести для себя что-то новое, а может просто, будет полезно почитать в целях освежить память. Надеюсь, что всем будет интересно.
Т.к. DML в диалекте БД MS SQL очень сильно связан с синтаксисом конструкции SELECT, то я начну рассказывать о DML именно с нее. На мой взгляд конструкция SELECT является самой главной конструкцией языка DML, т.к. за счет нее или ее частей осуществляется выборка необходимых данных из БД.
- SELECT – выборка данных
- INSERT – вставка новых данных
- UPDATE – обновление данных
- DELETE – удаление данных
- MERGE – слияние данных
В данной части, мы рассмотрим, только базовый синтаксис команды SELECT, который выглядит следующим образом:
SELECT [DISTINCT] список_столбцов или * FROM источник WHERE фильтр ORDER BY выражение_сортировки
Тема оператора SELECT очень обширная, поэтому в данной части я и остановлюсь только на его базовых конструкциях. Я считаю, что, не зная хорошо базы, нельзя приступать к изучению более сложных конструкций, т.к. дальше все будет крутиться вокруг этой базовой конструкции (подзапросы, объединения и т.д.).
Также в рамках этой части, я еще расскажу о предложении TOP. Это предложение я намерено не указал в базовом синтаксисе, т.к. оно реализуется по-разному в разных диалектах языка SQL.
Если язык DDL больше статичен, т.е. при помощи него создаются жесткие структуры (таблицы, связи и т.п.), то язык DML носит динамический характер, здесь правильные результаты вы можете получить разными путями.
Обучение так же будет продолжаться в режиме Step by Step, т.е. при чтении нужно сразу же своими руками пытаться выполнить пример. После делаете анализ полученного результата и пытаетесь понять его интуитивно. Если что-то остается непонятным, например, значение какой-нибудь функции, то обращайтесь за помощью в интернет.
Примеры будут показываться на БД Test, которая была создана при помощи DDL+DML в первой части.
Для тех, кто не создавал БД в первой части (т.к. не всех может интересовать язык DDL), может воспользоваться следующим скриптом:
Скрипт создания БД Test
-- создание БД CREATE DATABASE Test GO -- сделать БД Test текущей USE Test GO -- создаем таблицы справочники CREATE TABLE Positions( ID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_Positions PRIMARY KEY, Name nvarchar(30) NOT NULL ) CREATE TABLE Departments( ID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_Departments PRIMARY KEY, Name nvarchar(30) NOT NULL ) GO -- заполняем таблицы справочники данными SET IDENTITY_INSERT Positions ON INSERT Positions(ID,Name)VALUES (1,N'Бухгалтер'), (2,N'Директор'), (3,N'Программист'), (4,N'Старший программист') SET IDENTITY_INSERT Positions OFF GO SET IDENTITY_INSERT Departments ON INSERT Departments(ID,Name)VALUES (1,N'Администрация'), (2,N'Бухгалтерия'), (3,N'ИТ') SET IDENTITY_INSERT Departments OFF GO -- создаем таблицу с сотрудниками CREATE TABLE Employees( ID int NOT NULL, Name nvarchar(30), Birthday date, Email nvarchar(30), PositionID int, DepartmentID int, HireDate date NOT NULL CONSTRAINT DF_Employees_HireDate DEFAULT SYSDATETIME(), ManagerID int, CONSTRAINT PK_Employees PRIMARY KEY (ID), CONSTRAINT FK_Employees_DepartmentID FOREIGN KEY(DepartmentID) REFERENCES Departments(ID), CONSTRAINT FK_Employees_PositionID FOREIGN KEY(PositionID) REFERENCES Positions(ID), CONSTRAINT FK_Employees_ManagerID FOREIGN KEY (ManagerID) REFERENCES Employees(ID), CONSTRAINT UQ_Employees_Email UNIQUE(Email), CONSTRAINT CK_Employees_ID CHECK(ID BETWEEN 1000 AND 1999), INDEX IDX_Employees_Name(Name) ) GO -- заполняем ее данными INSERT Employees (ID,Name,Birthday,Email,PositionID,DepartmentID,ManagerID)VALUES (1000,N'Иванов И.И.','19550219','i.ivanov@test.tt',2,1,NULL), (1001,N'Петров П.П.','19831203','p.petrov@test.tt',3,3,1003), (1002,N'Сидоров С.С.','19760607','s.sidorov@test.tt',1,2,1000), (1003,N'Андреев А.А.','19820417','a.andreev@test.tt',4,3,1000)
Все, теперь мы готовы приступить к изучению языка DML.
SELECT – оператор выборки данных
Первым делом, для активного редактора запроса, сделаем текущей БД Test, выбрав ее в выпадающем списке или же командой «USE Test».
Начнем с самой элементарной формы SELECT:
SELECT * FROM Employees
В данном запросе мы просим вернуть все столбцы (на это указывает «*») из таблицы Employees – можно прочесть это как «ВЫБЕРИ все_поля ИЗ таблицы_сотрудники». В случае наличия кластерного индекса, возвращенные данные, скорее всего будут отсортированы по нему, в данном случае по колонке ID (но это не суть важно, т.к. в большинстве случаев сортировку мы будем указывать в явном виде сами при помощи ORDER BY …):
| ID | Name | Birthday | PositionID | DepartmentID | HireDate | ManagerID | |
|---|---|---|---|---|---|---|---|
| 1000 | Иванов И.И. | 1955-02-19 | i.ivanov@test.tt | 2 | 1 | 2015-04-08 | NULL |
| 1001 | Петров П.П. | 1983-12-03 | p.petrov@test.tt | 3 | 3 | 2015-04-08 | 1003 |
| 1002 | Сидоров С.С. | 1976-06-07 | s.sidorov@test.tt | 1 | 2 | 2015-04-08 | 1000 |
| 1003 | Андреев А.А. | 1982-04-17 | a.andreev@test.tt | 4 | 3 | 2015-04-08 | 1000 |
Вообще стоит сказать, что в диалекте MS SQL самая простая форма запроса SELECT может не содержать блока FROM, в этом случае вы можете использовать ее, для получения каких-то значений:
SELECT 5550/100*15, SYSDATETIME(), -- получение системной даты БД SIN(0)+COS(0)
| (No column name) | (No column name) | (No column name) |
|---|---|---|
| 825 | 2015-04-11 12:12:36.0406743 | 1 |
Обратите внимание, что выражение (5550/100*15) дало результат 825, хотя если мы посчитаем на калькуляторе получится значение (832.5). Результат 825 получился по той причине, что в нашем выражении все числа целые, поэтому и результат целое число, т.е. (5550/100) дает нам 55, а не (55.5).
- Целое / Целое = Целое (т.е. в данном случае происходит целочисленное деление)
- Вещественное / Целое = Вещественное
- Целое / Вещественное = Вещественное
SELECT 123/10, -- 12 123./10, -- 12.3 123/10. -- 12.3
Здесь (123.) = (123.0), просто в данном случае 0 можно отбросить и оставить только точку.
При других арифметических операциях действует та же самая логика, просто в случае деления этот нюанс более актуален.
Поэтому обращайте внимание на тип данных числовых столбцов. В том случае если он целый, а результат вам нужно получить вещественный, то используйте преобразование, либо просто ставьте точку после числа указанного в виде константы (123.).
Для преобразования полей можно использовать функцию CAST или CONVERT. Для примера воспользуемся полем ID, оно у нас типа int:
SELECT ID, ID/100, -- здесь произойдет целочисленное деление CAST(ID AS float)/100, -- используем функцию CAST для преобразования в тип float CONVERT(float,ID)/100, -- используем функцию CONVERT для преобразования в тип float ID/100. -- используем преобразование за счет указания что знаменатель вещественное число FROM Employees
| ID | (No column name) | (No column name) | (No column name) | (No column name) |
|---|---|---|---|---|
| 1000 | 10 | 10 | 10 | 10.000000 |
| 1001 | 10 | 10.01 | 10.01 | 10.010000 |
| 1002 | 10 | 10.02 | 10.02 | 10.020000 |
| 1003 | 10 | 10.03 | 10.03 | 10.030000 |
На заметку. В БД ORACLE синтаксис без блока FROM недопустим, там для этой цели используется системная таблица DUAL, которая содержит одну строку:
SELECT 5550/100*15, -- а в ORACLE результат будет равен 832.5 sysdate, sin(0)+cos(0) FROM DUAL
Примечание. Имя таблицы во многих РБД может предваряться именем схемы:
SELECT * FROM dbo.Employees -- dbo – имя схемы
Схема – это логическая единица БД, которая имеет свое наименование и позволяет сгруппировать внутри себя объекты БД такие как таблицы, представления и т.д.
Определение схемы в разных БД может отличатся, где-то схема непосредственно связанна с пользователем БД, т.е. в данном случае можно сказать, что схема и пользователь – это синонимы и все создаваемые в схеме объекты по сути являются объектами данного пользователя. В MS SQL схема – это независимая логическая единица, которая может быть создана сама по себе (см. CREATE SCHEMA).
По умолчанию в базе MS SQL создается одна схема с именем dbo (Database Owner) и все создаваемые объекты по умолчанию создаются именно в данной схеме. Соответственно, если мы в запросе указываем просто имя таблицы, то она будет искаться в схеме dbo текущей БД. Если мы хотим создать объект в конкретной схеме, мы должны будем так же предварить имя объекта именем схемы, например, «CREATE TABLE имя_схемы.имя_таблицы(…)».
В случае MS SQL имя схемы может еще предваряться именем БД, в которой находится данная схема:
SELECT * FROM Test.dbo.Employees -- имя_базы.имя_схемы.таблица
- в одном запросе мы обращаемся к объектам расположенных в разных схемах или базах данных
- требуется сделать перенос данных из одной схемы или БД в другую
- находясь в одной БД, требуется запросить данные из другой БД
- и т.п.
Так же не забываем, что в тексте запроса мы можем использовать как однострочные «— …», так и многострочные «/* … */» комментарии. Если запрос большой и сложный, то комментарии могут очень помочь, вам или кому-то другому, через некоторое время, вспомнить или разобраться в его структуре.
Если столбцов в таблице очень много, а особенно, если в таблице еще очень много строк, плюс к тому если мы делаем запросы к БД по сети, то предпочтительней будет выборка с непосредственным перечислением необходимых вам полей через запятую:
SELECT ID,Name FROM Employees
Т.е. здесь мы говорим, что нам из таблицы нужно вернуть только поля ID и Name. Результат будет следующим (кстати оптимизатор здесь решил воспользоваться индексом, созданным по полю Name):
| ID | Name |
|---|---|
| 1003 | Андреев А.А. |
| 1000 | Иванов И.И. |
| 1001 | Петров П.П. |
| 1002 | Сидоров С.С. |
На заметку. Порой бывает полезным посмотреть на то как осуществляется выборка данных, например, чтобы выяснить какие индексы используются. Это можно сделать если нажать кнопку «Display Estimated Execution Plan – Показать расчетный план» или установить «Include Actual Execution Plan – Включить в результат актуальный план выполнения запроса» (в данном случае мы сможем увидеть уже реальный план, соответственно, только после выполнения запроса):
Анализ плана выполнения очень полезен при оптимизации запроса, он позволяет выяснить каких индексов не хватает или же какие индексы вообще не используются и их можно удалить.
Если вы только начали осваивать DML, то сейчас для вас это не так важно, просто возьмите на заметку и можете спокойно забыть об этом (может это вам никогда и не пригодится) – наша первоначальная цель изучить основы языка DML и научится правильно применять их, а оптимизация это уже отдельное искусство. Порой важнее, чтобы на руках просто был правильно написанный запрос, который возвращает правильные результат с предметной точки зрения, а его оптимизацией уже занимаются отдельные люди. Для начала вам нужно научиться просто правильно писать запросы, используя любые средства для достижения цели. Главная цель которую вы сейчас должны достичь – чтобы ваш запрос возвращал правильные результаты.
Задание псевдонимов для таблиц
При перечислении колонок их можно предварять именем таблицы, находящейся в блоке FROM:
SELECT Employees.ID,Employees.Name FROM Employees
Но такой синтаксис обычно использовать неудобно, т.к. имя таблицы может быть длинным. Для этих целей обычно задаются и применяются более короткие имена – псевдонимы (alias):
SELECT emp.ID,emp.Name FROM Employees AS emp
SELECT emp.ID,emp.Name FROM Employees emp -- ключевое слово AS можно отпустить (я предпочитаю такой вариант)
Здесь emp – псевдоним для таблицы Employees, который можно будет использоваться в контексте данного оператора SELECT. Т.е. можно сказать, что в контексте этого оператора SELECT мы задаем таблице новое имя.
Конечно, в данном случае результаты запросов будут точно такими же как и для «SELECT ID,Name FROM Employees». Для чего это нужно будет понятно дальше (даже не в этой части), пока просто запоминаем, что имя колонки можно предварять (уточнять) либо непосредственно именем таблицы, либо при помощи псевдонима. Здесь можно использовать одно из двух, т.е. если вы задали псевдоним, то и пользоваться нужно будет им, а использовать имя таблицы уже нельзя.
На заметку. В ORACLE допустим только вариант задания псевдонима таблицы без ключевого слова AS.
DISTINCT – отброс строк дубликатов
Ключевое слово DISTINCT используется для того чтобы отбросить из результата запроса строки дубликаты. Грубо говоря представьте, что сначала выполняется запрос без опции DISTINCT, а затем из результата выбрасываются все дубликаты. Продемонстрируем это для большей наглядности на примере:
-- создадим для демонстрации временную таблицу CREATE TABLE #Trash( ID int NOT NULL PRIMARY KEY, Col1 varchar(10), Col2 varchar(10), Col3 varchar(10) ) -- наполним данную таблицу всяким мусором INSERT #Trash(ID,Col1,Col2,Col3)VALUES (1,'A','A','A'), (2,'A','B','C'), (3,'C','A','B'), (4,'A','A','B'), (5,'B','B','B'), (6,'A','A','B'), (7,'A','A','A'), (8,'C','A','B'), (9,'C','A','B'), (10,'A','A','B'), (11,'A',NULL,'B'), (12,'A',NULL,'B') -- посмотрим что возвращает запрос без опции DISTINCT SELECT Col1,Col2,Col3 FROM #Trash -- посмотрим что возвращает запрос с опцией DISTINCT SELECT DISTINCT Col1,Col2,Col3 FROM #Trash -- удалим временную таблицу DROP TABLE #Trash
Наглядно это будет выглядеть следующим образом (все дубликаты помечены одним цветом):

Теперь давайте рассмотрим где это можно применить, на более практичном примере – вернем из таблицы Employees только уникальные идентификаторы отделов (т.е. узнаем ID отделов в которых числятся сотрудники):
SELECT DISTINCT DepartmentID FROM Employees
| DepartmentID |
|---|
| 1 |
| 2 |
| 3 |
Здесь мы получили три строки, т.к. 2 сотрудника у нас числятся в одном отделе (ИТ).
Теперь узнаем в каких отделах, какие должности фигурируют:
SELECT DISTINCT DepartmentID,PositionID FROM Employees
| DepartmentID | PositionID |
|---|---|
| 1 | 2 |
| 2 | 1 |
| 3 | 3 |
| 3 | 4 |
Здесь мы получили 4 строчки, т.к. повторяющихся комбинаций (DepartmentID, PositionID) в нашей таблице нет.
Ненадолго вернемся к DDL
Так как данных для демонстрационных примеров начинает не хватать, а рассказать хочется более обширно и понятно, то давайте чуть расширим нашу таблицу Employess. К тому же немного вспомним DDL, как говорится «повторение – мать учения», и плюс снова немного забежим вперед и применим оператор UPDATE:
-- создаем новые колонки ALTER TABLE Employees ADD LastName nvarchar(30), -- фамилия FirstName nvarchar(30), -- имя MiddleName nvarchar(30), -- отчество Salary float, -- и конечно же ЗП в каких-то УЕ BonusPercent float -- процент для вычисления бонуса от оклада GO -- наполняем их данными (некоторые данные намерено пропущены) UPDATE Employees SET LastName=N'Иванов',FirstName=N'Иван',MiddleName=N'Иванович', Salary=5000,BonusPercent= 50 WHERE -- Иванов И.И. UPDATE Employees SET LastName=N'Петров',FirstName=N'Петр',MiddleName=N'Петрович', Salary=1500,BonusPercent= 15 WHERE -- Петров П.П. UPDATE Employees SET LastName=N'Сидоров',FirstName=N'Сидор',MiddleName=NULL, Salary=2500,BonusPercent=NULL WHERE -- Сидоров С.С. UPDATE Employees SET LastName=N'Андреев',FirstName=N'Андрей',MiddleName=NULL, Salary=2000,BonusPercent= 30 WHERE -- Андреев А.А.
Убедимся, что данные обновились успешно:
SELECT * FROM Employees
| ID | Name | … | LastName | FirstName | MiddleName | Salary | BonusPercent |
|---|---|---|---|---|---|---|---|
| 1000 | Иванов И.И. | Иванов | Иван | Иванович | 5000 | 50 | |
| 1001 | Петров П.П. | Петров | Петр | Петрович | 1500 | 15 | |
| 1002 | Сидоров С.С. | Сидоров | Сидор | NULL | 2500 | NULL | |
| 1003 | Андреев А.А. | Андреев | Андрей | NULL | 2000 | 30 |
Задание псевдонимов для столбцов запроса
Думаю, здесь будет проще показать, чем написать:
SELECT -- даем имя вычисляемому столбцу LastName+' '+FirstName+' '+MiddleName AS ФИО, -- использование двойных кавычек, т.к. используется пробел HireDate AS "Дата приема", -- использование квадратных скобок, т.к. используется пробел Birthday AS [Дата рождения], -- слово AS не обязательно Salary ZP FROM Employees
| ФИО | Дата приема | Дата рождения | ZP |
|---|---|---|---|
| Иванов Иван Иванович | 2015-04-08 | 1955-02-19 | 5000 |
| Петров Петр Петрович | 2015-04-08 | 1983-12-03 | 1500 |
| NULL | 2015-04-08 | 1976-06-07 | 2500 |
| NULL | 2015-04-08 | 1982-04-17 | 2000 |
Как видим заданные нами псевдонимы столбцов, отразились в заголовке результирующей таблицы. Собственно, это и есть основное предназначение псевдонимов столбцов.
Обратите внимание, т.к. у последних 2-х сотрудников не указано отчество (NULL значение), то результат выражения «LastName+’ ‘+FirstName+’ ‘+MiddleName» так же вернул нам NULL.
Для соединения (сложения, конкатенации) строк в MS SQL используется символ «+».
Запомним, что все выражения в которых участвует NULL (например, деление на NULL, сложение с NULL) будут возвращать NULL.
На заметку.
В случае ORACLE для объединения строк используется оператор «||» и конкатенация будет выглядеть как «LastName||’ ‘||FirstName||’ ‘||MiddleName». Для ORACLE стоит отметить, что у него для строковых типов есть исключение, для них NULL и пустая строка » это одно и тоже, поэтому в ORACLE такое выражение вернет для последних 2-х сотрудников «Сидоров Сидор » и «Андреев Андрей ». На момент версии ORACLE 12c, насколько я знаю, опции которая изменяет такое поведение нет (если не прав, прошу поправить меня). Здесь мне сложно судить хорошо это или плохо, т.к. в одних случаях удобнее поведение NULL-строки как в MS SQL, а в других как в ORACLE.
В ORACLE тоже допустимы все перечисленные выше псевдонимы столбцов, кроме […].
Для того чтобы не городить конструкцию с использованием функции ISNULL, в MS SQL мы можем применить функцию CONCAT. Рассмотрим и сравним 3 варианта:
SELECT LastName+' '+FirstName+' '+MiddleName FullName1, -- 2 варианта для замены NULL пустыми строками '' (получаем поведение как и в ORACLE) ISNULL(LastName,'')+' '+ISNULL(FirstName,'')+' '+ISNULL(MiddleName,'') FullName2, CONCAT(LastName,' ',FirstName,' ',MiddleName) FullName3 FROM Employees
| FullName1 | FullName2 | FullName3 |
|---|---|---|
| Иванов Иван Иванович | Иванов Иван Иванович | Иванов Иван Иванович |
| Петров Петр Петрович | Петров Петр Петрович | Петров Петр Петрович |
| NULL | Сидоров Сидор | Сидоров Сидор |
| NULL | Андреев Андрей | Андреев Андрей |
В MS SQL псевдонимы еще можно задавать при помощи знака равенства:
SELECT 'Дата приема'=HireDate, -- помимо "…" и […] можно использовать '…' [Дата рождения]=Birthday, ZP=Salary FROM Employees
Использовать для задания псевдонима ключевое слово AS или же знак равенства, наверное, больше дело вкуса. Но при разборе чужих запросов, данные знания могут пригодиться.
Напоследок скажу, что для псевдонимов имена лучше задавать, используя только символы латиницы и цифры, избегая применения ‘…’, «…» и […], то есть использовать те же правила, что мы использовали при наименовании таблиц. Дальше, в примерах я буду использовать только такие наименования и никаких ‘…’, «…» и […].
Основные арифметические операторы SQL
| Оператор | Действие |
|---|---|
| + | Сложение (x+y) или унарный плюс (+x) |
| — | Вычитание (x-y) или унарный минус (-x) |
| * | Умножение (x*y) |
| / | Деление (x/y) |
| % | Остаток от деления (x%y). Для примера 15%10 даст 5 |
Приоритет выполнения арифметических операторов такой же, как и в математике. Если необходимо, то порядок применения операторов можно изменить используя круглые скобки — (a+b)*(x/(y-z)).
И еще раз повторюсь, что любая операция с NULL дает NULL, например: 10+NULL, NULL*15/3, 100/NULL – все это даст в результате NULL. Т.е. говоря просто неопределенное значение не может дать определенный результат. Учитывайте это при составлении запроса и при необходимости делайте обработку NULL значений функциями ISNULL, COALESCE:
SELECT ID,Name, Salary/100*BonusPercent AS Result1, -- без обработки NULL значений Salary/100*ISNULL(BonusPercent,0) AS Result2, -- используем функцию ISNULL Salary/100*COALESCE(BonusPercent,0) AS Result3 -- используем функцию COALESCE FROM Employees
| ID | Name | Result1 | Result2 | Result3 |
|---|---|---|---|---|
| 1000 | Иванов И.И. | 2500 | 2500 | 2500 |
| 1001 | Петров П.П. | 225 | 225 | 225 |
| 1002 | Сидоров С.С. | NULL | 0 | 0 |
| 1003 | Андреев А.А. | 600 | 600 | 600 |
| 1004 | Николаев Н.Н. | NULL | 0 | 0 |
| 1005 | Александров А.А. | NULL | 0 | 0 |
Немного расскажу о функции COALESCE:
COALESCE (expr1, expr2, . exprn) - Возвращает первое не NULL значение из списка значений.
SELECT COALESCE(f1, f1*f2, f2*f3) val -- в данном случае вернется третье значение FROM (SELECT null f1, 2 f2, 3 f3) q
В основном, я сосредоточусь на рассказе конструкций языка DML и по большей части не буду рассказывать о функциях, которые будут встречаться в примерах. Если вам непонятно, что делает та или иная функция поищите ее описание в интернет, можете даже поискать информацию сразу по группе функций, например, задав в поиске Google «MS SQL строковые функции», «MS SQL математические функции» или же «MS SQL функции обработки NULL». Информации по функциям очень много, и вы ее сможете без труда найти. Для примера, в библиотеке MSDN, можно узнать больше о функции COALESCE:
Вырезка из MSDN Сравнение COALESCE и CASE
Выражение COALESCE — синтаксический ярлык для выражения CASE. Это означает, что код COALESCE(expression1. n) переписывается оптимизатором запросов как следующее выражение CASE:
CASE WHEN (expression1 IS NOT NULL) THEN expression1 WHEN (expression2 IS NOT NULL) THEN expression2 . ELSE expressionN END
Для примера рассмотрим, как можно воспользоваться остатком от деления (%). Данный оператор очень полезен, когда требуется разбить записи на группы. Например, вытащим всех сотрудников, у которых четные табельные номера (ID), т.е. те ID, которые делятся на 2:
SELECT ID,Name FROM Employees WHERE ID%2=0 -- остаток от деления на 2 равен 0
| ID | Name |
|---|---|
| 1000 | Иванов И.И. |
| 1004 | Николаев Н.Н. |
| 1002 | Сидоров С.С. |
ORDER BY – сортировка результата запроса
Предложение ORDER BY используется для сортировки результата запроса.
SELECT LastName, FirstName, Salary FROM Employees ORDER BY LastName,FirstName -- упорядочить результат по 2-м столбцам – по Фамилии, и после по Имени
| LastName | FirstName | Salary |
|---|---|---|
| Андреев | Андрей | 2000 |
| Иванов | Иван | 5000 |
| Петров | Петр | 1500 |
| Сидоров | Сидор | 2500 |
После имя поля в предложении ORDER BY можно задать опцию DESC, которая служит для сортировки этого поля в порядке убывания:
SELECT LastName,FirstName,Salary FROM Employees ORDER BY -- упорядочить в порядке Salary DESC, -- 1. убывания Заработной Платы LastName, -- 2. по Фамилии FirstName -- 3. по Имени
| LastName | FirstName | Salary |
|---|---|---|
| Иванов | Иван | 5000 |
| Сидоров | Сидор | 2500 |
| Андреев | Андрей | 2000 |
| Петров | Петр | 1500 |
Для заметки. Для сортировки по возрастанию есть ключевое слово ASC, но так как сортировка по возрастанию применяется по умолчанию, то про эту опцию можно забыть (я не помню случая, чтобы я когда-то использовал эту опцию).
Стоит отметить, что в предложении ORDER BY можно использовать и поля, которые не перечислены в предложении SELECT (кроме случая, когда используется DISTINCT, об этом случае я расскажу ниже). Для примера забегу немного вперед используя опцию TOP и покажу, как например, можно отобрать 3-х сотрудников у которых самая высокая ЗП, с учетом что саму ЗП в целях конфиденциальности я показывать не должен:
SELECT TOP 3 -- вернуть только 3 первые записи из всего результата ID,LastName,FirstName FROM Employees ORDER BY Salary DESC -- сортируем результат по убыванию Заработной Платы
| ID | LastName | FirstName |
|---|---|---|
| 1000 | Иванов | Иван |
| 1002 | Сидоров | Сидор |
Конечно здесь есть случай, что у нескольких сотрудников может быть одинаковая ЗП и тут сложно сказать каких именно трех сотрудников вернет данный запрос, это уже нужно решать с постановщиком задачи. Допустим, после обсуждения с постановщиком данной задачи, вы согласовали и решили использовать следующий вариант – сделать дополнительную сортировку по полю даты рождения (т.е. молодым у нас дорога), а если и дата рождения у нескольких сотрудников может совпасть (ведь такое тоже не исключено), то можно сделать третью сортировку по убыванию значений ID (в последнюю очередь под выборку попадут те, у кого ID окажется максимальным – например, те кто был принят последним, допустим табельные номера у нас выдаются последовательно):
SELECT TOP 3 -- вернуть только 3 первые записи из всего результата ID,LastName,FirstName FROM Employees ORDER BY Salary DESC, -- 1. сортируем результат по убыванию Заработной Платы Birthday, -- 2. потом по Дате рождения ID DESC -- 3. и для полной однозначности результата добавляем сортировку по ID
Т.е. вы должны стараться чтобы результат запроса был предсказуемым, чтобы вы могли в случае разбора полетов объяснить почему в «черный список» попали именно эти люди, т.е. все было выбрано честно, по утверждённым правилам.
Сортировать можно так же используя разные выражения в предложении ORDER BY:
SELECT LastName,FirstName FROM Employees ORDER BY CONCAT(LastName,' ',FirstName) -- используем выражение
Так же в ORDER BY можно использовать псевдонимы заданные для колонок:
SELECT CONCAT(LastName,' ',FirstName) fi FROM Employees ORDER BY fi -- используем псевдоним
Стоит отметить что в случае использования предложения DISTINCT, в предложении ORDER BY могут использоваться только колонки, перечисленные в блоке SELECT. Т.е. после применения операции DISTINCT мы получаем новый набор данных, с новым набором колонок. По этой причине, следующий пример не отработает:
SELECT DISTINCT LastName,FirstName,Salary FROM Employees ORDER BY ID -- ID отсутствует в итоговом наборе, который мы получили при помощи DISTINCT
Т.е. предложение ORDER BY применяется уже к итоговому набору, перед выдачей результата пользователю.
Примечание 1. Так же в предложении ORDER BY можно использовать номера столбцов, перечисленных в SELECT:
SELECT LastName,FirstName,Salary FROM Employees ORDER BY -- упорядочить в порядке 3 DESC, -- 1. убывания Заработной Платы 1, -- 2. по Фамилии 2 -- 3. по Имени
Для начинающих выглядит удобно и заманчиво, но лучше забыть и никогда не использовать такой вариант сортировки.
Если в данном случае (когда поля явно перечислены), такой вариант еще допустим, то для случая с использованием «*» такой вариант лучше никогда не применять. Почему – потому что, если кто-то, например, поменяет в таблице порядок столбцов, или удалит столбцы (и это нормальная ситуация), ваш запрос может так же работать, но уже неправильно, т.к. сортировка уже может идти по другим столбцам, и это коварно тем что данная ошибка может обнаружиться очень нескоро.
В случае, если бы столбы были явно перечислены, то в вышеуказанной ситуации, запрос либо бы продолжал работать, но также правильно (т.к. все явно определено), либо бы он просто выдал ошибку, что данного столбца не существует.
Так что можете смело забыть, о сортировке по номерам столбцов.
Примечание 2.
В MS SQL при сортировке по возрастанию NULL значения будут отображаться первыми.
SELECT BonusPercent FROM Employees ORDER BY BonusPercent
Соответственно при использовании DESC они будут в конце
SELECT BonusPercent FROM Employees ORDER BY BonusPercent DESC
Если необходимо поменять логику сортировки NULL значений, то используйте выражения, например:
SELECT BonusPercent FROM Employees ORDER BY ISNULL(BonusPercent,100)
В ORACLE для этой цели предусмотрены 2 опции NULLS FIRST и NULLS LAST (применяется по умолчанию). Например:
SELECT BonusPercent FROM Employees ORDER BY BonusPercent DESC NULLS LAST
Обращайте на это внимание при переходе на ту или иную БД.
TOP – возврат указанного числа записей
Вырезка из MSDN. TOP – ограничивает число строк, возвращаемых в результирующем наборе запроса до заданного числа или процентного значения. Если предложение TOP используется совместно с предложением ORDER BY, то результирующий набор ограничен первыми N строками отсортированного результата. В противном случае возвращаются первые N строк в неопределенном порядке.
Обычно данное выражение используется с предложением ORDER BY и мы уже смотрели примеры, когда нужно было вернуть N-первых строк из результирующего набора.
Без ORDER BY обычно данное предложение применяется, когда нужно просто посмотреть на неизвестную нам таблицу, в которой может быть очень много записей, в этом случае мы можем, для примера, попросить вернуть нам только первые 10 строк, но для наглядности мы скажем только 2:
SELECT TOP 2 * FROM Employees
Так же можно указать слово PERCENT, для того чтобы вернулось соответствуй процент строк из результирующего набора:
SELECT TOP 25 PERCENT * FROM Employees
На моей практике чаше применяется именно выборка по количеству строк.
Так же с TOP можно использовать опцию WITH TIES, которая поможет вернуть все строки в случае неоднозначной сортировки, т.е. это предложение вернет все строки, которые равны по составу строкам, которые попадают в выборку TOP N, в итоге строк может быть выбрано больше чем N. Давайте для демонстрации добавим еще одного «Программиста» с окладом 1500:
INSERT Employees(ID,Name,Email,PositionID,DepartmentID,ManagerID,Salary) VALUES(1004,N'Николаев Н.Н.','n.nikolayev@test.tt',3,3,1003,1500)
и введем еще одного сотрудника без указания должности и отдела с окладом 2000:
INSERT Employees(ID,Name,Email,PositionID,DepartmentID,ManagerID,Salary) VALUES(1005,N'Александров А.А.','a.alexandrov@test.tt',NULL,NULL,1000,2000)
Теперь давайте выберем при помощи опции WITH TIES всех сотрудников, у которых оклад совпадает с окладами 3-х сотрудников, с самым маленьким окладом (надеюсь дальше будет понятно, к чему я клоню):
SELECT TOP 3 WITH TIES ID,Name,Salary FROM Employees ORDER BY Salary
Здесь хоть и указано TOP 3, но запрос вернул 4 записи, т.к. значение Salary которое вернуло TOP 3 (1500 и 2000) оказалось у 4-х сотрудников. Наглядно это работает примерно следующим образом:

На заметку.
В разных БД TOP реализуется разными способами, в MySQL для этого есть предложение LIMIT, в котором дополнительно можно задать начальное смещение.
В ORACLE 12c, тоже ввели свой аналог совмещающий функциональность TOP и LIMIT – ищите по словам «ORACLE OFFSET FETCH». До версии 12c для этой цели обычно использовался псевдостолбец ROWNUM.
А что же будет если применить одновременно предложения DISTINCT и TOP? На такие вопросы легко ответить, проводя эксперименты. В общем, не бойтесь и не ленитесь экспериментировать, т.к. большая часть познается именно на практике. Порядок слов в операторе SELECT следующий, первым идет DISTINCT, а после него идет TOP, т.е. если рассуждать логически и читать слева-направо, то первым применится отброс дубликатов, а потом уже по этому набору будет сделан TOP. Что-ж проверим и убедимся, что так и есть:
SELECT DISTINCT TOP 2 Salary FROM Employees ORDER BY Salary
| Salary |
|---|
| 1500 |
| 2000 |
Т.е. в результате мы получили 2 самые маленькие зарплаты из всех. Конечно может быть случай что ЗП для каких-то сотрудников может быть не указанной (NULL), т.к. схема нам это позволяет. Поэтому в зависимости от задачи принимаем решение либо обработать NULL значения в предложении ORDER BY, либо просто отбросить все записи, у которых Salary равна NULL, а для этого переходим к изучению предложения WHERE.
WHERE – условие выборки строк
Данное предложение служит для фильтрации записей по заданному условию. Например, выберем всех сотрудников работающих в «ИТ» отделе (его >SELECT ID,LastName,FirstName,Salary FROM Employees WHERE DepartmentID=3 — ИТ ORDER BY LastName,FirstName
| ID | LastName | FirstName | Salary |
|---|---|---|---|
| 1004 | NULL | NULL | 1500 |
| 1003 | Андреев | Андрей | 2000 |
| 1001 | Петров | Петр | 1500 |
Предложение WHERE пишется до команды ORDER BY.
- WHERE – если указано, то первым делом из всего набора Employees идет отбор только удовлетворяющих условию записей
- DISTINCT – если указано, то отбрасываются все дубликаты
- ORDER BY – если указано, то делается сортировка результата
- TOP – если указано, то из отсортированного результата возвращается только указанное число записей
Рассмотрим для наглядности пример:
SELECT DISTINCT TOP 1 Salary FROM Employees WHERE DepartmentID=3 ORDER BY Salary
Наглядно это будет выглядеть следующим образом:

Стоит отметить, что проверка на NULL делается не знаком равенства, а при помощи операторов IS NULL и IS NOT NULL. Просто запомните, что на NULL при помощи оператора «=» (знак равенства) сравнивать нельзя, т.к. результат выражения будет так же равен NULL.
Например, выберем всех сотрудников, у которых не указан отдел (т.е. DepartmentID IS NULL):
SELECT ID,Name FROM Employees WHERE DepartmentID IS NULL
| ID | Name |
|---|---|
| 1005 | Александров А.А. |
Теперь для примера посчитаем бонус для всех сотрудников у которых указано значение BonusPercent (т.е. BonusPercent IS NOT NULL):
SELECT ID,Name,Salary/100*BonusPercent AS Bonus FROM Employees WHERE BonusPercent IS NOT NULL
Да, кстати, если подумать, то значение BonusPercent может равняться нулю (0), а так же значение может быть внесено со знаком минус, ведь мы не накладывали на данное поле никаких ограничений.
SELECT ID,Name,Salary/100*BonusPercent AS Bonus FROM Employees WHERE NOT(BonusPercent
Так же данное выражение можно переписать и сразу сказав сразу «верни всех сотрудников, у которых есть бонус» выразив это выражением (BonusPercent>0 и BonusPercent IS NOT NULL):
SELECT ID,Name,Salary/100*BonusPercent AS Bonus FROM Employees WHERE BonusPercent>0 AND BonusPercent IS NOT NULL
Также в блоке WHERE можно делать проверку разного рода выражений с применением арифметических операторов и функций. Например, аналогичную проверку можно сделать, использовав выражение с функцией ISNULL:
SELECT ID,Name,Salary/100*BonusPercent AS Bonus FROM Employees WHERE ISNULL(BonusPercent,0)>0
Булевы операторы и простые операторы сравнения
Да, без математики здесь не обойтись, поэтому сделаем небольшой экскурс по булевым и простым операторам сравнения.
Булевых операторов в языке SQL всего 3 – AND, OR и NOT:
| AND | логическое И. Ставится между двумя условиями (условие1 AND условие2). Чтобы выражение вернуло True, нужно, чтобы истинными были оба условия |
|---|---|
| OR | логическое ИЛИ. Ставится между двумя условиями (условие1 OR условие2). Чтобы выражение вернуло True, достаточно, чтобы истинным было только одно условие |
| NOT | инвертирует условие/логическое_выражение. Накладывается на другое выражение (NOT логическое_выражение) и возвращает True, если логическое_выражение = False и возвращает False, если логическое_выражение = True |
Для каждого булева оператора можно привести таблицы истинности где дополнительно показано какой будет результат, когда условия могут быть равны NULL:

Есть следующие простые операторы сравнения, которые используются для формирования условий:
| Условие | Значение |
|---|---|
| = | Равно |
| Меньше | |
| > | Больше |
| Меньше или равно | |
| >= | Больше или равно |
| <> != |
Не равно |
Плюс имеются 2 оператора для проверки значения/выражения на NULL:
| IS NULL | Проверка на равенство NULL |
|---|---|
| IS NOT NULL | Проверка на неравенство NULL |
Приоритет: 1) Все операторы сравнения; 2) NOT; 3) AND; 4) OR.
При построении сложных логических выражений используются круглые скобки:
((условие1 AND условие2) OR NOT(условие3 AND условие4 AND условие5)) OR (…)
Так же при помощи использования круглых скобок, можно изменить стандартную последовательность вычислений.
Здесь я постарался дать представление о булевой алгебре в достаточном для работы объеме. Как видите, чтобы писать условия посложнее без логики уже не обойтись, но ее здесь немного (AND, OR и NOT) и придумывали ее люди, так что все достаточно логично.
Идем к завершению второй части
Как видите даже про базовый синтаксис оператора SELECT можно говорить очень долго, но, чтобы остаться в рамках статьи, напоследок я покажу дополнительные логических операторы – BETWEEN, IN и LIKE.
BETWEEN – проверка на вхождение в диапазон
Этот оператор имеет следующий вид:
проверяемое_значение [NOT] BETWEEN начальное_ значение AND конечное_ значение
В роли значений могут выступать выражения.
Разберем на примере:
SELECT ID,Name,Salary FROM Employees WHERE Salary BETWEEN 2000 AND 3000 -- у кого ЗП в диапазоне 2000-3000
| ID | Name | Salary |
|---|---|---|
| 1002 | Сидоров С.С. | 2500 |
| 1003 | Андреев А.А. | 2000 |
| 1005 | Александров А.А. | 2000 |
Собственно, BETWEEN это упрощенная запись вида:
SELECT ID,Name,Salary FROM Employees WHERE Salary>=2000 AND Salary
Перед словом BETWEEN может использоваться слово NOT, которое будет осуществлять проверку значения на не вхождение в указанный диапазон:
SELECT ID,Name,Salary FROM Employees WHERE Salary NOT BETWEEN 2000 AND 3000 -- аналогично выражению NOT(Salary>=2000 AND Salary<=3000)
Соответственно, в случае использования BETWEEN, IN, LIKE вы можете так же объединять их с другими условиями при помощи AND и OR:
SELECT ID,Name,Salary FROM Employees WHERE Salary BETWEEN 2000 AND 3000 -- у кого ЗП в диапазоне 2000-3000 AND DepartmentID=3 -- учитывать сотрудников только отдела 3
IN – проверка на вхождение в перечень значений
Этот оператор имеет следующий вид:
проверяемое_значение [NOT] IN (значение1, значение2, …)
Думаю, проще показать на примере:
SELECT ID,Name,Salary FROM Employees WHERE PositionID IN(3,4) -- у кого должность равна 3 или 4
| ID | Name | Salary |
|---|---|---|
| 1001 | Петров П.П. | 1500 |
| 1003 | Андреев А.А. | 2000 |
| 1004 | Николаев Н.Н. | 1500 |
Т.е. по сути это аналогично следующему выражению:
SELECT ID,Name,Salary FROM Employees WHERE PositionID=3 OR PositionID=4 -- у кого должность равна 3 или 4
В случае NOT это будет аналогично (получим всех кроме тех, кто из отдела 3 и 4):
SELECT ID,Name,Salary FROM Employees WHERE PositionID NOT IN(3,4) -- аналогично выражению NOT(PositionID=3 OR PositionID=4)
Так же запрос с NOT IN можно выразить и через AND:
SELECT ID,Name,Salary FROM Employees WHERE PositionID<>3 AND PositionID<>4 -- равносильно PositionID NOT IN(3,4)
Учтите, что искать NULL значения при помощи конструкции IN не получится, т.к. проверка NULL=NULL вернет так же NULL, а не True:
SELECT ID,Name,DepartmentID FROM Employees WHERE DepartmentID IN(1,2,NULL) -- NULL записи не войдут в результат
В этом случае разбивайте проверку на несколько условий:
SELECT ID,Name,DepartmentID FROM Employees WHERE DepartmentID IN(1,2) -- 1 или 2 OR DepartmentID IS NULL -- или NULL
Или же можно написать что-то вроде:
SELECT ID,Name,DepartmentID FROM Employees WHERE ISNULL(DepartmentID,-1) IN(1,2,-1) -- если вы уверены, что в нет и не будет департамента с первый вариант, в данном случае будет более правильным и надежным. Ну ладно, это всего лишь пример, для демонстрации того какие еще конструкции можно строить.
Так же стоит упомянуть еще более коварную ошибку, связанную с NULL, которую можно допустить при использовании конструкции NOT IN. Для примера, давайте попробуем выбрать всех сотрудников, кроме тех, у которых отдел равен 1 или у которых отдел вообще не указан, т.е. равен NULL. В качестве решения напрашивается вариант:
SELECT ID,Name,DepartmentID FROM Employees WHERE DepartmentID NOT IN(1,NULL)
Но выполнив запрос, мы не получим ни одной строки, хотя мы ожидали увидеть следующее:
| ID | Name | DepartmentID |
|---|---|---|
| 1001 | Петров П.П. | 3 |
| 1002 | Сидоров С.С. | 2 |
| 1003 | Андреев А.А. | 3 |
| 1004 | Николаев Н.Н. | 3 |
Опять же шутку здесь сыграло NULL указанное в списке значений.
Разберем почему в данном случае возникла логическая ошибка. Разложим запрос при помощи AND:
SELECT ID,Name,DepartmentID FROM Employees WHERE DepartmentID<>1 AND DepartmentID<>NULL -- проблема из-за этой проверки на NULL - это условие всегда вернет NULL
Правое условие (DepartmentID<>NULL) нам всегда здесь даст неопределенность, т.е. NULL. Теперь вспомним таблицу истинности для оператора AND, где (TRUE AND NULL) дает NULL. Т.е. при выполнении левого условия (DepartmentID<>1) из-за неопределенного правого условия в результате мы получим неопределенное значение всего выражения (DepartmentID<>1 AND DepartmentID<>NULL), поэтому строка не войдет в результат.
Переписать условие правильно можно следующим образом:
SELECT ID,Name,DepartmentID FROM Employees WHERE DepartmentID NOT IN(1) -- или в данном случае просто DepartmentID<>1 AND DepartmentID IS NOT NULL -- и отдельно проверяем на NOT NULL
IN еще можно использовать с подзапросами, но к такой форме мы вернемся, уже в последующих частях данного учебника.
LIKE – проверка строки по шаблону
Про данный оператор я расскажу только в самом простом виде, который является стандартом и поддерживается большинством диалектов языка SQL. Даже в таком виде при помощи него можно решить много задач, которые требуют выполнить проверку по содержимому строки.
Этот оператор имеет следующий вид:
проверяемая_строка [NOT] LIKE строка_шаблон [ESCAPE отменяющий_символ]
- Знак подчеркивания «_» — говорит, что на его месте может стоять любой единичный символ
- Знак процента «%» — говорит, что на его месте может стоять сколько угодно символов, в том числе и ни одного
SELECT ID,Name FROM Employees WHERE Name LIKE 'Пет%' -- у кого имя начинается с букв "Пет" SELECT ID,LastName FROM Employees WHERE LastName LIKE '%ов' -- у кого фамилия оканчивается на "ов" SELECT ID,LastName FROM Employees WHERE LastName LIKE '%ре%' -- у кого фамилия содержит сочетание "ре"
Рассмотрим примеры с символом «_»:
SELECT ID,LastName FROM Employees WHERE LastName LIKE '_етров' -- у кого фамилия состоит из любого первого символа и последующих букв "етров" SELECT ID,LastName FROM Employees WHERE LastName LIKE '____ов' -- у кого фамилия состоит из четырех любых символов и последующих букв "ов"
При помощи ESCAPE можно задать отменяющий символ, который отменяет проверяющее действие специальных символов «_» и «%». Данное предложение используется, когда в строке нужно непосредственно проверить наличие знака процента или знака подчеркивания.
Для демонстрации ESCAPE давайте занесем в одну запись мусор:
UPDATE Employees SET FirstName='Это_мусор, содержащий %' WHERE посмотрим, что вернут следующие запросы:
SELECT * FROM Employees WHERE FirstName LIKE '%!%%' ESCAPE '!' -- строка содержит знак "%" SELECT * FROM Employees WHERE FirstName LIKE '%!_%' ESCAPE '!' -- строка содержит знак "_"
В случае, если требуется проверить строку на полное совпадение, то вместо LIKE лучше использовать просто знак «=»:
SELECT * FROM Employees WHERE FirstName='Петр'
На заметку.
В MS SQL в шаблоне оператора LIKE так же можно задать поиск по регулярным выражениям, почитайте о нем в интернете, в том случае, если вам станет недостаточно стандартных возможностей данного оператора.
В ORACLE для поиска по регулярным выражениям применяется функция REGEXP_LIKE.
Немного о строках
В случае проверки строки на наличие Unicode символов, нужно будет ставить перед кавычками символ N, т.е. N'…'. Но так как у нас в таблице все символьные поля в формате Unicode (тип nvarchar), то для этих полей можно всегда использовать такой формат. Пример:
SELECT ID,Name FROM Employees WHERE Name LIKE N'Пет%' SELECT ID,LastName FROM Employees WHERE LastName=N'Петров'
Если делать правильно, при сравнении с полем типа varchar (ASCII) нужно стараться использовать проверки с использованием '…', а при сравнении поля с типом nvarchar (Unicode) нужно стараться использовать проверки с использованием N'…'. Это делается для того, чтобы избежать в процессе выполнения запроса неявных преобразований типов. То же самое правило используем при вставке (INSERT) значений в поле или их обновлении (UPDATE).
При сравнении строк стоит учесть момент, что в зависимости от настройки БД (collation), сравнение строк может быть, как регистро-независимым (когда 'Петров'='ПЕТРОВ'), так и регистро-зависимым (когда 'Петров'<>'ПЕТРОВ').
В случае регистро-зависимой настройки, если требуется сделать поиск без учета регистра, то можно, например, сделать предварительное преобразование правого и левого выражения в один регистр – верхний или нижний:
SELECT ID,Name FROM Employees WHERE UPPER(Name) LIKE UPPER(N'Пет%') -- или LOWER(Name) LIKE LOWER(N'Пет%') SELECT ID,LastName FROM Employees WHERE UPPER(LastName)=UPPER(N'Петров') -- или LOWER(LastName)=LOWER(N'Петров')
Немного о датах
При проверке на дату, вы можете использовать, как и со строками одинарные кавычки '…'.
Вне зависимости от региональных настроек в MS SQL можно использовать следующий синтаксис дат 'YYYYMMDD' (год, месяц, день слитно без пробелов). Такой формат даты MS SQL поймет всегда:
SELECT ID,Name,Birthday FROM Employees WHERE Birthday BETWEEN '19800101' AND '19891231' -- сотрудники 80-х годов ORDER BY Birthday
В некоторых случаях, дату удобнее задавать при помощи функции DATEFROMPARTS:
SELECT ID,Name,Birthday FROM Employees WHERE Birthday BETWEEN DATEFROMPARTS(1980,1,1) AND DATEFROMPARTS(1989,12,31) ORDER BY Birthday
Так же есть аналогичная функция DATETIMEFROMPARTS, которая служит для задания Даты и Времени (для типа datetime).
Еще вы можете использовать функцию CONVERT, если требуется преобразовать строку в значение типа date или datetime:
SELECT CONVERT(date,'12.03.2015',104), CONVERT(datetime,'2014-11-30 17:20:15',120)
Значения 104 и 120, указывают какой формат даты используется в строке. Описание всех допустимых форматов вы можете найти в библиотеке MSDN задав в поиске «MS SQL CONVERT».
Функций для работы с датами в MS SQL очень много, ищите «ms sql функции для работы с датами».
Примечание. Во всех диалектах языка SQL свой набор функций по работе с датами и применяется свой подход по работе с ними.
Немного о числах и их преобразованиях
Информация этого раздела наверно больше будет полезна ИТ-специалистам. Если вы таковым не являетесь, а ваша цель просто научится писать запросы для получения из БД необходимой вам информации, то такие тонкости вам возможно и не понадобятся, но в любом случае можете бегло пройтись по тексту и взять что-то на заметку, т.к. если вы взялись за изучение SQL, то вы уже приобщаетесь к ИТ.
В отличие от функции преобразования CAST, в функции CONVERT можно задать третий параметр, который отвечает за стиль преобразования (формат). Для разных типов данных может использоваться свой набор стилей, которые могут повлиять на возвращаемый результат. Использование стилей мы уже затрагивали при рассмотрении преобразования строки функцией CONVERT в типы date и datetime.
Подробней про функции CAST, CONVERT и стили можно почитать в MSDN – «Функции CAST и CONVERT (Transact-SQL)»: msdn.microsoft.com/ru-ru/library/ms187928.aspx
Для упрощения примеров здесь будут использованы инструкции языка Transact-SQL – DECLARE и SET.
Конечно, в случае преобразования целого числа в вещественное (которое я привел вначале данного урока, в целях демонстрации разницы между целочисленным и вещественным делением), знание нюансов преобразования не так критично, т.к. там мы делали преобразование целого числа в вещественное (диапазон которого намного больше диапазона целых):
DECLARE @min_int int SET @min_int=-2147483648 DECLARE @max_int int SET @max_int=2147483647 SELECT -- (-2147483648) @min_int,CAST(@min_int AS float),CONVERT(float,@min_int), -- 2147483647 @max_int,CAST(@max_int AS float),CONVERT(float,@max_int), -- numeric(16,6) @min_int/1., -- (-2147483648.000000) @max_int/1. -- 2147483647.000000
Возможно не стоило указывать способ неявного преобразования, получаемого делением на (1.), т.к. желательно стараться делать явные преобразования, для большего контроля типа получаемого результата. Хотя, в случае, если мы хотим получить результат типа numeric, с указанным количеством цифр после запятой, то мы можем в MS SQL применить трюк с умножением целого значения на (1., 1.0, 1.00 и т.д):
DECLARE @int int SET @int=123 SELECT @int*1., -- numeric(12, 0) - 0 знаков после запятой @int*1.0, -- numeric(13, 1) - 1 знак @int*1.00, -- numeric(14, 2) - 2 знака -- хотя порой лучше сделать явное преобразование CAST(@int AS numeric(20, 0)), -- 123 CAST(@int AS numeric(20, 1)), -- 123.0 CAST(@int AS numeric(20, 2)) -- 123.00
В некоторых случаях детали преобразования могут быть действительно важны, т.к. они влияют на правильность полученного результата, например, в случае, когда делается преобразование числового значения в строку (varchar). Рассмотрим примеры по преобразованию значений типа money и float в varchar:
-- поведение при преобразовании money в varchar DECLARE @money money SET @money = 1025.123456789 -- произойдет неявное преобразование в 1025.1235, т.к. тип money хранит только 4 цифры после запятой SELECT @money, -- 1025.1235 -- по умолчанию CAST и CONVERT ведут себя одинаково (т.е. грубо говоря применяется стиль 0) CAST(@money as varchar(20)), -- 1025.12 CONVERT(varchar(20), @money), -- 1025.12 CONVERT(varchar(20), @money, 0), -- 1025.12 (стиль 0 - без разделителя тысячных и 2 цифры после запятой (формат по умолчанию)) CONVERT(varchar(20), @money, 1), -- 1,025.12 (стиль 1 - используется разделитель тысячных и 2 цифры после запятой) CONVERT(varchar(20), @money, 2) -- 1025.1235 (стиль 2 - без разделителя и 4 цифры после запятой)
-- поведение при преобразовании float в varchar DECLARE @float1 float SET @float1 = 1025.123456789 DECLARE @float2 float SET @float2 = 1231025.123456789 SELECT @float1, -- 1025.123456789 @float2, -- 1231025.12345679 -- по умолчанию CAST и CONVERT ведут себя одинаково (т.е. грубо говоря применяется стиль 0) -- стиль 0 - Не более 6 разрядов. По необходимости используется экспоненциальное представление чисел -- при преобразовании в varchar здесь творятся действительно страшные вещи CAST(@float1 as varchar(20)), -- 1025.12 CONVERT(varchar(20), @float1), -- 1025.12 CONVERT(varchar(20), @float1, 0), -- 1025.12 CAST(@float2 as varchar(20)), -- 1.23103e+006 CONVERT(varchar(20), @float2), -- 1.23103e+006 CONVERT(varchar(20), @float2, 0), -- 1.23103e+006 -- стиль 1 - Всегда 8 разрядов. Всегда используется экспоненциальное представление чисел. -- этот стиль для float тоже не очень точен CONVERT(varchar(20), @float1, 1), -- 1.0251235e+003 CONVERT(varchar(20), @float2, 1), -- 1.2310251e+006 -- стиль 2 - Всегда 16 разрядов. Всегда используется экспоненциальное представление чисел. -- здесь с точностью уже получше CONVERT(varchar(30), @float1, 2), -- 1.025123456789000e+003 - OK CONVERT(varchar(30), @float2, 2) -- 1.231025123456789e+006 - OK
Как видно из примера, плавающие типы float, real в некоторых случаях действительно могут создать большую погрешность, особенно при перегонке в строку и обратно (такое может быть при разного рода интеграциях, когда данные, например, передаются в текстовых файлах из одной системы в другую).
Если нужно явно контролировать точность до определенного знака, более 4-х, то для хранения данных, порой лучше использовать тип decimal/numeric. Если хватает 4-х знаков, то можно использовать и тип money – он примерно соотвествует numeric(20,4).
-- decimal и numeric DECLARE @money money SET @money = 1025.123456789 -- 1025.1235 DECLARE @float1 float SET @float1 = 1025.123456789 DECLARE @float2 float SET @float2 = 1231025.123456789 DECLARE @numeric numeric(28,9) SET @numeric = 1025.123456789 SELECT CAST(@numeric as varchar(20)), -- 1025.12345679 CONVERT(varchar(20), @numeric), -- 1025.12345679 CAST(@money as numeric(28,9)), -- 1025.123500000 CAST(@float1 as numeric(28,9)), -- 1025.123456789 CAST(@float2 as numeric(28,9)) -- 1231025.123456789
Примечание.
С версии MS SQL 2008, можно использовать вместо конструкции:
DECLARE @money money SET @money = 1025.123456789
Более короткий синтаксис инициализации переменных:
DECLARE @money money = 1025.123456789
Заключение второй части
В этой части, я постарался вспомнить и отразить наиболее важные моменты, касающиеся базового синтаксиса. Базовая конструкция – это костяк, без которого нельзя приступать к изучению более сложных конструкций языка SQL.
Надеюсь, данный материал поможет людям, делающим первые шаги в изучении языка SQL.
Удачи в изучении и применении на практике данного языка.
- SQL
- Microsoft SQL Server
Какие опции могут быть использованы в операторах выборки данных sql
В официальном стандарте SQL/92 определяются три уровня языка: полный SQL, промежуточный SQL и вводный SQL. Основная идея состоит в том, что полный SQL является полным стандартом, промежуточный SQL - cтрогое подмножество полного SQL, а вводный SQL - строгое подмножество промежуточного SQL. Разработчики стандарта стремились позволить поэтапную реализацию с продвижением от поддержки вводного SQL через поддержку промежуточного SQL к поддержке полного SQL (как мы отмечали выше, до сих пор ни одна компания-производитель реляционных СУБД не объявила, что в ее продукте целиком поддерживается полный SQL). В п. 3.14.1 перечисляются основные свойства полного SQL, которые отсутствуют в промежуточном SQL, а в п. 3.14.2 указываются основные черты, которые в дополнение к этому отсутствуют во вводном SQL.
Язык SQL, определенный стандартом, называется "соответствующим языком SQL". Реализация называется "соответствующей реализацией SQL", если в ней обрабатывается соответствующий язык SQL в соответствии со спецификациями стандарта. Таким образом, соответствующая реализация SQL должна поддерживать соответствующий язык SQL по крайней мере на вводном уровне. Такая реализация должна также поддерживать по крайней мере один "стиль связывания" (модуль, встроенный SQL или прямой SQL), и в случае модуля или встроенного SQL, по крайней мере один из официальных основных языков (Ada, Си, COBOL, FORTRAN, MUMPS, Pascal или PL/1). Более того, в такой реализации должны быть также документированы определения для всех свойств соответствующего языка SQL, которые установлены стандартом как определяемые в реализации.
- обеспечение поддержки дополнительных свойств или опций, не специфицированных в стандарте;
- обеспечение опций для обработки соответствующего языка SQL несоответствующим образом;
- обеспечение опций для обработки не соответствующего языка SQL.
С другой стороны, от реализации, которая провозглашается соответствующей стандарту на любом уровне (за исключением, возможно, вводного уровня), требуется поддержка опции SQLFlagger для помечания элементов, которые не соответствуют указанному уровню (см.п.3.14.3).
В стандарте SQL/92 многие аспекты явно установлены как "зависимые от реализации", т.е. неопределенные; на самом деле, некоторые аспекты кажутся (возможно, неумышленно) неопределенными неявно. Даже если две реализации могут законно быть провозглашены соответствующими стандарту, это не дает абсолютной гарантии переносимости приложений.
В стандарте специально не определяется метод компиляции прикладных программ со встроенным SQL или иной способ их обработки.
3.14.1. Промежуточный SQL
В этом разделе приводятся некоторые основные различия между полным SQL и промежуточным SQL. Заметим, что мы не претендуем на полноту этого списка; цель состоит только в том, чтобы предоставить общую идею этих различий. Для абсолютно точной информации следует обращаться к самому стандарту (соответствующая информация разбросана по всему документу).
- идентификаторы, в которых последний символ есть подчеркивание;
- явные имена каталогов;
- операторы SETCATALOG, SETSCHEMA, SETNAMES;
- операторы CONNECT, SETCONNECTION, DISCONNECT;
- все конструкции, связанные с битовыми строками;
- все, что служит для трансляции, преобразования и (явного) сравнения;
- явная спецификация точности для данных типа TIME и TIMESTAMP;
- значения SECOND для данных типа DATETIME или INTERVAL с более чем микросекундной точностью;
- функции POSITION, UPPER, LOWER;
- UNIONJOIN;
- возможность указания CORRESPONDING для операторов UNION, EXCEPT и INTERSECT;
- предикаты IS[NOT]TRUE, IS[NOT]FALSE, IS[NOT]UNKNOWN;
- условия MATCH в определениях внешнего ключа;
- утверждения целостности общего вида (операторы CREATE и DROPASSERTION);
- проверочные ограничения базовой таблицы, которые ссылаются на другие таблицы;
- определения действий ONUPDATE в определениях внешнего ключа;
- откладываемые ограничения и оператор SETCONSTRAINTS;
- "глобальные" и "объявляемые локальные" временные таблицы;
- привилегии INSERT уровня столбцов;
- LOCAL или CASCADED в опциях проверки (хотя CASCADED должно поддерживаться неявно);
- оператор ALTERDOMAIN;
- INSENSITIVE курсоры;
- спецификация "TABLE таблица" внутри табличного выражения;
- параметры или переменные основной программы как имена области дескрипторов SQL;
- все, что служит для работы с генерируемыми пользователями именами операторов;
- все, что служит для работы с генерируемыми пользователями именами курсоров;
- операторы DEALLOCATEPREPARE, DESCRIBEINPUT и возможность наличия раздела INTO в операторе EXECUTE.
- ссылка на таблицу не может быть табличным выражением в круглых скобках;
- оператор DISTINCT допускается внутри табличного выражения не более одного раза на каждом уровне вложенности;
- список сравниваемых значений в правой части условия IN не должен включать более сложные элементы, чем литерал, ссылка на столбец или встроенная функция без параметров;
- если при ссылке на агрегатную функцию указывается DISTINCT, аргумент должен представлять простую ссылку на столбец;
- привилегия REFERENCES не требуется для столбцов, используемых в проверочном ограничении (это на самом деле противоположность ограничению; из этого следует, что промежуточный SQL не является вполне строгим подмножеством полного SQL);
- наличие в определении курсора ORDERBY влечет неявно свойство FORREADONLY;
- операторы INSERT, UPDATE и DELETE не могут включать раздел WHERE (ни прямо в случае поисковой операции, ни через определение курсора в случае позиционной операции), ссылающийся на таблицу, которая является целью этого оператора;
- на некоторые таблицы информационной схемы (например, TRANSLATIONS) нельзя ссылаться.
3.14.2. Вводный SQL
- идентификаторы длиннее, чем из 18 символов;
- малые буквы в идентификаторах;
- оператор SETSESSIONAUTHORIZATION;
- символьные строки переменного размера;
- определяемые в реализации наборы символов, включая строки национальных символов;
- все конструкции, связанные с datetime и interval;
- все, что связано с доменами;
- явные именованные ограничения;
- константы CURRENT_USER, SESSION_USER, SYSTEM_USER (однако USER поддерживается);
- CHARACTER_LENGHT, OCTET_LENGHT;
- функции SUBSTRING, TRIM, EXTRACT;
- операция конкатенации символьных строк;
- выражения с переключателем;
- оператор явного преобразования типов (CAST);
- раздел DEFAULT в операторах INSERT и UPDATE;
- явный оператор JOIN;
- операции EXCEPT и INTERSECT;
- элементы выборки в форме "R.*";
- условие UNIQUE;
- оператор DROPSCHEMA;
- оператор DROPTABLE;
- задание действия ONDELETE в определениях внешнего ключа;
- оператор ALTERTABLE;
- оператор DROPVIEW;
- оператор REVOKE;
- оператор SETTRANSACTION;
- динамический SQL;
- прокручиваемые курсоры;
- раздел FORUPDATE в определении курсора;
- преобразования между точными и приблизительными численными значениями при присваивании;
- информационная схема;
- оператор GETDIAGNOSTICS.
- конструктор строки должен включать в точности один компонент, за исключением специального случая, когда конструктор строки является компонентом конструктора таблицы (в этом случае он должен быть единственным таким компонентом), и случая, когда конструктор строки используется для определения источника в операторе INSERT;
- табличное выражение в круглых скобках не может включать UNION;
- если в разделе FROM выражения выборки присутствует ссылка на представление, определение которого включает разделы GROUPBY или HAVING, то
- одиночный оператор SELECT не может включать разделы GROUPBY и ORDERBY и не может ссылаться на представление, в определении которого использованы разделы GROUPBY и ORDERBY;
- если какое-либо значение в условии сравнения представляет собой выражение выборки в круглых скобках, это выражение выборки не может содержать разделы GROUPBY и HAVING и не должно ссылаться на представление, в определении которого использованы разделы GROUPBY и ORDERBY;
- для UNION типы данных соответствующих столбцов должны быть в точности одними и теми же (и NOTNULL должно прилагаться ко всем или ни к кому);
- в условии LIKE первый операнд должен быть ссылкой на столбец, и "pattern" и "escape" должны быть литералами, параметрами или переменными основной программы;
- в проверке на неопределенное значение операнд должен быть ссылкой на столбец;
- оператор CREATESCHEMA должен включать раздел AUTHIRIZATION и не должен включать имени схемы;
- определение модуля должно включать раздел AUTHIRIZATION и не должен включать раздела SCHEMA;
- каждый столбец, упомянутый в определении возможного ключа, должен быть явно определен как NOTNULL;
- ключевое слово TABLE не должно появляться в операторе GRANT;
- операторы COMMIT и ROLLBACK должны включать паразитное слово WORK.
3.14.3. SQLFlagger
Как упоминалось в начале этого раздела, от реализации, которая провозглашается соответствующей стандарту на любом уровне, требуется обеспечение SQLFlagger. Назначение SQLFlagger состоит в помечании любой специфичной в реализации конструкции SQL, т.е. конструкции, которая распознается и поддерживается реализацией, но не соответствует уровню стандарта, соответствие которому провозглашено. Целью является идентификация свойств SQL, которые могли бы породить разные результаты в разных средах, т.е. свойств, которые потребовали бы внимания, если бы приложения или запросы на SQL перемещались из одной среды в другую. Такие соображения уместны, например, если приложение разрабатывается на рабочей станции, а выполняется на mainframe.
Реализация, которая объявляется соответствующей полному SQL, должна обеспечивать SQLFlagger, который поддерживает следующие опции: - EntrySQLFlagging (т.е. опцию для помечания конструкций SQL, не соответствующих вводному SQL); - IntermediateSQLFlagging (т.е. опцию для помечания конструкций SQL, не соответствующих промежуточному SQL); - FullSQLFlagging (т.е. опцию для помечания конструкций SQL, не соответствующих полному SQL).
Должны также поддерживаться опции проверки "только синтаксиса" и "с привлечением каталога". Проверка "только синтаксиса" означает, что от реализации требуется выполнение только тех проверок, которые возможны без доступа к схеме определений (DefinitionSchema). Проверка "с привлечением каталога" означает, что от реализации дополнительно требуется выполнять те проверки (за исключением проверок привилегий), которые возможны, если схема определений доступна. В обоих случаях проверка замышляется только статической (т.е. "времени компиляции"); не требуется проверка элементов, которые невозможно определить до времени выполнения.
Реализация, объявленная соответствующей промежуточному SQL, должна обеспечивать SQLFlagging, который поддерживает вводный SQL и промежуточный SQL, и должна поддерживать как минимум проверку "только синтаксиса".
Реализация, объявленная соответствующей вводному SQL, должна обеспечивать SQLFlagging, который поддерживает как минимум проверку "только синтаксиса" вводного SQL.
