SQL и Базы данных

Вопросы и ответы для подготовки к собеседованиям

Вопросы: (показать все)

Реляционная база данных

Реляционная база данных - база данных, основанная на реляционной модели данных.

Реляционной называется база данных, в которой все данные, доступные пользователю, организованы в виде таблиц, а все oпepaции базы данных выполняются над этими таблицами.

Столбцов не может быть 0, строк может быть 0.

В правильно построенной реляционной базе данных в каждой таблице есть столбец (или комбинация столбцов), для которого значения во всех строках различны. Этот столбец (столбцы) называется первичным ключом (primary key).

Таблица, в которой все строки отличаются друг от друга, в математических терминах называется отношением (relation).

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

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

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

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

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

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

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

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

  7. Правило высокоуровневого добавления, обновления и удаления.
    Операции вставки, обновления и удаления должны применяться к отношению в целом.

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

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

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

  11. Правило независимости распространения.
    Реляционная база данных должна быть переносима не только в пределах системы, но и по сети.

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

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

Что такое SQL

SQL (Structured Query Language) - язык структурированных запросов.

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

Что умеет SQL:

  • Определение данных.
    SQL позволяет пользователю определить структуру и организацию хранимых данных и взаимоотношения между элементами сохраненных данных.

  • Выборка данных.
    SQL дает пользователю или приложению возможность извлекать из базы содержащиеся в ней данные и пользоваться ими.

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

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

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

  • Целостность данных.
    SQL позволяет обеспечить целостность базы данных, защищая ее от разрушения из-за несогласованных изменений или отказа системы.

Сердцем СУБД является механизм базы данных (database engine, часто называемый просто движком). Oн отвечает за структурирование данных, сохранение и получение их из базы данных. Он принимает SQL-запросы от других компонентов СУБД, от пользовательских приложений и даже от других вычислительных систем.

Язык SQL относится к непроцедурным (nonprocedural) языкам — он лишь описывает нужные компоненты (например, таблицы) и желаемые результаты, не указывая, как именно эти результаты должны быть получены. Каждая реализация SQL является надстройкой над процессором базы данных (database engine), который интерпретирует операторы SQL и определяет порядок обращения к структурам БД для корректного и эффективного формирования желаемого результата.

Несколько свойства SQL, обеспечивающие такой его успех в течение последних десятилетий...

  • Независимость от конкретных СУБД
  • Межплатформенная переносимость
  • Наличие стандартов
  • Поддержка со стороны компании IВМ
  • Поддержка со стороны компании Microsoft
  • Построение на реляционной модели
  • Высокоуровневая структура, напоминающая естественный язык
  • Возможность выполнения специальных интерактивных запросов
  • Обеспечение программного доступа к базам данных
  • Возможность различного представления данных
  • Полноценность в качестве языка, предназначенного для работы с базами данных
  • Возможность динамического определения данных
  • Поддержка архитектуры клиент/ сервер
  • Поддержка приложений уровня предприятия
  • Расширяемость и поддержка объектно-ориентированных технологий
  • Возможность доступа к данным в Интернете
  • Интеграция с языком java (протокол JDВC)
  • Поддержка открытого кода
  • Промышленная инфраструктура

Итого:

сайтик про SQL

Команды SQL

Группы команд SQL

DLL команды

(раскрыть все)

  • CREATE
    Создает объект БД (базу, таблицу, представление, пользователя и т.д.).

    CREATE DATABASE Test

  • ALTER
    Изменяет объект.

    ALTER TABLE ...

  • DROP
    Удаляет объект.

    DROP TABLE ...

  • TRUNCATE
    "Удаление" всех записей в таблице.

    TRUNCATE TABLE ...

    Особенности

    • Не срабатывают триггеры, в частности, триггер удаления
    • Удаляет все строки в таблице, не записывая при этом удаление отдельных строк данных в журнал транзакций
    • Сбрасывает счетчик идентификаторов до начального значения
    • Чтобы использовать, необходимы права на изменение таблицы
    • Не используется когда в таблице есть внешние ключи надо использовать DELETE

Команды, что бы проверить выполнение

SHOW DATABASES
Посмотеть созданые базы.

SHOW TABLES
Кроме пользовательских таблиц покажет также и служебные таблицы.

DESCRIBE table_name
Получение информации о столбцах


DCL команды

  • GRANT
    Предоставляет пользователю (группе) разрешения на определенные операции с объектом.

    Предоставление права чтения таблицы students пользователю.
    GRANT SELECT ON table_name TO user_name;

  • REVOKE
    Отзывает ранее выданные разрешения.

    Отменить запрет.
    REVOKE SELECT ON table_name FROM user_name;

  • DENY
    Задает запрет, имеющий приоритет над разрешением

    Запрет права выборки из таблицы пользователя.
    DENY SELECT ON table_name TO user_name;


TCL команды

  • COMMIT
    Применяется для завершения транзакции и сохранения изменений в базе данных.

  • ROLLBACK
    Откатывает все изменения, сделанные в контексте текущей транзакции.

  • SAVEPOINT
    Создаёт точку к которой группа транзакций может откатиться, разбивает транзакцию на более мелкие.

  • SET TRANSACTION
    Применяется для установки параметров доступа к данным в текущей транзакции.

    Транзакция предназначена только для чтения:
    SET TRANSACTION READ ONLY

Команды управление транзакциями используются только для DML команд: INSERT, UPDATE, DELETE


DML команды

(раскрыть все)

  • SELECT
    Выбирает данные, удовлетворяющие заданным условиям.

    Схема запроса

    SELECT [DISTINCT | ALL] поля_таблиц
    FROM список_таблиц
    [WHERE условия_на_ограничения_строк]
    [GROUP BY условия_группировки]
    [HAVING условия_на_ограничения_строк_после_группировки по агрегатным функциям]
    [ORDER BY порядок_сортировки [ASC | DESC]]
    [LIMIT ограничение_количества_записей]
  • INSERT
    Добавляет новые данные.

    Схема запроса

    INSERT INTO имя_таблицы [(поле_таблицы, ...)]
    VALUES (значение_поля_таблицы, ...)
    | SELECT поле_таблицы, ... FROM имя_таблицы ...
  • UPDATE
    Изменяет существующие данные.

  • DELETE
    Удаляет данные.


Типы данных

Строковые

Типы данных SQL Описание
CHAR(size) Строки фиксированной длиной (могут содержать буквы, цифры и специальные символы).
Фиксированный размер указан в скобках.
Можно записать до 255 символов
VARCHAR(size) Может хранить не более 255 символов.
TINYTEXT Может хранить не более 255 символов.
TEXT Может хранить не более 65 535 символов.
BLOB Может хранить не более 65 535 символов.
MEDIUMTEXT Может хранить не более 16 777 215 символов.
MEDIUMBLOB Может хранить не более 16 777 215 символов.
LONGTEXT Может хранить не более 4 294 967 295 символов.
LONGBLOB Может хранить не более 4 294 967 295 символов.
ENUM(x,y,z,etc.) Позволяет вводить список допустимых значений.
Может содержать до 65 535.
Если при вставке значения не будет присутствовать в списке ENUM, то мы получим пустое значение.
Ввести возможные значения можно в таком формате: ENUM ( 'X', 'Y', 'Z')
SET Как ENUM, но может содержать до 64 значений.

С плавающей точкой (дробные числа) и целые числа

Типы данных SQL Описание
TINYINT(size) Может хранить числа от -128 до 127
SMALLINT(size) Диапазон от -32 768 до 32 767
MEDIUMINT(size) Диапазон от -8 388 608 до 8 388 607
INT(size) Диапазон от -2 147 483 648 до 2 147 483 647
BIGINT(size) Диапазон от -9 223 372 036 854 775  808 до 9 223 372 036 854 775 807
FLOAT(size,d) Число с плавающей точкой небольшой точности.
DOUBLE(size,d) Число с плавающей точкой двойной точности.
DECIMAL(size,d) Дробное число, хранящееся в виде строки.

Дата и время

Типы данных SQL Описание
DATE() Дата в формате ГГГГ-ММ-ДД
DATETIME() Дата и время в формате ГГГГ-ММ-ДД ЧЧ:ММ:СС
TIMESTAMP() Дата и время в формате timestamp.
Однако при получении значения поля оно отображается не в формате timestamp, а в виде ГГГГ-ММ-ДД ЧЧ:ММ:СС
TIME() Время в формате ЧЧ:ММ:СС
YEAR() Год в двух значной или в четырехзначном формате.

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


Нюансы работы с NULL в SQL

NULL - специальное значение (псевдозначение), которое может быть записано в поле таблицы базы данных. NULL соответствует понятию "пустое поле", то есть "поле, не содержащее никакого значения".

NULL означает отсутствие, неизвестность информации.

Значение NULL не является значением в полном смысле слова: по определению оно означает отсутствие значения и не принадлежит ни одному типу данных.

Поэтому NULL не равно ни логическому значению FALSE, ни пустой строке, ни 0.

При сравнении NULL с любым значением будет получен результат NULL, а не FALSE и не 0.

Более того, NULL не равно NULL!

Команды для проверки: IS NULL, IS NOT NULL

Хранимые процедуры и триггеры

Хранимые процедуры

Хранимая процедура - объект базы данных, представляющий собой набор SQL-инструкций, который компилируется один раз и хранится на сервере.

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

В хранимых процедурах могут выполняться стандартные операции с базами данных (как DDL, так и DML). Кроме того, в хранимых процедурах возможны циклы и ветвления, то есть в них могут использоваться инструкции управления процессом исполнения.


Триггеры

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

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

Схема запроса

CREATE TRIGGER имя_триггера
ON {имя_таблицы | имя_представления}
{AFTER | INSTEAD OF} [INSERT | UPDATE | DELETE]
AS выражения_sql

VIEW и временные таблицы

VIEW

View (Представление) - виртуальная таблица, представляющая данные одной или более таблиц альтернативным образом.

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

Представления значительно расширяют возможности управления данными.

Пример дать публичный доступ к некоторой (но не всей) информации в таблице:

CREATE VIEW Londonstaff
        AS SELECT *
        FROM Salespeople
        WHERE city = 'London';

Временные таблицы

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

Они могут быть локальными (только я могу работать) или глобальными (все).

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

Срок жизни временной таблицы – сеанс с БД

Транзакции

Транзакция

Транзакция - это воздействие на базу данных, переводящее её из одного целостного (консистентного) состояния в другое и выражаемое в изменении данных, хранящихся в базе данных.
Это N (N≥1) запросов к БД, которые выполнятся успешно все вместе или не выполнятся вовсе.


ACID

Основные свойства транзакции

  • Атомарность (atomicity)
    Гарантирует, что никакая транзакция не будет зафиксирована в системе частично. Будут либо выполнены все её подоперации, либо не выполнено ни одной.

  • Согласованность (consistency)
    Транзакция, достигающая своего нормального завершения (EOT — end of transaction, завершение транзакции) и, тем самым, фиксирующая свои результаты, сохраняет согласованность базы данных.

    Другими словами, каждая успешная транзакция по определению фиксирует только допустимые результаты.

  • Изолированность (isolation)
    Во время выполнения транзакции параллельные транзакции не должны оказывать влияние на ее результат.

  • Долговечность (durability)
    Независимо от проблем на нижних уровнях (к примеру, обесточивание системы или сбои в оборудовании) изменения, сделанные успешно завершённой транзакцией, должны остаться сохраненными после возвращения системы в работу. Если пользователь получил подтверждение от системы, что транзакция выполнена, он может быть уверен, что сделанные им изменения не будут отменены из-за какого-либо сбоя.


Аномалии

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

  1. Потерянное обновление (англ. lost update)
    При одновременном изменении одного блока данных разными транзакциями теряются все изменения, кроме последнего.

  2. Грязное чтение (англ. dirty read)
    Чтение данных, добавленных или изменённых транзакцией, которая впоследствии не подтвердится (откатится);

  3. Неповторяющееся чтение (англ. non-repeatable read)
    При повторном чтении в рамках одной транзакции ранее прочитанные данные оказываются изменёнными.

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


Уровни изоляции

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

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

  • Чтение неподтверждённых данных (грязное чтение) (read uncommitted, dirty read) - чтение незафиксированных изменений как своей транзакции, так и параллельных транзакций.
    Нет гарантии, что данные, измененные другими транзакциями, не будут в любой момент изменены в результате их отката, поэтому такое чтение является потенциальным источником ошибок.
    Невозможны потерянные изменения, возможны неповторяемое чтение и фантомы.

  • Чтение подтвержденных данных (read committed) - чтение всех изменений своей транзакции и зафиксированных изменений параллельных транзакций.
    Потерянные изменения и грязное чтение не допускается, возможны неповторяемое чтение (когда мы видим обновленные и удаленные строки (UPDATE, DELETE)) и фантомы (когда мы видим добавленные записи (INSERT)).

  • Повторяемость чтения (repeatable read, snapshot) - чтение всех изменений своей транзакции, любые изменения, внесенные параллельными транзакциями после начала своей, недоступны.
    Потерянные изменения, грязное и неповторяемое чтение невозможны, возможны фантомы.

  • Упорядочиваемость (serializable) - результат параллельного выполнения сериализуемой транзакции с другими транзакциями должен быть логически эквивалентен результату их какого-либо последовательного выполнения.
    Проблемы синхронизации не возникают.

Уровень изоляции Не должно быть Будут проблемы с
READ UNCOMMITTED, DIRTY READ

видят не закомиченные результаты +2проблемы

lost update dirty read
non-repeatable read
phantom reads
READ COMMITTED

видят закомиченные результаты +2проблемы

lost update
dirty read
non-repeatable read
phantom reads
REPEATABLE READ, SNAPSHOT

видят результаты Update и Delete +1проблема

lost update
dirty read
non-repeatable read
phantom reads
SERIALIZABLE lost update
dirty read
non-repeatable read
phantom reads
Нет проблем с синхронизацией

Распространение транзакций

Распространение транзакций (Propagation) в @Transactional

  • REQUIRED(0)
    Если активная транзакция уже существует, метод будет выполняться в её контексте. Если транзакции нет, она будет создана. Режим по-умолчанию.

  • SUPPORTS(1)
    Если существует активная транзакция, метод будет выполнен в её контексте. Если транзакции нет, метод будет выполнен вне контекста транзакции.

  • MANDATORY(2)
    Метод требует наличия активной транзакции. Если ее нет, будет выброшено исключение.

  • REQUIRES_NEW(3)
    Метод всегда будет выполняться в новой транзакции. Если уже есть активная транзакция, она будет приостановлена до завершения новой транзакции.

  • NOT_SUPPORTED(4)
    Метод будет выполняться вне контекста транзакции. Если имеется активная транзакция, она будет приостановлена на время его выполнения.

  • NEVER(5)
    Метод никогда не должен быть выполнен в контексте транзакции. Если транзакция активна, будет выброшено исключение.

  • NESTED(6)
    Метод будет выполнен в рамках вложенной транзакции, если уже существует активная транзакция. В противном случае, будет создана новая транзакция.

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

интересный видосик про транзакции


Грабли в JAVA

Грабли при использовании аннотации @Transactional:

  • Не вызывать метод, помеченный @Transactional из одного класса.
  • @Transactional не откатит транзакцию если выброшено Exception, работает с RuntimeException.

    Но можно настроить

    @Transactional(rollbackFor=Exception.class)
  • @Transactional работает только с public методами.
  • @Transactional занимает соединение с БД.

очень кратенько, но полезненько

Виды JOIN

Определение

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

Особенностями операции соединения являются следующее:
- В схему таблицы-результата входят столбцы обеих исходных таблиц (таблиц-операндов), то есть схема результата является "сцеплением" схем операндов;
- Каждая строка таблицы-результата является "сцеплением" строки из одной таблицы-операнда со строкой второй таблицы-операнда;
- При необходимости соединения не двух, а нескольких таблиц, операция соединения применяется несколько раз (последовательно).

SELECT <поля_для_выбора>
FROM table_1
[INNER] | [[LEFT | RIGHT | FULL][OUTER]] JOIN table_2
    ON <условие_соединения>
[[INNER] | [[LEFT | RIGHT | FULL][OUTER]] JOIN table_n
    ON <условие_соединения>]

INNER JOIN

Внутреннее объединение

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

inner_join
SELECT *
FROM table_left
INNER JOIN table_right
    ON table_left.key = table_right.key;

OUTER JOIN

Внешнее соединение

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

Если внутреннее объединение имеет сходство с бинарным "и", то внешнее — несколько вариаций бинарного "или".

ПОДРОБНЕЕ ДАЛЕЕ...

CROSS JOIN

Перекрестное соединение

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

В разработке Cross Join может использоваться при создании тех же фильтров в интернет-магазине. Например, человек ищет обувь по характеристикам "тип" и "размер" — должны быть выведены все возможные комбинации типа с размером.

cross_join
SELECT *
FROM table_left
CROSS JOIN table_right;

SELF JOIN

Самосоединение

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

Чтобы Self Join работал правильно, могут потребоваться псевдонимы таблиц: они помогают называть одну и ту же таблицу разными именами. В результате оператор "воспринимает" переданные сущности как разные.


