- •Глава 1. Историческая справка. 10
- •Глава 2. Пример бд «Ресторан». 20
- •Глава 3. Выборка данных. 25
- •Глава 4. Подзапросы и производные таблицы 65
- •Глава 5. Функции ранжирования. 86
- •Глава 1. Историческая справка.
- •Стандарты.
- •1.2. Структура sql.
- •Глава 2. Пример бд «Ресторан».
- •2.1. Описание.
- •2.2. Диаграмма.
- •2.3. Данные в таблицах.
- •Глава 3. Выборка данных.
- •3.1. Оператор select. Синтаксис.
- •3.2. Примеры запросов с использованием единственной таблицы.
- •3.2.1. Выборка без использования фразы where.
- •3.2.1.1. Простейшие примеры.
- •3.2.1.2. Исключение дубликатов (distinct).
- •3.2.1.3. Выборка вычисляемых значений.
- •3.2.2. Выборка c использованием фразы where.
- •3.2.2.1. Использование операторов сравнения.
- •3.2.2.2. Сравнение с null.
- •3.2.2.3. Использование between.
- •3.2.2.4. Использование in (not in).
- •3.2.2.5. Использование like.
- •3.2.2.6. Выборка с упорядочением.
- •3.2.3. Использование агрегатных функций для подведения итогов.
- •3.2.3.1. Агрегатные функции без использования фразы group by.
- •3.2.3.2. Фраза group by.
- •3.2.3.3. Раздел having.
- •3.3. Примеры запросов с использованием нескольких таблиц.
- •3.3.1. Соединения «с условием where».
- •3.3.2. Соединение таблиц с дополнительными условиями.
- •3.3.2.1. Соединение таблицы со своей копией.
- •3.4. Соединения нескольких таблиц, используя join.
- •3.4.1. Внутреннее соединение.
- •3.4.2. Внешнее соединение.
- •3.4.2.1. Левое внешнее соединение.
- •3.4.2.2. Правое внешнее соединение.
- •3.4.2.3. Полное внешнее соединение.
- •3.4.2.4. Перекрёстное соединение.
- •3.4.3. Реальные примеры соединений.
- •Isnull(cast(n.Количество as varchar), ’нет’) as на_складе
- •Isnull(cast(n.Количество as varchar),’нет’) as на_складе
- •Глава 4. Подзапросы и производные таблицы
- •4.1.Производная таблица.
- •4.2. Вложенные подзапросы
- •4.2.1 Простые вложенные подзапросы
- •4.2.2. Использование одной и той же таблицы во внешнем и вложенном подзапросе
- •4.2.3. Использование агрегатных функций в подзапросах.
- •4.2.4. Подзапросы в предложении having.
- •4.3. Соотнесенные подзапросы.
- •4.4 Использование оператора exists.
- •4.5. Использование операторов any и all.
- •4.6. Объединение запросов union.
- •4.6.1. Union и устранение дубликатов.
- •4.6.2. Использование строк и выражений с union.
- •4.6.3. Использование union с order by.
- •4.6.4. Реализация внешнего полного соединения через запросы с union.
- •Глава 5. Функции ранжирования.
- •5.1. Функция row_number.
- •5.2. Функции rank() и dense_rank()
- •Глава 6. ИспользованиеPivoTиUnpivot.
- •In ([Овощи], [Мясо], [Рыба], [Молоко], [Яйца], [Крупа], [Фрукты], [Кофе])
- •In ( [первый сведенный столбец], [второй сведенный столбец],
- •In ([Овощи], [Мясо], [Рыба], [Молоко], [Яйца], [Крупа], [Фрукты], [Кофе])
- •Insert into Продукты (id_Продукта, Продукт, Белки)
- •Values (18, 'Горох', 180 );
- •Insert into Продукты (Продукт, Белки, Жиры)
- •7.4. Оператор update.
- •Глава 8. Представление.
- •Insert into Список_блюд values (36, 'Рагу', 3, 20);
- •Глава 9. Создание, изменение и удаление таблиц.
- •9.1. Оператор create table
- •6. Ограничение identity (автоинкрементное поле).
- •9.2. Изменение таблицы после того как она была создана (alter table).
- •9.3. Удаление таблицы (drop table).
- •9.4. Операторы создания и удаления индексов.
- •9.5. Понятие домена
- •Глава 10. Обобщенные табличные выражения (сте).
- •Представления, производные таблицы и выражения cte.
- •Рекурсивные запросы.
- •Id_father integer foreign key references Tree (id),
- •Values (1, null, 'all'), (2, 1, 'sea'), (3, 1, 'earth'),
- •Деревья без рекурсии.
- •Пример использования сте для решения задачи Коммивояжера.
- •Insert into tur select to_town, from_town, miles from tur;
- •Глава 11. Этапы выполнения командыSql.
- •11.1. Оптимизация запросов.
- •Приложение 1. Реализация реляционной алгебры средствами оператора select (Реляционная полнота sql).
- •Intersect
- •Рекомендуемая литература
Деревья без рекурсии.
В программировании рекурсию всегда можно избежать, если использовать стек. В нашем случае так же можно обойтись без рекурсии, поместив стек внутрь таблицы. Для этого добавим в таблицу два новых столбца.
ALTER TABLE Tree
ADD RIGHT_BOUND INTEGER
ALTER TABLE Tree
ADD LEFT_BOUND INTEGER
Заполним эти новые столбцы следующими числами:
UPDATE Tree SET LEFT_BOUND = 1 , RIGHT_BOUND = 26 WHERE ID = 1
UPDATE Tree SET LEFT_BOUND = 2 , RIGHT_BOUND = 7 WHERE ID = 2
UPDATE Tree SET LEFT_BOUND = 8 , RIGHT_BOUND = 19 WHERE ID = 3
UPDATE Tree SET LEFT_BOUND = 20, RIGHT_BOUND = 25 WHERE ID = 4
UPDATE Tree SET LEFT_BOUND = 3 , RIGHT_BOUND = 4 WHERE ID = 5
UPDATE Tree SET LEFT_BOUND = 5 , RIGHT_BOUND = 6 WHERE ID = 6
UPDATE Tree SET LEFT_BOUND = 9 , RIGHT_BOUND = 10 WHERE ID = 7
UPDATE Tree SET LEFT_BOUND = 11, RIGHT_BOUND = 16 WHERE ID = 8
UPDATE Tree SET LEFT_BOUND = 17, RIGHT_BOUND = 18 WHERE ID = 9
UPDATE Tree SET LEFT_BOUND = 21, RIGHT_BOUND = 22
WHERE ID = 10
UPDATE Tree SET LEFT_BOUND = 23, RIGHT_BOUND = 24
WHERE ID = 11
UPDATE Tree SET LEFT_BOUND = 12, RIGHT_BOUND = 13
WHERE ID = 12
UPDATE Tree SET LEFT_BOUND = 14, RIGHT_BOUND = 15
WHERE ID = 13
Фактически мы реализовали стек, нумеруя строки данных. Вот поясняющая картинка:
ALL |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | |||
SEA |
| |||
SUBMARINE |
| |||
| ||||
BOAT |
| |||
| ||||
| ||||
EARTH |
| |||
CAR |
| |||
| ||||
TWO WHEELES |
| |||
MOTORCYCLE |
| |||
| ||||
BYCYCLE |
| |||
| ||||
| ||||
TRUCK |
| |||
| ||||
| ||||
AIR |
| |||
ROCKET |
| |||
| ||||
PLANE |
| |||
| ||||
|
Теперь, чтобы получить всех предков МОТОЦИКЛА, мы только берем границы МОТОЦИКЛА (MOTORCYCLE) - слева 12, а справа 13 - и помещаем их в предложение WHERE, выбирая данные, правая граница которых превышает 12, а левая меньше 13.
И вот запрос, дающий тот же самый результат, что и сложный иерархический рекурсивный запрос:
SELECT * FROM Tree WHERE RIGHT_BOUND > 12
AND LEFT_BOUND < 13;
Результат |
| |||
ID |
ID_FATHER |
NAME |
RIGHT_BOUND |
LEFT_BOUND |
1 |
NULL |
ALL |
26 |
1 |
3 |
1 |
EARTH |
19 |
8 |
8 |
3 |
TWO WHEELES |
16 |
11 |
12 |
8 |
MOTORCYCLE |
13 |
12 |
Такое представление деревьев хорошо известно в литературе по БД, особенно в трудах Джо Селко ("Деревья и иерархии" и т. д.).
Пример использования сте для решения задачи Коммивояжера.
Проблема состоит в том, чтобы проехать на машине от Парижа до Тулузы, используя сеть автострад.
385 420 470
375 335 305 320
240 205
Создадим таблицу и занесем данные: CREATE TABLE TUR
(FROM_TOWN VARCHAR(32),
FROM_TOWN |
TO_TOWN |
MILES |
PARIS |
NANTES |
385 |
PARIS |
CLERMONT-FERRAND |
420 |
PARIS |
LYON |
470 |
CLERMONT-FERRAND |
MONTPELLIER |
335 |
CLERMONT-FERRAND |
TOULOUSE |
375 |
LYON |
MONTPELLIER |
305 |
LYON |
MARSEILLE |
320 |
MONTPELLIER |
TOULOUSE |
240 |
MARSEILLE |
NICE |
205 |
MILES INTEGER);
Возьмем в качестве начала рекурсии “Париж” и построим СТЕ
WITH
Результат |
|
TO-TOWN | |
PARIS | |
NANTES | |
CLERMONT-FERRAND | |
LYON | |
MONTPELLIER | |
MARSEILLE | |
NICE | |
TOULOUSE | |
MONTPELLIER | |
TOULOUSE | |
TOULOUSE |
AS
( SELECT DISTINCT FROM_TOWN
FROM TUR
WHER FROM_TOWN= 'PARIS'
UNION ALL
SELECT TO_TOWN
FROM TUR INNER JOIN TUR_PARIS
ON TUR_PARIS.[TO-TOWN] = TUR.FROM_TOWN
)
SELECT * FROM TUR_PARIS;
Как видно из результата запроса, существует три способа добраться до Тулузы. Отфильтруем пункт назначения.
WITH
Результат |
|
TO-TOWN | |
TOULOUSE | |
TOULOUSE | |
TOULOUSE |
AS
( SELECT DISTINCT FROM_TOWN
FROM TUR
WHER FROM_TOWN= 'PARIS'
UNION ALL
SELECT TO_TOWN
FROM TUR INNER JOIN TUR_PARIS
ON TUR_PARIS.[TO-TOWN] = TUR.FROM_TOWN
)
SELECT * FROM TUR_PARIS
WHERE [TO-TOWN] = 'TOULOUSE' ;
Мы можем уточнить этот запрос, подсчитав число шагов по каждому направлению, расстояния по различным направлениям и выведя списки городов, которые можно посетить, двигаясь по этим направлениям:
WITH
TUR_PARIS([TO-TOWN], STEPS, DISTANSE, WAY)
AS
( SELECT DISTINCT FROM_TOWN, 0, 0
,cast('PARIS' as VarChar(MAX))
FROM TUR
WHERE FROM_TOWN= 'PARIS'
UNION ALL
SELECT TO_TOWN, T_P.STEPS+1,
T_P. DISTANSE + T.MILES, T_P.WAY+ ' '+T.TO_TOWN
FROM TUR T INNER JOIN TUR_PARIS T_P
ON T_P.[TO-TOWN] = T.FROM_TOWN
)
SELECT * FROM TUR_PARIS
WHERE [TO-TOWN] = 'TOULOUSE';
Результат |
| ||
TO-TOWN |
STEPS |
DISTANSE |
WAY |
TOULOUSE |
3 |
1015 |
PARIS LYON MONTPELLIER TOULOUSE |
TOULOUSE |
2 |
795 |
PARIS CLERMONT-FERRAND TOULOUSE |
TOULOUSE |
3 |
995 |
PARIS CLERMONT-FERRAND MONTPELLIER TOULOUSE |
Теперь мы сможем написать рекурсивный запрос решение очень сложной задачи, названной задачей коммивояжера (одна из действительных проблем исследования, на которых Edsger Wybe Dijkstra нашел первый эффективный алгоритм и получил премию Turing Award в 1972):
WITH
TUR_PARIS([TO-TOWN], STEPS, DISTANSE, WAY)
AS
( SELECT DISTINCT FROM_TOWN, 0, 0
,cast('PARIS' as VarChar(MAX))
FROM TUR
WHERE FROM_TOWN= 'PARIS'
UNION ALL
SELECT TO_TOWN, T_P.STEPS+1,
T_P. DISTANSE + T.MILES, T_P.WAY+ ' '+T.TO_TOWN
FROM TUR T INNER JOIN TUR_PARIS T_P
ON T_P.[TO-TOWN] = T.FROM_TOWN
)
SELECT TOP 1 * FROM TUR_PARIS
WHERE [TO-TOWN] = 'TOULOUSE'
ORDER BY DISTANSE;
Результат |
| ||
TO-TOWN |
STEPS |
DISTANSE |
WAY |
TOULOUSE |
2 |
795 |
PARIS CLERMONT-FERRAND TOULOUSE |
Следует заметить, что TOP n - нестандартная для SQL конструкция. Перепишем запрос без её использования:
WITH
TUR_PARIS([TO-TOWN], STEPS, DISTANSE, WAY)
AS
( SELECT DISTINCT FROM_TOWN, 0, 0
,cast('PARIS' as VarChar(MAX))
FROM TUR
WHERE FROM_TOWN= 'PARIS'
UNION ALL
SELECT TO_TOWN, T_P.STEPS+1,
T_P. DISTANSE + T.MILES, T_P.WAY+ ' '+T.TO_TOWN
FROM TUR T INNER JOIN TUR_PARIS T_P
ON T_P.[TO-TOWN] = T.FROM_TOWN
),
MIN_DISTANSE( DISTANSE)
AS
(SELECT MIN( DISTANSE)
FROM TUR_PARIS
WHERE [TO-TOWN] = 'TOULOUSE'
)
SELECT TUR_PARIS.*
FROM TUR_PARIS JOIN MIN_DISTANSE ON MIN_DISTANSE.DISTANSE=TUR_PARIS.DISTANSE
WHERE [TO-TOWN] = 'TOULOUSE';
В приведенной обработке сети имеется только одно ограничение — мы построили маршруты в одном направлении. Мы можем проехать из Парижа до Лиона, но нам не позволено совершить поездку из Лиона до Парижа. Для этого мы должны добавить обратные пути в таблицу.
Это может быть сделано с помощью очень простого запроса: