Silver 31 декабря 2019

SQL или NoSQL? А также самые сложные SELECT-запросы

Являются ли NoSQL базы будущим программирования? Или каждой технологии отведено своё место? Отвечаем на это в статье.

Типы NoSQL

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

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

Более того, конкретная база может использовать сразу несколько технологий. Например, AWS DynamoDB является одновременно документной, типом ключ-значение и с широким столбцом, а Azure Cosmos DB – графовой и документной. На рынке огромное количество баз, и такое разнообразие ставит трудную задачу перед архитекторами. В нашей статье мы постараемся помочь хотя бы с выбором между SQL и NoSQL.

Достоинства SQL

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

1-я форма:

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

2-я форма:

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

3-я форма:

  • Атрибуты не должны зависеть ни от каких других атрибутов кроме ключа.

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

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

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

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

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

Или вот такой запрос, выбирающий товары по критериям:

            SELECT
    products.id,
    products.name,
    price.link,
    price.discount,
    price.click_count,
    price.currency,
    price_currency.name,
    
    -- default currency is ruble
    (@ori_price:=price.price) AS price,

    -- convertation in dollars
    ROUND(price.price / {$course}, 2) AS dol_price,

    -- select minimal price for product
    (@min_price:=(
                SELECT MIN(price)
                FROM price
                WHERE product_id = products.id
                   && id != price.id
            )
    ) AS min_val,

    -- compare price
    -- " + 1 - 1"
    (@diff:= ROUND(
                    (
                        @max:=GREATEST(@ori_price + 1 - 1, @min_price + 1 - 1)
                        -
                        @min:=LEAST(@ori_price + 1 - 1, @min_price + 1 - 1)
                    ) / @min * 100, 1
                )
    ) AS diff,
    (IF(@ori_price + 1 - 1 <= @min_price + 1 - 1, @diff, -@diff)) AS diff

FROM products
    LEFT JOIN price          ON price.product_id = products.id
    LEFT JOIN price_currency ON price_currency.id = price.currency

WHERE products.archive = 0
ORDER BY products.date_add DESC
        

Достоинства NoSQL

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

Ключ-значение

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

Нетрудно заметить, что это полностью нарушает принципы нормализации, что приводит к ряду недостатков:

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

Денормализация

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

Вывод

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

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

А что вы думаете о SQL и NoSQL?

РУБРИКИ В СТАТЬЕ

МЕРОПРИЯТИЯ

Комментарии 1

ВАКАНСИИ

Программист С++
Санкт-Петербург, по итогам собеседования
Unity Developer
Минск, по итогам собеседования
DevOps Engineer
Казань, по итогам собеседования
Middle Unity Developer
Москва, по итогам собеседования

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ

BUG