Какие бывают виды join'ов?

Чаще всего выделяют четыре режима SQL JOIN: INNER, OUTER, SELF и CROSS.

OUTER JOIN's

LEFT JOIN

Возвращает пересечение множеств и все элементы из левой таблицы.

left_join
SELECT *
FROM table_left
LEFT JOIN table_right
    ON table_left.key = table_right.key;

Если в table_right нет соответствующей строки, то возвращаются NULL для столбцов из table_right.


RIGHT JOIN

Возвращает пересечение множеств и все элементы из правой таблицы.

right_join
SELECT *
FROM table_left
RIGHT JOIN table_right
    ON table_left.key = table_right.key;

Если в table_left нет соответствующей строки, то возвращаются NULL для столбцов из table_left.


FULL JOIN

Возвращает обе таблицы, объединенные в одну.

full_join
SELECT *
FROM table_left
FULL JOIN table_right ON table_left.key = table_right.key;

Если в table_left нет соответствующей строки, то возвращаются NULL для столбцов из table_left, и наоборот, если в table_right нет соответствующей строки.


LEFT JOIN c NULL

Возвращает данные из левой таблицы, но без пересечений с правой.

left_null_join
SELECT *
FROM table_left
LEFT JOIN table_right
    ON table_left.key = table_right.key
WHERE table_right.key IS NULL;

Этот прием полезен для поиска "несвязанных" строк в левой таблице.


FIGHT JOIN с NULL

Возвращает данные из правой таблицы, но без пересечений с левой.

right_null_join
SELECT *
FROM table_left
RIGHT JOIN table_right
    ON table_left.key = table_right.key
WHERE table_left.key IS NULL;

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


FULL JOIN с NULL

Возвращает результат из обеих таблиц, кроме пересечений.

Работает как исключающее "или".

full_null_join
SELECT *
FROM table_left
FULL JOIN table_right
    ON table_left.key = table_right.key
WHERE table_left.key IS NULL
    OR table_right.key IS NULL;

Запрос выбирает все "несвязанные" строки из обеих таблиц.

Дополнительные вопросы

Что лучше использовать JOIN или подзапросы?

Обычно лучше использовать JOIN, поскольку в большинстве случаев он более понятен и лучше оптимизируется СУБД (но 100% этого гарантировать нельзя). Так же JOIN имеет заметное преимущество над подзапросами в случае, когда список выбора SELECT содержит столбцы более чем из одной таблицы.

Подзапросы лучше использовать в случаях, когда нужно вычислять агрегатные значения и использовать их для сравнений во внешних запросах.


Что делает UNION?

Объединяет запросы в одну таблицу.

SELECT поля_таблиц FROM список_таблиц ...
UNION [ALL]
SELECT поля_таблиц FROM список_таблиц ... ;

UNION по умолчанию убирает повторения в результирующей таблице. Для отображения с повторением есть необязательный параметр ALL.

Не путайте операции объединения запросов с операциями объединения таблиц.

Для этого служит оператор JOIN.

Не путайте операции объединения запросов с подзапросами.

Подзапросы выполняются для связанных таблиц.

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


Чем WHERE отличается от HAVING?

Отличие HAVING от WHERE:

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

HAVING
Сначала группируются записи, а затем выбираются по условию, при этом, в отличие от WHERE, в нём можно использовать значения агрегатных функций

Выражения WHERE используются вместе с операциями SELECT, UPDATE, DELETE, в то время как HAVING только с SELECT и предложением GROUP BY, т.е. использовать WHERE в запросах с агрегатными функциями нельзя, для этого и был введен HAVING.


Что такое ORDER BY?

SELECT поля_таблиц FROM список_таблиц
ORDER BY столбец_1 [ASC | DESC][, столбец_n [ASC | DESC]];

Правило сортировки применяется только к указанным столбцам.
Можно указать направление сортировки.


Что такое GROUP BY?

Иногда требуется узнать информацию не о самих объектах, а об определенных группах, которые они образуют. Для этого используется оператор GROUP BY и агрегатные функции.

SELECT family_member, SUM(unit_price * amount) FROM Payments
GROUP BY family_member;

При использовании GROUP BY все значения NULL считаются равными.
Агрегатные функции применяются для значений, не равных NULL.
Исключением является функция COUNT().

SUM(поле_таблицы) Возвращает сумму значений
AVG(поле_таблицы) Возвращает среднее значение
COUNT(поле_таблицы) Возвращает количество записей
MIN(поле_таблицы) Возвращает минимальное значение
MAX(поле_таблицы) Возвращает максимальное значение


Что такое DISTINCT?

Оператор SQL DISTINCT используется для указания на то, что следует работать только с уникальными значениями столбца.
Может использоваться с агрегатными функциями.

SELECT COUNT(DISTINCT Singer)
AS CountOfSingers
FROM Artists

Что такое LIMIT?

SELECT поля_выборки
FROM список_таблиц
LIMIT [количество_пропущенных_записей,] количество_записей_для_вывода;

Когда необходимо сделать отступ от начала таблицы, предназначена конструкция OFFSET FETCH.

Для того, чтобы вывести строки с 3 по 5, нужно использовать такой запрос:

SELECT * FROM Company LIMIT 2, 3;

Что такое EXISTS?

EXISTS берет подзапрос, как аргумент, и оценивает его как TRUE, если подзапрос возвращает какие-либо записи и FALSE, если нет.

CREATE DATABASE IF NOT EXIST имя_базы_данных;
DROP DATABASE IF EXIST имя_базы_данных;

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

Найти тех производителей портативных компьютеров, которые также производят принтеры:

SELECT DISTINCT maker
FROM Product AS l
WHERE l.type = 'laptop' AND
    NOT EXISTS (SELECT р.maker
                FROM Product r
                WHERE r.type = 'printer' AND r.maker = l.maker);

