Каковы наиболее распространенные анти-шаблоны SQL?

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

Ответ 1

Меня постоянно разочаровывает стремление большинства программистов смешать их UI-логику в слое доступа к данным:

SELECT
    FirstName + ' ' + LastName as "Full Name",
    case UserRole
        when 2 then "Admin"
        when 1 then "Moderator"
        else "User"
    end as "User Role",
    case SignedIn
        when 0 then "Logged in"
        else "Logged out"
    end as "User signed in?",
    Convert(varchar(100), LastSignOn, 101) as "Last Sign On",
    DateDiff('d', LastSignOn, getDate()) as "Days since last sign on",
    AddrLine1 + ' ' + AddrLine2 + ' ' + AddrLine3 + ' ' +
        City + ', ' + State + ' ' + Zip as "Address",
    'XXX-XX-' + Substring(
        Convert(varchar(9), SSN), 6, 4) as "Social Security #"
FROM Users

Обычно программисты делают это, потому что они намереваются привязать свой набор данных непосредственно к сетке, и его просто удобно иметь серверный формат SQL Server, чем формат на клиенте.

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

Ответ 2

Вот мои лучшие 3.

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

SELECT *
Insert Into blah SELECT *

должен быть

SELECT fieldlist
Insert Into blah (fieldlist) SELECT fieldlist

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

DECLARE @LoopVar int

SET @LoopVar = (SELECT MIN(TheKey) FROM TheTable)
WHILE @LoopVar is not null
BEGIN
  -- Do Stuff with current value of @LoopVar
  ...
  --Ok, done, now get the next value
  SET @LoopVar = (SELECT MIN(TheKey) FROM TheTable
    WHERE @LoopVar < TheKey)
END

Число 3. DateLogic через типы строк.

--Trim the time
Convert(Convert(theDate, varchar(10), 121), datetime)

Должно быть

--Trim the time
DateAdd(dd, DateDiff(dd, 0, theDate), 0)

Я видел недавний всплеск "Один запрос лучше, чем два, amiright?"

SELECT *
FROM blah
WHERE (blah.Name = @name OR @name is null)
  AND (blah.Purpose = @Purpose OR @Purpose is null)

Этот запрос требует двух или трех разных планов выполнения в зависимости от значений параметров. Только один план выполнения генерируется и застревает в кеш для этого текста sql. Этот план будет использоваться независимо от значения параметров. Это приводит к неустойчивой низкой производительности. Гораздо лучше написать два запроса (один запрос для каждого плана выполнения).

Ответ 3

  • Защищенные пользователем поля пароля, например. Самоочевидный.

  • Использование LIKE для индексированных столбцов, и я почти соблазн просто скажите LIKE вообще.

  • Утилизация значений PK, генерируемых SQL.

  • Сюрприз никто не упомянул бог-стол. Ничего не говорится "органический", как 100 столбцов бит флаги, большие строки и целые числа.

  • Тогда там "Я пропустил .ini файлы ": сохранение CSV, pipe разделительные строки или другой синтаксический анализ требуемые данные в больших текстовых полях.

  • И для сервера MS SQL использование курсоры вообще. Там лучше способ выполнения любой заданной задачи курсора.

Отредактировано, потому что там так много!

Ответ 4

Не нужно глубоко разбираться в этом: не использовать подготовленные заявления.

Ответ 5

Использование бессмысленных псевдонимов таблицы:

from employee t1,
department t2,
job t3,
...

Делает чтение большого оператора SQL намного сложнее, чем нужно

Ответ 7

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

Как правило, эта таблица поиска выглядит следующим образом:

ID INT,
Name NVARCHAR(132),
IntValue1 INT,
IntValue2 INT,
CharValue1 NVARCHAR(255),
CharValue2 NVARCHAR(255),
Date1 DATETIME,
Date2 DATETIME

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

Ответ 8

Те, что мне больше всего не нравятся,

  • Использование пробелов при создании таблиц, sprocs и т.д. Я в порядке с CamelCase или under_scores и сингулярными или множественными числами и UPPERCASE или строчными буквами, но должен ссылаться на таблицу или столбец [с пробелами], особенно если [это странно разнесенные] (да, я натолкнулся на это) действительно раздражает меня.

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

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

  • Доступ. Может ли программа быть анти-шаблоном? У меня есть SQL Server на моей работе, но многие люди используют доступ из-за его доступности, "простоты использования" и "дружелюбия" для нетехнических пользователей. Слишком много здесь, чтобы войти, но если вы были в подобной среде, вы знаете.

Ответ 9

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

Ответ 10

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

Ответ 11

Для хранения значений времени следует использовать только часовой пояс UTC. Местное время не должно использоваться.

Ответ 12

select some_column, ...
from some_table
group by some_column

