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

StackOverflow https://stackoverflow.com/questions/346659

  •  19-08-2019
  •  | 
  •  

Вопрос

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

Это было полезно?

Решение

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

SELECT
    FirstName + ' ' + LastName as "Full Name",
    case UserRole
        when 2 then "Admin"
        when 1 then "Moderator"
        else "User"
    end as "User's 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, а не формат на клиенте.

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

Другие советы

Вот мои лучшие 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)
<Ч>

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

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

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

  • Поля паролей, читаемые человеком, блин.Самоочевидно.

  • С использованием НРАВИТСЯ против индексированных колонны, и я почти испытываю искушение просто скажите LIKE в общем.

  • Переработка значений PK, сгенерированных SQL.

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

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

  • А для MS SQL Server использование Курсоры совсем.Есть лучший способ выполнения любой заданной задачи курсора.

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

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

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

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

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

var query = "select COUNT(*) from Users where UserName = '" 
            + tbUser.Text 
            + "' and Password = '" 
            + tbPassword.Text +"'";
<Ол>
  • слепо доверяющий пользовательский ввод
  • Не используется параметризованные запросы
  • пароли открытого текста
  • Мои ошибки - это таблицы доступа из 450 столбцов, которые были собраны 8-летним сыном лучшего друга управляющего директора по собачьей собаке и изворотливой таблицей поиска, которая существует только потому, что кто-то не знает, как правильно нормализовать структуру данных. .

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

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

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

    Больше всего мне не нравятся

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

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

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

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

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

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

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

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

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

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

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

    select some_column, ...
    from some_table
    group by some_column
    

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

    SELECT FirstName + ' ' + LastName as "Full Name", case UserRole when 2 then "Admin" when 1 then "Moderator" else "User" end as "User's 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
    

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

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

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

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

      

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

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

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

    Это применимо, когда:

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

    Временное злоупотребление таблицей.

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

    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)
    

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

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

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

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

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

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

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

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

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

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

    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 в таблицах ImageMediaType и ImageMajorMime с первичными ключами int.

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

    CONVERT(NVARCHAR, GETDATE())
    

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

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

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

    • !Paramed. Может ли запрос иметь более одной цели?Возможно, но следующий человек, который прочитает это, не узнает об этом до глубокой медитации.Даже если они вам сейчас не нужны, скорее всего, они вам понадобятся, даже если это «просто» для отладки.Добавление параметров сокращает время обслуживания и сохраняет СУХОСТЬ.Если у вас есть предложениеwhere, у вас должны быть параметры.

    • Дело не в ДЕЛЕ -

      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  
      

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

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

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

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

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

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

    Использование 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, поскольку внутренний цикл заканчивается. Это может быть фактором в отчетах о нарушениях управления.

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

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

    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top