Aгрегатные функции

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

SQL предоставляет несколько агрегатных функций:

Назавание Описание
COUNT Производит подсчет записей, удовлетворяющих условию запроса.
SUM Вычисляет арифметическую сумму всех значений колонки.
AVG вычисляет среднее арифметическое всех значений.
MAX Определяет наибольшее из всех выбранных значений.
MIN Определяет наименьшее из всех выбранных значений.
SUBSTRING Позволяет извлечь из выражения его часть заданной длины, начиная от заданной начальной позиции.
STRING_AGG Конкатенирует строки.
STRING_SPLIT Выполняет операцию, обратную STRING_AGG. Она принимает на входе символьную строку и разбивает её на подстроки по заданному вторым параметром разделителю.
LOWER Преобразуют все символы аргумента к нижнему.
UPPER Преобразуют все символы аргумента верхнему регистру.

В чем разница между COUNT(*) и COUNT(column)?

COUNT(*)
подсчитывает количество записей в таблице, не игнорируя значение NULL, поскольку эта функция оперирует записями, а не столбцами.

COUNT (column)
подсчитывает количество значений в column. При подсчете количества значений столбца эта форма функции COUNT не принимает во внимание значение NULL.

Ограничения и Ключи

Ограничения (constraints)

При создании таблицы можно установить ограничение на значения которые могут быть в нее внесены.

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

    Первичные ключи не могут позволить значений NULL.

  • CHECK
    Позволяет установить свое условие, которому должно удовлетворять значение вводимое в таблицу, прежде чем оно будет принято.

  • UNIQUE
    Обеспечивает отсутствие дубликатов в столбце или наборе столбцов.

  • FOREIGN KEY
    Защищает от действий, которые могут нарушить связи между таблицами. FOREIGN KEY в одной таблице указывает на PRIMARY KEY в другой. Поэтому данное ограничение нацелено на то, чтобы не было записей FOREIGN KEY, которым не отвечают записи PRIMARY KEY.

CREATE TABLE < table name >
        (< column name > < column constraint >,
        < column name > < data type > < column constraint > ...
        < table constraint > ( < column name >
        [, < column name > ])... );
[CONSTRAINT имя_ограничения]
FOREIGN KEY (столбец1, столбец2, ... столбецN)
REFERENCES главная_таблица (столбец_главной_таблицы1, столбец_главной_таблицы2, ... столбец_главной_таблицыN)
[ON DELETE действие]
[ON UPDATE действие]

Ключи

Простой ключ состоит из одного атрибута (поля).

Составной ключ (composite) - из двух и более.

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

Термин "candidate" подразумевает, что все такие ключи конкурируют за почётную роль "первичного ключа" (primary key), а оставшиеся назначаются "альтернативными ключами" (alternate keys).
Из множества всех потенциальных ключей набора данных выбирают первичный ключ, все остальные ключи называют альтернативными.

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

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

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

если по простому ни как, то определение по-умному
- это подмножество атрибутов некоторого отношения A, значения которых должны совпадать со значениями некоторого потенциального ключа некоторого отношения B.


Какие отличия между ограничениями PRIMARY и UNIQUE?

По умолчанию ограничение PRIMARY создает кластерный индекс на столбце, а UNIQUE - некластерный*.
Другим отличием является то, что PRIMARY не разрешает NULL записей, в то время как UNIQUE разрешает одну (а в некоторых СУБД несколько) NULL запись.

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


Может ли значение в столбце, на который наложено ограничение FOREIGN KEY, равняться NULL?

Может, если на данный столбец не наложено ограничение NOT NULL.


Что такое суррогатные ключи?

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

Индексы

Определение

Индекс (index) - объект базы данных, создаваемый с целью повышения производительности выборки данных.

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

Преимущества

  • ускорение поиска и сортировки по определенному полю или набору полей.
  • обеспечение уникальности данных.

Недостатки

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

Индексы предпочтительней для:

  • Поля-счетчика, чтобы, в том числе, избежать и повторения значений в этом поле.
  • Поля, по которому проводится сортировка данных.
  • Полей, по которым часто проводится соединение наборов данных.

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

  • Поля, которое объявлено первичным ключом (primary key).
  • Поля, в котором данные выбираются из некоторого диапазона.

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

Использование индексов нецелесообразно для:

  • Полей, которые редко используются в запросах;
  • Полей, которые содержат всего два или три значения, например: мужской, женский пол или значения "да", "нет".

    Так называемые низкоселективные поля.


Типы индексов