и предполагая, что результат будет отсортирован по some_column. Я видел это немного с Sybase, где предположение имеет место (на данный момент).

Ответ 13

, используя @@IDENTITY вместо SCOPE_IDENTITY()

Цитата из этого ответа:

  • @@IDENTITY возвращает последнее значение идентификации, сгенерированное для любой таблицы текущего сеанса, во всех областях. Вы должны быть осторожны здесь, так как это по областям. Вы можете получить значение из триггера вместо текущего оператора.
  • SCOPE_IDENTITY возвращает последнее значение идентификации, сгенерированное для любой таблицы текущего сеанса и текущей области. Как правило, вы хотите использовать.
  • IDENT_CURRENT возвращает последнее значение идентификации, сгенерированное для конкретной таблицы в любом сеансе и в любой области. Это позволяет указать, из какой таблицы вы хотите получить значение, в случае, если два выше не совсем то, что вам нужно (очень редко). Вы можете использовать это, если хотите получить текущее значение IDENTITY для таблицы, в которую вы не ввели запись.

Ответ 14

Повторное использование поля "мертвое" для чего-то, для чего оно не предназначено (например, для хранения пользовательских данных в поле "Факс" ) - очень заманчиво, как быстрое исправление!

Ответ 15

SELECT FirstName + ' ' + LastName as "Full Name", case UserRole when 2 then "Admin" when 1 then "Moderator" else "User" end as "User Role", case SignedIn when 0 then "Logged in" else "Logged out" end as "User signed in?", Convert(varchar(100), LastSignOn, 101) as "Last Sign On", DateDiff('d', LastSignOn, getDate()) as "Days since last sign on", AddrLine1 + ' ' + AddrLine2 + ' ' + AddrLine3 + ' ' + City + ', ' + State + ' ' + Zip as "Address", 'XXX-XX-' + Substring(Convert(varchar(9), SSN), 6, 4) as "Social Security #" FROM Users

Или, перебирая все в одну строку.

Ответ 16

  • Синтаксис FROM TableA, TableB WHERE для JOINS, а не FROM TableA INNER JOIN TableB ON

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

Ответ 17

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

Это относится, когда:

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

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

Ответ 18

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

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

Смотрите O'Reilly SQL Cookbook Приложение A для хорошего обзора оконных функций.

Ответ 19

Контральный взгляд: чрезмерная одержимость нормализацией.

В большинстве систем SQL/RBDB есть много возможностей (транзакций, репликации), которые весьма полезны даже при ненормализованных данных. Дисковое пространство дешево, и иногда это может быть проще (более простой код, более быстрое время разработки), чтобы манипулировать/фильтровать/искать извлеченные данные, чем писать схему 1NF и обрабатывать все неприятности в ней (сложные комбинации, неприятные подзапросы, и т.д).

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

(больше мыслей об этом... http://writeonly.wordpress.com/2008/12/05/simple-object-db-using-json-and-python-sqlite/)

Ответ 20

Нарушение временной таблицы.

В частности, такого рода вещи:

SELECT personid, firstname, lastname, age
INTO #tmpPeople
FROM People
WHERE lastname like 's%'

DELETE FROM #tmpPeople
WHERE firstname = 'John'

DELETE FROM #tmpPeople
WHERE firstname = 'Jon'

DELETE FROM #tmpPeople
WHERE age > 35

UPDATE People
SET firstname = 'Fred'
WHERE personid IN (SELECT personid from #tmpPeople)

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

И да, я видел страницы кода в этой форме в производственных БД.

Ответ 21

Я просто помещаю это вместе, основываясь на некоторых ответах SQL здесь на SO.

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

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

Ответ 22

1) Я не знаю, что это "официальный" анти-шаблон, но я не люблю и стараюсь избегать строковых литералов как магических значений в столбце базы данных.

Пример из таблицы MediaWiki 'image':

img_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", 
    "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
img_major_mime ENUM("unknown", "application", "audio", "image", "text", 
    "video", "message", "model", "multipart") NOT NULL default "unknown",

(я просто замечаю другую оболочку, еще одну вещь, которую нужно избегать)

Я проектирую такие случаи, как int lookups в таблицах ImageMediaType и ImageMajorMime с первичными ключами int.

2) преобразование даты/строки, которое зависит от конкретных настроек NLS

CONVERT(NVARCHAR, GETDATE())

без идентификатора формата

Ответ 23

Идентичные подзапросы в запросе.

