SQL应该格式化输出还是只检索原始数据?

时间:2009-05-30 08:20:15

标签: sql

一般来说,我编写的SQL查询会返回未格式化的数据,并将其留给表示层,网页或Windows应用程序,以根据需要格式化数据。与我合作的其他人,包括我的老板,将坚持让数据库更有效率。我不确定我是否会购买它,并且相信即使通过让数据库执行可获得的可衡量的性能提升,也有更有说服力的理由可以避免这种情况。

例如,我会将查询放在数据访问层中,以便尽可能重用查询。鉴于此,我确定如果数据保持其本机类型而不是将数据转换为字符串并在其上应用格式化函数,则更有可能重用查询,例如,将日期列格式化为DD -MMM-YYYY格式用于显示。当然,如果SQL将日期作为格式化字符串返回,您可以反转该过程以将值恢复为日期数据类型,但这似乎很尴尬,因为缺少更好的单词。 Furtehrmore,当涉及到格式化其他数据时,例如,机器序列号由前缀,基数和后缀组成,每个子字段中删除了分隔符号和前导零,您可能会有可能无法正确向另一个方向转回原始序列号。也许这是一个不好的例子,但我希望你能看到我的方向......

为了更进一步,我看到人们编写非常复杂的SQL,因为它们本质上是将我称之为表示逻辑的内容写入SQL而不是返回简单数据,然后在表示层中应用此表示逻辑。在我看来,这导致非常复杂,难以维护和更脆弱的SQL,不太适应变化。

以我在系统中发现的实际例子为例,告诉我你的想法。我给这种方法的理性是,这使得Web应用程序非常简单地呈现页面,因为它在经典ASP Web应用程序中使用以下1行经典ADO逻辑片段来处理返回的行:

     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实际上是使用嵌入在经典ASP页面中的VB脚本构建的,而不是调用存储过程,所以你有嵌入式连接和引用标记的额外复杂性,如果你知道我的意思,不是提到缺乏格式。当我被要求帮助调试SQL时,我做的第一件事就是添加SQL输出的debug.print并将其抛出我刚刚找到的SQL格式化程序。在粘贴以下链接时丢失了一些格式:

编辑(Andomar):复制内联:(删除外部链接,谢谢 - 乍得)

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 个答案:

答案 0 :(得分:16)

答案显然是“只检索输出”。在SQL Server上格式化具有以下问题:

  • 它增加了来自SQL Server的网络流量
  • SQL的字符串处理功能很差
  • SQL服务器未优化以执行字符串操作
  • 您正在使用可以更好地用于查询处理的服务器CPU周期
  • 它可能会使查询优化器生活困难(或不可能)
  • 您必须编写更多查询以支持不同的格式
  • 您可能必须编写不同的查询以支持在不同浏览器上进行格式化
  • 您不能为不同目的重复使用查询

我相信还有更多。

答案 1 :(得分:6)

SQL应该格式化,期间。它是一个关系代数,用于从数据库中提取(使用SELECT时)数据。

让DBMS为您格式化数据是错误的,这应该留给您自己的代码(在DBMS之外)。 DBMS通常负载不足,无需为您进行演示工作。它还针对数据检索进行了优化,而不是演示。

我知道如果我尝试做类似的事情,会立即执行的DBA: - )

答案 2 :(得分:5)

在SQL中格式化输出的概念确实打破了表示和数据分离的整个概念,不仅如此,还有许多条件可能出现:

  • 如果您需要本地化日期格式,该怎么办?英国对美国使用不同的日期格式, 例如 - 您是否一直在国际化回到您的数据层?

  • 如果格式规则发生变化怎么办?即某些文本需要以不同的方式进行格式化以符合一些新的公司政策吗?同样,您需要一直回到数据层。

  • 如果我们采用网络环境,您如何决定转义价值?如果要输出到网页,JSON或其他地方,可能需要使用不同形式的转义...