(раскрыть все)

  • По порядку сортировки:

    • упорядоченные - индексы, в которых элементы упорядочены

  • По источнику данных:

    • индексы по представлению (view)
    • индексы по выражениям
  • По воздействию на источник данных:

    • кластерный индекс - при определении в наборе данных физическое расположение данных перестраивается в соответствии со структурой индекса.

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

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

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

  • По структуре:

    • B-деревья

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

    • B+-деревья

      - Усовершенствованная версия B-дерева.
      - Все значения данных хранятся только в листьях, тогда как внутренние узлы содержат только индексы и используются для навигации.
      - Это позволяет быстрее выполнять последовательное считывание данных.

    • B*-деревья

      - Дальнейшее развитие концепции B+-деревьев, которые используют более плотное заполнение узлов.
      - Более высокая плотность заполнения узлов снижает высоту дерева, что может привести к увеличению эффективности операций поиска за счет уменьшения количества операций доступа к диску.

    • Хэш-индексы

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

  • По количественному составу:

    • простой индекс (индекс с одним ключом) - строится по одному полю.

    • составной (многоключевой, композитный) индекс - строится по нескольким полям при этом важен порядок их следования.

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

    • главный индекс (индекс по первичному ключу) - это тот индексный ключ, под управлением которого в данный момент находится набор данных.

      Набор данных не может быть отсортирован по нескольким индексным ключам одновременно.

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

  • По характеристике содержимого:

    • уникальный индекс состоит из множества уникальных значений поля.

    • плотный индекс (NoSQL) - индекс, при котором, каждом документе в индексируемой коллекции соответствует запись в индексе, даже если в документе нет индексируемого поля.

    • разреженный индекс (NoSQL) - тот, в котором представлены только те документы, для которых индексируемый ключ имеет какое-то определённое значение (существует).

    • пространственный индекс - оптимизирован для описания географического местоположения. Представляет из себя многоключевой индекс состоящий из широты и долготы.

    • составной пространственный индекс - индекс, включающий в себя кроме широты и долготы ещё какие-либо мета-данные (например теги). Но географические координаты должны стоять на первом месте.

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

    • хэш-индекс - предполагает хранение не самих значений, а их хэшей, благодаря чему уменьшается размер (а, соответственно, и увеличивается скорость их обработки) индексов из больших полей.

      Т.о., при запросах с использованием хэш-индексов, сравниваться будут не искомое со значения поля, а хэш от искомого значения с хэшами полей. Из-за нелинейности хэш-функций данный индекс нельзя сортировать по значению, что приводит к невозможности использования в сравнениях больше/меньше и "is null".
      Кроме того, так как хэши не уникальны, то для совпадающих хэшей применяются методы разрешения коллизий.

    • битовый индекс (bitmap index) - метод битовых индексов заключается в создании отдельных битовых карт (последовательностей 0 и 1) для каждого возможного значения столбца, где каждому биту соответствует запись с индексируемым значением, а его значение равное 1 означает, что запись, соответствующая позиции бита содержит индексируемое значение для данного столбца или свойства.

    • обратный индекс (reverse index) - B-tree индекс, но с реверсированным ключом, используемый в основном для монотонно возрастающих значений (например, автоинкрементный идентификатор) в OLTP системах с целью снятия конкуренции за последний листовой блок индекса, т.к. благодаря переворачиванию значения две соседние записи индекса попадают в разные блоки индекса.
      Он не может использоваться для диапазонного поиска.

    • функциональный индекс, индекс по вычисляемому полю (function-based index) - индекс, ключи которого хранят результат пользовательских функций. Функциональные индексы часто строятся для полей, значения которых проходят предварительную обработку перед сравнением в команде SQL.

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

    • первичный индекс - уникальный индекс по полю первичного ключа.

    • вторичный индекс - индекс по другим полям (кроме поля первичного ключа).

    • XML-индекс - вырезанное материализованное представление больших двоичных XML-объектов (BLOB) в столбце с типом данных xml.

  • По механизму обновления:

    • полностью перестраиваемый - при добавлении элемента заново перестраивается весь индекс.

    • пополняемый (балансируемый) - при добавлении элементов индекс перестраивается частично (например, одна из ветви) и периодически балансируется.

  • По покрытию индексируемого содержимого:

    • полностью покрывающий (полный) индекс - покрывает всё содержимое индексируемого объекта.

    • частичный индекс (partial index) - это индекс, построенный на части набора данных, удовлетворяющей определенному условию самого индекса.
      Данный индекс создан для уменьшения размера индекса.

    • инкрементный (delta) индекс - индексируется малая часть данных(дельта), как правило, по истечении определенного времени.
      Используется при интенсивной записи.

      Например, полный индекс перестраивается раз в сутки, а дельта-индекс строится каждый час. По сути это частичный индекс по временной метке.

    • индекс реального времени (real-time index) - особый вид инкрементного индекса, характеризующийся высокой скоростью построения.
      Предназначен для часто меняющихся данных.

  • Индексы в кластерных системах:

    • глобальный индекс - индекс по всему содержимому всех сегментов БД (shard).

    • сегментный индекс глобальный индекс по полю-сегментируемому ключу (shard key).
      Используется для быстрого определения сегмента, на котором хранятся данные в процессе маршрутизации запроса в кластере БД.

    • локальный индекс - индекс по содержимому только одного сегмента БД.


В чем отличие между кластерными и некластерными индексами?

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

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

Для одного набора данных может быть создан только один кластерный индекс.


Имеет ли смысл индексировать данные, имеющие небольшое количество возможных значений?

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


Когда полное сканирование набора данных выгоднее доступа по индексу?

Полное сканирование производится многоблочным чтением. Сканирование по индексу - одноблочным. Также (и очень важно), при доступе по индексу сначала идет сканирование самого индекса, а затем чтение блоков из набора данных.
Число блоков, которые надо при этом прочитать из набора зависит от фактора кластеризации. Если суммарная стоимость всех необходимых одноблочных чтений больше стоимости полного сканирования многоблочным чтением, то полное сканирование выгоднее и оно выбирается оптимизатором.
Т.о., полное сканирование выбирается при слабой селективности предикатов запроса и/или слабой кластеризации данных, либо в случае очень маленьких наборов данных.


Как создать индекс?

Индекс можно создать либо с помощью выражения CREATE INDEX:

CREATE INDEX index_name ON table_name (column_name)

Либо указав ограничение целостности в виде уникального UNIQUE или первичного PRIMARY ключа в операторе создания таблицы CREATE TABLE.

Репликация и шардирование

Репликация

Реплицирование - это процесс создания и поддержания копий данных на нескольких серверах или узлах в системе.

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

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

Существует несколько моделей репликации:

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

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

Репликация помогает обеспечить:

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

  • Доступность:
    при высокой нагрузке запросы могут распределяться между разными репликами, что снижает нагрузку на каждый отдельный узел.

  • Географическое распределение данных:
    хранение реплик данных в разных географических локациях позволяет пользователям получать доступ к данным с меньшими задержками.

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


