Должен ли SQL форматировать вывод или просто получать необработанные данные? - PullRequest
4 голосов
/ 30 мая 2009

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

Например, я буду помещать свои запросы в слой доступа к данным с целью возможного повторного использования запросов, когда это возможно. Исходя из этого, я констатирую, что запросы с большей вероятностью можно будет повторно использовать, если данные остаются в своем собственном типе, а не преобразовывать данные в строку и применять к ним функции форматирования, например форматирование столбца даты в DD -МММ-ГГГГ формат для отображения. Конечно, если SQL возвращал даты в виде отформатированных строк, вы можете повернуть процесс вспять, чтобы вернуть значение обратно в тип данных date, но это кажется неудобным из-за отсутствия лучшего слова. Более того, когда дело доходит до форматирования других данных, например, серийного номера машины, состоящего из префикса, базы и суффикса с разделительными чертами и ведущими нулями, удаленными в каждом подполе, вы рискуете возможностью того, что вы не сможете правильно вернитесь к исходному серийному номеру, когда будете двигаться в другом направлении. Может быть, это плохой пример, но я надеюсь, что вы видите направление, в котором я иду с этим ...

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

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

     oRS.GetString ( , , "</td>" & vbCrLf & "<td style=""font-size:x-small"" nowrap>" ,"</td>" & vbCrLf & "</tr>" & vbCrLf & "<tr>" & vbCrLf & _
     "<td style=""font-size:x-small"" nowrap>" ,"&nbsp;" ) & "</td>" & vbCrLf & "</tr>" & vbCrLf & _

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

Поскольку SQL не отображался с CR / LF, когда я вставлял сюда, я решил разместить SQL на пустом персональном сайте Google. Пожалуйста, не стесняйтесь комментировать. Спасибо.

Кстати, этот SQL на самом деле был создан с использованием VB Script, вложенного в классическую страницу ASP, без вызова хранимой процедуры, поэтому у вас есть дополнительная сложность встроенных конкатенаций и разметки в кавычках, если вы знаете, что я имею в виду, а не упомянуть отсутствие форматирования. Первое, что я сделал, когда меня попросили помочь отладить SQL, - это добавить debug.print к выводу SQL и передать его через средство форматирования SQL, которое я только что нашел. Некоторая часть форматирования была потеряна при вставке по следующей ссылке:

Редактировать (Andomar): скопировано inline: (внешняя ссылка удалена, спасибо-Чад)

SELECT 
Substring(Datename("dw",start_datetime),1,3) 
+ ', '
+ Cast(start_datetime AS VARCHAR) "Start Time (UTC/GMT)"
,program_name "Program Name"
,run_sequence "Run Sequence"
,CASE 
WHEN batchno = 0
THEN Char(160)
WHEN batchno = NULL
THEN Char(160)
ELSE Cast(batchno AS VARCHAR)
END "Batch #" /* ,Replace(Replace(detail_log ,'K:\' ,'file://servernamehere/DiskVolK/') ,'\' ,'/') "log"*/ /* */
,Cast('<a href="GOIS_ViewLog.asp?Program_Name=' AS VARCHAR(99))
+ Cast(program_name AS VARCHAR)
+ Cast('&Run_Sequence=' AS VARCHAR)
+ Cast(run_sequence AS VARCHAR)
+ Cast('&Page=1' AS VARCHAR)
+ ''
+ Cast('">'
+ CASE 
WHEN end_datetime >= start_datetime
THEN CASE 
WHEN end_datetime <> 'Jan 1 1900 2:00 PM'
THEN CASE 
WHEN (success_code = 10
OR success_code = 0)
AND exit_code = 10
THEN CASE 
WHEN errorcount = 0
THEN 'Completed Successfully'
ELSE 'Completed with Errors'
END
WHEN success_code = 100
AND exit_code = 10
THEN 'Completed with Errors'
ELSE CASE 
WHEN program_name <> 'FileDepCheck'
THEN 'Failed'
ELSE 'File not found'
END
END
ELSE CASE 
WHEN success_code = 10
AND exit_code = 0
THEN 'Failed; Entries for Input File Missing'
ELSE 'Aborted'
END
END
ELSE CASE 
WHEN ((Cast(Datediff(mi,start_datetime,Getdate()) AS INT) <= 240)
OR ((SELECT 
Count(* )
FROM 
MASTER.dbo.sysprocesses a(nolock)
INNER JOIN gcsdwdb.dbo.update_log b(nolock)
ON a.program_name = b.program_name
WHERE a.program_name = update_log.program_name
AND (Abs(Datediff(n,b.start_datetime,a.login_time))) < 1) > 0))
THEN 'Processing...'
ELSE 'Aborted without end date'
END
END
+ '</a>' AS VARCHAR) "Status / Log"
,Cast('<a href="' AS VARCHAR)
+ Replace(Replace(detail_log,'K:\','file://servernamehere/DiskVolK/'),
'\','/')
+ Cast('" title="Click to view Detail log text file"' AS VARCHAR(99))
+ Cast('style="font-family:comic sans ms; font-size:12; color:blue"><img src="images\DetailLog.bmp" border="0"></a>' AS VARCHAR(999))
+ Char(160)
+ Cast('<a href="' AS VARCHAR)
+ Replace(Replace(summary_log,'K:\','file://servernamehere/DiskVolK/'),
'\','/')
+ Cast('" title="Click to view Summary log text file"' AS VARCHAR(99))
+ Cast('style="font-family:comic sans ms; font-size:12; color:blue"><img src="images\SummaryLog.bmp" border="0"></a>' AS VARCHAR(999)) "Text Logs"
,errorcount "Error Count"
,warningcount "Warning Count"
,(totmsgcount
- errorcount
- warningcount) "Information Message Count"
,CASE 
WHEN end_datetime > start_datetime
THEN CASE 
WHEN Cast(Datepart("hh",(end_datetime
- start_datetime)) AS INT) > 0
THEN Cast(Datepart("hh",(end_datetime
- start_datetime)) AS VARCHAR)
+ ' hr '
ELSE ' '
END
+ CASE 
WHEN Cast(Datepart("mi",(end_datetime
- start_datetime)) AS INT) > 0
THEN Cast(Datepart("mi",(end_datetime
- start_datetime)) AS VARCHAR)
+ ' min '
ELSE ' '
END
+ CASE 
WHEN Cast(Datepart("ss",(end_datetime
- start_datetime)) AS INT) > 0
THEN Cast(Datepart("ss",(end_datetime
- start_datetime)) AS VARCHAR)
+ ' sec '
ELSE ' '
END
ELSE CASE 
WHEN end_datetime = start_datetime
THEN '< 1 sec'
ELSE CASE 
WHEN ((Cast(Datediff(mi,start_datetime,Getdate()) AS INT) <= 240)
OR ((SELECT 
Count(* )
FROM 
MASTER.dbo.sysprocesses a(nolock)
INNER JOIN gcsdwdb.dbo.update_log b(nolock)
ON a.program_name = b.program_name
WHERE a.program_name = update_log.program_name
AND (Abs(Datediff(n,b.start_datetime,a.login_time))) < 1) > 0))
THEN 'Running '
+ Cast(Datediff(mi,start_datetime,Getdate()) AS VARCHAR)
+ ' min'
ELSE '&nbsp;'
END
END
END "Elapsed Time" /* ,end_datetime "End Time (UTC/GMT)" ,datepart("hh" ,
(end_datetime - start_datetime)) "Hr" ,datepart("mi" ,(end_datetime - start_datetime)) "Mins" ,datepart("ss" ,(end_datetime - start_datetime)) "Sec" ,datepart("ms" ,(end_datetime - start_datetime)) "mSecs" ,datepart("dw" ,start_datetime) "dp" ,case when datepart("dw" ,start_datetime) = 6 then ' Fri' when datepart("dw" ,start_datetime) = 5 then ' Thu' else '1' end */
,totalrows "Total Rows"
,inserted "Rows Inserted"
,updated "Rows Updated" /* ,success_code "succ" ,exit_code "exit" */
FROM 
update_log
WHERE start_datetime >= '5/29/2009 16:15'
ORDER BY start_datetime DESC

Ответы [ 7 ]

16 голосов
/ 30 мая 2009

Ответ, очевидно, «просто получить вывод». Форматирование на сервере SQL имеет следующие проблемы:

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

Я уверен, что есть еще много.

6 голосов
/ 30 мая 2009

SQL должен не быть форматированием, точка. Это реляционная алгебра для извлечения (при использовании SELECT) данных из базы данных.

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

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

5 голосов
/ 30 мая 2009

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

  • Что если вам нужно локализовать форматы даты? Великобритания использует другой формат даты для США, например - вы собираетесь полностью интернационализироваться вплоть до уровня данных?

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

  • Если мы возьмем веб-контекст, как вы решите экранировать значения? Различные формы экранирования могут быть желательны, если вы выводите на веб-страницу, или в JSON, или в другое место ...

Не только это, но и функции манипуляции со строками SQL обычно не очень быстры.

2 голосов
/ 30 мая 2009

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

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

Наше программное обеспечение использует светофоры в качестве быстрого индикатора состояния, поэтому в базе данных имеется много полей с символами, в которых R, A, G, U обозначаются красным, янтарный, зеленый и неизвестный. У меня было несколько трюков, таких как SELECTS со встроенными операторами CASE для преобразования односимвольных кодов в их английские аналоги:

SELECT CASE status WHEN 'R' THEN 'Red' WHEN 'G' THEN 'Green' ...etc... 

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

SELECT
    CASE status WHEN 'R' THEN 'Red' WHEN 'G' THEN 'Green' WHEN 'A' THEN 'Amber' END as status,
    CASE status WHEN 'R' THEN 0 WHEN 'A' THEN 1 WHEN 'G' THEN 2 END as sort
FROM
    table
ORDER BY
    sort 

Это только краткий пример. У меня были другие приемы для форматирования даты, сборки имен и т. Д.

Это, конечно, привело к проблемам в создании многоязычного приложения, поскольку английский язык кипит в базе данных. Мне нужно найти локаль клиента и написать много многоязычных СЛУЧАЙ для поддержки других языков. Нехорошо. Также даты были проблемой. Американцы любят свои даты мм / дд, а европейцы делают дд / мм.

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

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

1 голос
/ 30 мая 2009

При рассмотрении вопроса о том, следует ли форматировать данные от имени уровня представления, учтите, что вашим «уровнем представления» может быть веб-служба или другая программа. Вы можете начать с форматирования от имени фрагмента кода пользовательского интерфейса, но позже потребуется тот же запрос, который будет использоваться веб-службой, что будет иметь другие требования.

Моим фаворитом был набор хранимых процедур, которые все форматировали дату / время. В местном часовом поясе. Он не работал так хорошо, когда вызывался веб-сервисом из другого часового пояса. Это работало еще хуже, когда менялись региональные настройки сервера базы данных, меняя формат даты / времени. О, и это не сработало в полночь, так как в конце оно обрезало "00:00".

OTOH, это было очень удобно для пользовательского интерфейса.

1 голос
/ 30 мая 2009

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

Например, если дата и время подходят, они должны быть возвращены изначально. С другой стороны, если вы возвращаете только год в поле даты и времени (или квартал, например 1/1, 4/1, 7/1, 10/1), и клиент должен проанализировать информацию, поместите его в отдельный столбец (например, год = 2008 или квартал = '2008Q1'). Некоторые переводы кода из кода в описание (удаление столбца кода и выдача только описания). Существуют разумные случаи, когда целесообразно объединение и построение строк.

Ваш конкретный пример - это место, где это неуместно, и, хотя на первый взгляд это выглядит как более слабое связывание (только изменение SP в базе данных), оно может на самом деле создать более сильное связывание, заставляя писать дополнительные SP для разных видов использования вместо нескольких Пользовательские интерфейсы могут использовать один и тот же SP. А затем, возможно, потребуется синхронизировать несколько SP по мере развития системы.

0 голосов
/ 30 мая 2009

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

  • SQL очень мощный: сколько строк в C # займет этот запрос?
  • SQL очень легко обновить. Я предполагаю, что этот код находится в хранимой процедуре, которую вы можете изменить с помощью простого ALTER PROC. Это может значительно сократить время наложения исправлений.
  • SQL быстрый; Я видел случаи, когда введение слоя ORM замедляло приложение для сканирования.
  • SQL легко отлаживать, а ошибки легко воспроизводить. Просто запустите запрос. Тестирование вашего исправления - это вопрос запуска нового запроса.
  • Подобный SQL не так уж сложно поддерживать, если он правильно отформатирован. Там не так много SQL, я не могу понять в течение 5-10 минут; но многослойные решения C # могут занять очень много времени, особенно если вам нужно выяснить, какая абстракция слоя нарушается.

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

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