不仅如此,SQL字符串操作函数通常也不是非常活跃。

答案 3 :(得分:2)

我是负责我公司产品报告引擎的开发人员。简单来说,引擎的工作原理是构建数据的XML文档,从数据库进入报表,然后根据用户需求以任何方式转换XML以构建Web页面,PDF或Word文档。

五年前,当我开始使用数据库格式化输出时,虽然我很高兴地说我写的内容与问题示例一样可怕。随着时间的推移,我已经采取了另一种方式,现在XML只保留原始数据,并在演示期间对其进行了整理。

我们的软件使用交通信号灯作为快速一览状态指示器,因此我们在数据库中有很多字段存储'R','A','G','U'代表红色,琥珀色,绿色和未知。我有一些技巧,如带有嵌入式CASE语句的SELECTS,可以将单个字符代码转换为英语对应的代码:

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 

这只是一个简短的例子。我还有其他技巧来进行日期格式化,名称组合等等。

这当然导致了应用程序多语言的问题,因为英语被煮沸到数据库中。我需要查找客户区域设置并编写大量多语言CASES以支持其他语言。不好。日期也是一个问题。美国人喜欢他们的日期mm / dd和欧洲人做dd / mm。

它还导致了其他重复问题。如果有人添加了第四个或第五个红绿灯选项,我必须修改我的所有SQL,当新状态已经在代码中表示为Java枚举或其他东西时,我可以在我从数据库中读取单个字符后查找。 / p>

在我的情况下,让数据库返回原始数据以及编写一套比较器和格式化程序以用用户的母语和编码在文档中显示数据变得更加容易。如果我今天重新开始这就是我要做的事。

答案 4 :(得分:1)

我认为在SQL的出路上有一些转换的地方,这取决于调用程序的期望。

例如,如果日期时间合适,则应以原生方式返回。另一方面,如果您只返回datetime字段中的一年(或1/4,4 / 1,7 / 1,10 / 1等四分之一),并且客户端需要解析信息,把它放在一个单独的栏目中(如年份= 2008或季度='2008Q1')。从代码到描述的一些代码转换(删除代码列并仅发出描述)。在合理的情况下,连接和字符串构建是合适的。

您的特定示例是一个不合适的地方,而表面看起来更松散耦合(只更改数据库中的SP)它实际上可以通过强制为不同的用法而不是多个写入额外的SP来创建更强的耦合用户界面能够使用相同的SP。然后,随着系统的发展,可能需要同步更改多个SP。

答案 5 :(得分:1)

在考虑是否代表您的表示层格式化数据时,请考虑您的“表示层”可能是Web服务或其他程序。您可以首先代表一段UI代码进行格式化,然后才需要Web服务使用相同的查询,这将有不同的要求。

我的最爱是一组存储过程,所有存储过程都格式化日期/时间。在当地时区。当来自不同时区的Web服务调用时,它不能很好地工作。当数据库服务器的区域设置发生更改,更改日期/时间格式时,它的效果更差。哦,它在午夜没有用,因为它最后截断了“00:00”。

OTOH,UI非常方便。

答案 6 :(得分:0)

我认识的大多数人在这里不同意我,但我有点喜欢这种做法。所以我会列出一些优点:

  • SQL非常强大:此查询需要多少行C#?
  • SQL非常容易更新。我想这个代码是在一个存储过程中,您可以使用简单的ALTER PROC进行更改。这可以大大减少修复时间。
  • SQL很快;我已经看到引入ORM层会使应用程序陷入爬行的情况。
  • SQL易于调试,错误很容易重现。只需运行查询。测试修复程序是运行新查询的问题。
  • 这样的SQL在格式正确的情况下并不难维护。没有多少SQL我在5-10分钟内无法理解;但是多层C#解决方案可能需要很长时间,特别是如果你必须弄清楚哪个层的抽象正在破坏。

我确信其他人会列出SQL方法的缺点。