Шардирование

Шардирование (от англ. "sharding") - это метод разделения базы данных или другой системы на более мелкие, управляемые части, которые называются "шардами" (shards). Каждая шарда — это подмножество данных, которое может быть размещено и обработано отдельно, обычно на разных серверах или узлах.

Цель шардирования - улучшить производительность и масштабируемость системы. Оно позволяет распределить нагрузку на несколько серверов, что в свою очередь может сделать систему более устойчивой к отказам. Если компактно выражаться, шардирование помогает обрабатывать большие объемы данных более эффективно, обеспечивая параллельную обработку запросов. Часто шардирование применяют в распределённых системах и масштабируемых веб-приложениях, где требуется обработка больших объёмов данных с высокой скоростью.

Основные принципы шардирования:

  1. Чёткое определение логики разделения данных:
    нужно четко решить по какому критерию данные будут распределены между шардамию (например, по какому-то идентификатору, временному интервалу и т.д.).

  2. Изоляция:
    каждая шарда автономна и может работать независимо от других, хотя данные в шардах часто всё равно нуждаются в согласованности.

  3. Горизонтальное масштабирование:
    добавление нового шарда позволяет увеличить ёмкость системы без значительных изменений в архитектуре.

Нормализация и нормальные формы

Нормализация

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

Нормализация – это и есть здравый смысл в проектировании БД.

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


Нормальные формы


Первая нормальная форма

Отношение находится в 1НФ, если все его атрибуты являются простыми, все используемые домены должны содержать только скалярные значения (может быть выражено одним, как правило, действительным числом). Не должно быть повторений строк в таблице.

Пример:

Например, есть таблица "Автомобили":

Производитель Модель
BMW M5, X5M, M1
NISSAN GT-R

Нарушение нормализации 1НФ происходит в моделях BMW, т.к. в одной ячейке содержится список из 3 элементов: M5, X5M, M1, т.е. он не является атомарным.

Преобразуем таблицу к 1НФ (в одной ячейке – одно значение):

Производитель Модель
BMW M5
BMW X5M
BMW M1
NISSAN GT-R

Вторая нормальная форма

Отношение находится во 2НФ, если оно находится в 1НФ и каждый не ключевой атрибут неприводимо зависит от Первичного Ключа(ПК).
Неприводимость означает, что в составе потенциального ключа отсутствует меньшее подмножество атрибутов, от которого можно также вывести данную функциональную зависимость.

Короче, прослеживается 2 зависимости из которых можно выделить 2-ю таблицу.

Пример:

Дана таблица

Модель Производитель Цена Скидка
M5 BMW 5500000 5%
X5M BMW 6000000 5%
M1 BMW 2500000 5%
GT-R NISSAN 5000000 10%

Таблица находится в первой нормальной форме, но не во второй.
- Цена машины зависит от модели и производителя.
- Скидка зависят от производителя
То есть зависимость от первичного ключа неполная.

Исправляется это путем декомпозиции на два отношения, в которых не ключевые атрибуты зависят от первичного ключа.

Модель Производитель Цена
M5 BMW 5500000
X5M BMW 6000000
M1 BMW 2500000
GT-R NISSAN 5000000
Производитель Скидка
BMW 5%
NISSAN 10%

Третья нормальная форма

Отношение находится в 3НФ, когда находится во 2НФ и каждый не ключевой атрибут нетранзитивно зависит от первичного ключа.

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

Пример:

Есть таблица

Модель Салон Телефон
BMW Риал-Авто 12-34-56
AUDI Риал-Авто 12-34-56
NISSAN Некст-Авто 98-76-54

Таблица находится во 2НФ, но не в 3НФ.

В отношении атрибут "Модель" является первичным ключом.

Личных телефонов у автомобилей нет, и телефон зависит исключительно от магазина.

Т.o., в отношении существуют следующие функциональные зависимости:
Модель->Салон, Салон->Телефон, Модель->Телефон.
Зависимость Модель->Телефон является транзитивной, следовательно, отношение не находится в 3НФ.

В результате разделения исходного отношения получаются два отношения, находящиеся в 3НФ:

Салон Телефон
Риал-Авто 12-34-56
Некст-Авто 98-76-54
Модель Салон
BMW Риал-Авто
AUDI Риал-Авто
NISSAN Некст-Авто

Нормальная форма Бойса-Кодда

Частная форма третьей нормальной формы

Определение 3НФ не совсем подходит для следующих отношений:

  1. Отношение имеет два или более потенциальных ключа;
  2. Два и более потенциальных ключа являются составными;
  3. Они пересекаются, т.е. имеют хотя бы один общий атрибут.

Для отношений, имеющих один потенциальный ключ (первичный), НФБК является 3НФ. Отношение находится в НФБК, когда каждая нетривиальная и неприводимая слева функциональная зависимость обладает потенциальным ключом в качестве детерминанта.

Предположим, рассматривается отношение, представляющее данные о бронировании стоянки на день:

Номер стоянки Время начала Время окончания Тариф
1 09:30 10:30 Бережливый
1 11:00 12:00 Бережливый
1 14:00 15:30 Стандарт
2 10:00 12:00 Премиум-В
2 12:00 14:00 Премиум-В
2 15:00 15:80 Премиум-А

Тариф имеет уникальное название и зависит от выбранной стоянки и наличии льгот, в частности:

  • Бережливый - стоянка 1 для льготников.

  • Стандарт - стоянка 1 для не льготников.

  • Премиум-А стоянка 2 для льготников.

  • Премиум-B стоянка 2 для не льготников.

Таким образом, возможны следующие составные первичные ключи:

[Номер стоянки, Время начала],
[Номер стоянки, Время окончания],
[Тариф, Время начала],
[Тариф, Время окончания].