Ответ 24

  • Измененный вид - вид, который изменяется слишком часто и без уведомления или причины. Это изменение будет либо замечено в самое неподходящее время, либо хуже быть неправильным и никогда не замечать. Возможно, ваше приложение сломается, потому что кто-то подумал о лучшем имени для этой колонки. Как правило, мнения должны расширять полезность базовых таблиц при сохранении контракта с потребителями. Исправьте проблемы, но не добавляйте функции или ухудшайте поведение при изменении, для чего создайте новое представление. Чтобы смягчить, не разделяйте представления с другими проектами и используйте CTEs, когда позволяют платформы. Если в вашем магазине есть администратор базы данных, вы, вероятно, не сможете изменять представления, но все ваши взгляды будут устаревшими или бесполезными в этом случае.

  • The! Paramed - может ли запрос иметь более одной цели? Наверное, но следующий человек, который его читает, не будет знать до глубокой медитации. Даже если вы не нуждаетесь в них сейчас, скорее всего, вы захотите, даже если это "просто" для отладки. Добавление параметров снижает время обслуживания и сохраняет вещи DRY. Если у вас есть предложение where, у вас должны быть параметры.

  • Случай без CASE -

    SELECT  
    CASE @problem  
      WHEN 'Need to replace column A with this medium to large collection of strings hanging out in my code.'  
        THEN 'Create a table for lookup and add to your from clause.'  
      WHEN 'Scrubbing values in the result set based on some business rules.'  
        THEN 'Fix the data in the database'  
      WHEN 'Formating dates or numbers.'   
        THEN 'Apply formating in the presentation layer.'  
      WHEN 'Createing a cross tab'  
        THEN 'Good, but in reporting you should probably be using cross tab, matrix or pivot templates'   
    ELSE 'You probably found another case for no CASE but now I have to edit my code instead of enriching the data...' END  
    

Ответ 25

Сохраненные процедуры или функции без комментариев...

Ответ 26

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

Ответ 27

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

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

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

Ответ 28

Разработчики, которые пишут запросы, не имея представления о том, что делает приложения SQL (как отдельные запросы, так и многопользовательские системы) быстрыми или медленными. Это включает в себя незнание о:

  • стратегии минимизации ввода-вывода, учитывая, что "узким местом" большинства запросов является ввод-вывод, а не центральный процессор
  • Перспективное влияние различных видов доступа к физическому хранилищу (например, много последовательных операций ввода-вывода будет быстрее, чем множество небольших случайных операций ввода-вывода, хотя это менее важно, если ваше физическое хранилище является SSD!)
  • как вручную настроить запрос, если СУБД создает плохой план запроса
  • как диагностировать низкую производительность базы данных, как "отлаживать" медленный запрос и как читать план запроса (или EXPLAIN, в зависимости от выбранной вами СУБД).
  • стратегии блокировки для оптимизации пропускной способности и предотвращения взаимоблокировок в многопользовательских приложениях
  • важность пакетной обработки и других трюков для обработки обработки наборов данных.
  • таблица и индексный дизайн, чтобы наилучшим образом сбалансировать пространство и производительность (например, охватывать индексы, уменьшая, насколько это возможно, индексы, сокращать типы данных до необходимого минимального размера и т.д.).

Ответ 29

Использование SQL в качестве прославленного пакета ISAM (индексированный метод последовательного доступа). В частности, вложенные курсоры вместо объединения операторов SQL в один, хотя и более крупный, оператор. Это также считается "злоупотреблением оптимизатором", поскольку на самом деле оптимизатор не так много делает. Это можно комбинировать с незаготовленными операторами для максимальной неэффективности:

DECLARE c1 CURSOR FOR SELECT Col1, Col2, Col3 FROM Table1

FOREACH c1 INTO a.col1, a.col2, a.col3
    DECLARE c2 CURSOR FOR
        SELECT Item1, Item2, Item3
            FROM Table2
            WHERE Table2.Item1 = a.col2
    FOREACH c2 INTO b.item1, b.item2, b.item3
        ...process data from records a and b...
    END FOREACH
END FOREACH

Правильное решение (почти всегда) заключается в объединении двух операторов SELECT в один:

DECLARE c1 CURSOR FOR
    SELECT Col1, Col2, Col3, Item1, Item2, Item3
        FROM Table1, Table2
        WHERE Table2.Item1 = Table1.Col2
        -- ORDER BY Table1.Col1, Table2.Item1

FOREACH c1 INTO a.col1, a.col2, a.col3, b.item1, b.item2, b.item3
    ...process data from records a and b...
END FOREACH

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

Кроме того, сортировка в приложении обычно не имеет значения.

Ответ 30

Я просто натолкнулся на определение определения следующим образом:

CREATE OR REPLACE FORCE VIEW PRICE (PART_NUMBER, PRICE_LIST, LIST_VERSION ...)
AS
  SELECT sp.MKT_PART_NUMBER,
    sp.PRICE_LIST,
    sp.LIST_VERSION,
    sp.MIN_PRICE,
    sp.UNIT_PRICE,
    sp.MAX_PRICE,
...

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