Отношение находится в 3НФ.
Требования второй нормальной формы выполняются, так как все атрибуты входят в какой-то из потенциальных ключей, а неключевых атрибутов в отношении нет.
Также нет и транзитивных зависимостей, что соответствует требованиям третьей нормальной формы.
Тем не менее, существует функциональная зависимость Тариф->Номер стоянки, в которой левая часть (детерминант) не является потенциальным ключом отношения, то есть отношение не находится в нормальной форме Бойса — Кодда.

Недостатком данной структуры является то, что, например, по ошибке можно приписать тариф "Бережливый" к бронированию второй стоянки, хотя он может относиться только к первой стоянки.

Можно улучшить структуру с помощью декомпозиции отношения на два и добавления атрибута "Имеет льготы", получив отношения, удовлетворяющие НФБК:

Тарифы

Тариф Номер стоянки Имеет льготы
Бережливый 1 Да
Стандарт 1 Нет
Премиум-А 2 Да
Премиум-В 2 Нет

Бронирование

Тариф Время начала Время окончания
Бережливый 09:30 10:30
Бережливый 11:00 12:00
Стандарт 14:00 15:00
Премиум-В 10:00 12:00
Премиум-В 12:00 14:00
Премиум-А 15:00 18:00

Четвертая нормальная форма

Отношение находится в 4НФ, если оно находится в НФБК и все нетривиальные многозначные зависимости фактически являются функциональными зависимостями от ее потенциальных ключей.

В отношении R (A, B, C) существует многозначная зависимость R.A -> -> R.B в том и только в том случае, если множество значений B, соответствующее паре значений A и C, зависит только от A и не зависит от С.

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

Такая переменная отношения не соответствует 4НФ, так как существует следующая многозначная зависимость:
Ресторан->Вид пиццы
Ресторан->Район доставки

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

Для предотвращения аномалии нужно декомпозировать отношение, разместив независимые факты в разных отношениях.
В данном примере следует выполнить декомпозицию на [Ресторан, Вид пиццы] и [Ресторан, Район доставки].

Однако, если к исходной переменной отношения добавить атрибут, функционально зависящий от потенциального ключа, например цену с учётом стоимости доставки ([Ресторан, Вид пиццы, Район доставки]->Цена), то полученное отношение будет находиться в 4НФ и его уже нельзя подвергнуть декомпозиции без потерь.


Пятая нормальная форма

Отношения находятся в 5НФ, если оно находится в 4НФ и отсутствуют сложные зависимые соединения между атрибутами.
Если "Атрибут_1" зависит от "Атрибута_2", а "Атрибут_2" в свою очередь зависит от "Атрибута_3", а "Атрибут_3" зависит от "Атрибута_1", то все три атрибута обязательно входят в один кортеж.

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

На практике трудно найти пример реализации этого требования в чистом виде.

Например, некоторая таблица содержит три атрибута "Поставщик", "Товар" и "Покупатель".

Покупатель_1 приобретает несколько Товаров у Поставщика_1.

Покупатель_1 приобрел "новый Товар" у Поставщика_2.

Тогда в силу изложенного выше требования Поставщик_1 обязан поставлять Покупателю_1 тот же самый "новый Товар", а Поставщик_2 должен поставлять Покупателю_1, кроме "нового Товара", всю номенклатуру Товаров Поставщика_1.

Этого на практике не бывает. Покупатель свободен в своем выборе товаров.

Поэтому для устранения отмеченного затруднения все три атрибута разносят по разным отношениям (таблицам).

После выделения трех новых отношений (Поставщик, Товар и Покупатель) необходимо помнить, что при извлечении информации (например, о покупателях и товарах) необходимо в запросе соединить все три отношения.
Любая комбинация соединения двух отношений из трех неминуемо приведет к извлечению неверной (некорректной) информации.

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

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

Пятая нормальная форма ориентирована на работу с зависимыми соединениями. Указанные зависимые соединения между тремя атрибутами встречаются очень редко. Зависимые соединения между четырьмя, пятью и более атрибутами указать практически невозможно.


Доменно-ключевая нормальная форма

Переменная отношения находится в ДКНФ тогда и только тогда, когда каждое наложенное на неё ограничение является логическим следствием ограничений доменов и ограничений ключей, наложенных на данную переменную отношения.

Ограничение домена - ограничение, предписывающее использовать для определённого атрибута значения только из некоторого заданного домена.
Ограничение по своей сути является заданием перечня (или логического эквивалента перечня) допустимых значений типа и объявлением о том, что указанный атрибут имеет данный тип.

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

Любая переменная отношения, находящаяся в ДКНФ, обязательно находится в 5НФ. Однако не любую переменную отношения можно привести к ДКНФ.


Шестая нормальная форма

Переменная отношения находится в 6НФ тогда и только тогда, когда она удовлетворяет всем нетривиальным зависимостям соединения.

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

Каждая переменная отношения, которая находится в 6НФ, также находится и в 5НФ.

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

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

В данном примере соединение проекций отношения должно производится при помощи оператора U_JOIN.

Работники

Табельный номер Время Должность Домашний адрес
6575 01-01-2000:10-02-2003 Слесарь ул. Ленина, д. 10
6575 11-02-2003:15-06-2006 Слесарь ул. Советская, д. 22
6575 16-06-2006:05-03-2009 Бригадир ул. Советская, д. 22

Переменная отношения "Работники" не находится в 6НФ и может быть подвергнута декомпозиции на переменные отношения "Должности работников" и "Домашние адреса работников".

Должности работников

Табельный номер Время Должность
6575 01-01-2000:10-02-2003 Слесарь
6575 16-06-2006:05-03-2009 Бригадир

Домашние адреса работников

Табельный номер Время Домашний адрес
6575 01-01-2000:10-02-2003 ул. Ленина, д. 10
6575 11-02-2003:15-06-2006 ул. Советская, д. 22