本文打算给出一些有益的建议,以帮助编写一个更可读的T-SQL查询。
简介
人们对代码的主要期望是它能正常工作并产生预期的输出。然而,这种期望并不是说这段代码好的充分标准,因为现代的应用程序现在是由包括许多开发人员在内的团队开发的。因此,一个开发者不可能永远对其他开发者隐藏他的代码,因为一个代码可能需要其他开发者的阅读、审查、编辑或维护。在这一点上,对代码的理解完全取决于其可读性。
编写一个可读的T-SQL查询
如上所述,一个T-SQL查询也会被许多其他数据库开发人员重新阅读,并且需要编辑它。当一个查询是不可读的,数据库开发人员将花费更多的时间阅读查询,而不是改变查询。最糟糕的情况是,他们可能会误解代码,并以本来不应该改变的方式来改变它。主要是以下规则有助于提高T-SQL查询的可读性。
- 格式化
- 使用注释
- 应用DRY**(Don**'tRepeat Yourself)原则
格式化T-SQL查询
查询格式化可以被定义为将查询代码转化为一个组织良好、易于阅读的格式。一个格式良好的查询可以让开发人员毫不费力地阅读和理解,从而有助于减轻以下工作。
- 维护应用程序的代码
- 调试和修复错误的过程
我们可以看到下面这个查询的混乱情况,很明显,这种情况导致代码非常难以阅读。所有的描述、表名和列名都是杂乱无章的。
DECLAre @NewValue as varchar(100)declare @I AS INT=0 if OBJECT_ID(
N'tempdb..#NewTempTable')is nOT NULL BEgin dROP
TABLE #NewTempTable END CREATe table #NewTempTable(Id inT,Column1 INT)
IF @NewValue<=0WHILE @I<=10 BEgiN INSErt inTO #NewTempTable valUEs(
@NewValue,@NewValue*100)seT @I=@I+1 END IF @NewValue>0 begin select p.ProductID,p.
ProductNumber,p.Name,s.
CarrierTrackingNumber,h.AccountNumber,h.
CreditCardApprovalCode,dbo.[ufnGetStock](p.ProductID)as
Stock,case when AccountNumber likE'10%'
THEN'Account New'ELSE'Account Old'END As
'AccountRename'
,concat(Substring(
CarrierTrackingNumber
,1,4),Substring(p.Class,1,4))froM Sales.SalesOrderDetailEnlarged s Inner jOIN
Production.Product p On s.ProductID=p.ProductID inNER JOin
Sales.
SalesOrderHeaderEnlarged h on
h.SalesOrderID=s.SalesOrderID where s.OrderQty>2aND LEN(
CreditCardApprovalCode)>10ORDer by conCAT(Substring(CarrierTrackingNumber,1,
4),Substring(p.Class,1,4)),ProductID desc end
为了使这段代码更具可读性,我们可以使用各种在线SQL格式化工具,或者可以使用SQL Server Management Studio(SSMS)的第三方SQL格式化插件。如,我们可以使用在线SQL格式化工具或SSMS的插件。
对于Azure Data Studio,一些扩展可以帮助格式化T-SQL查询。可怜的SQL格式器可以成为Azure Data Studio中重新格式化查询的一个选项。
在T-SQL查询中添加足够的描述性注释
"你的代码应该是自我记录的 "是开发人员之间非常有名的口头禅,它意味着不是使用注释,你的代码应该足够清晰,以减少对注释的需求。也许这种方法迫使开发人员写出更多可读的代码,但每个开发人员可能有不同的编码行为,因此添加注释总是会增加代码的可读性。
在T-SQL查询的开头添加一些描述性的注释也会提高查询代码的可读性。这个注释可以很快解释这些查询获取了哪些记录,业务逻辑,表和模式描述。此外,我们可以添加项目管理工具的问题编号。注释的主要特点是,它可以在短时间内清楚地向大家解释查询的细节。下面的查询从生产表中获取了一些记录,同时对每个产品进行了计算。
SELECT Production.Product.Name,
Production.Product.
ProductNumber,(SElect SUM(OrderQty*UnitPrice) from Sales.SalesOrderDetail where
Sales.
SalesOrderDetail.ProductID=
Production.Product.ProductID)AS SalesTotal from Production.
Product
现在,我们重新格式化查询代码,然后为查询和列名添加一些描述性注释。我们在查询描述注释的上方和下方创建边框,然后我们将查询描述添加到其中。同时,我们在列名附近添加列的描述。
/***************************************************************************************
Query Description:
This query returns the main data of the product and
calculates total order amount for each of them
****************************************************************************************/
SELECT P.Name --Name of the product.
, P.ProductNumber --Unique product identification number.
,
(
SELECT SUM
(
OrderQty --Quantity ordered per product.
*
UnitPrice --Selling price of a single product.
)
FROM Sales.SalesOrderDetail SalesLine
WHERE SalesLinE.ProductID = P.ProductID
) AS SalesTotal
FROM Production.Product P
使用列描述
在设计完一个数据库的模式后,记录它是另一个重要的要点。有时这一步可以被跳过,因为它被开发人员和数据库管理员认为是费力和不必要的。然而,当下一个开发者需要解决一些关于所设计的数据库的问题时,模式文档是一个很好的指导原则。至少,添加列描述有助于理解该列在其所属表中的使用目的。
"如果你想为下一个开发者留下一笔财富,就请为你所创建的表添加列描述。"
列描述的另一个好处是,我们可以将其作为注释添加到查询中,以确定列的名称。起初,我们瞥见如何创建列描述。我们可以使用SSMS非常容易地创建列描述。要做到这一点,我们在对象资源管理器中找到我们要创建列描述的表。用右键选择一个表,然后选择设计。
作为下一步,我们选择我们要添加描述的列。所选列的所有属性将在 "列属性"标签中被激活。在这个选项卡中,我们找到描述字段,为该列添加一个简短的描述性解释。
做完这一切后,我们点击保存按钮或按下Ctrl+S组合键。要看这个列的描述,我们可以使用下面的查询。
选择 st.name [Table], sc.name [Column], sep.值 [描述] 从 sys.tables st inner join sys.columns sc on st.object_id = sc.object_id left join sys.extended_properties sep on st.object_id = sep.major_id 和 sc.column_id = sep.minor_id 和 sep.名称 = 'MS_Description'。 WHERE st.name ='CountryListCensus'。 |
扩展属性是SQL Server中的一项功能,用于存储关于数据库对象的更多信息。我们可以使用这个功能来添加更多关于表的描述。要为一个表添加扩展属性,我们右键单击该表,然后选择属性。在表的属性窗口中,我们点击扩展属性并添加表的描述。首先,我们给一个名称,然后添加它的值。
我们可以获得扩展的事件属性来使用下面的查询。
select
st.name [Table],
sc.name [Column],
sep.value [Description]
from sys.tables st
inner join sys.columns sc on st.object_id = sc.object_id
left join sys.extended_properties sep on st.object_id = sep.major_id
and sc.column_id = sep.minor_id
and sep.name = 'MS_Description'
WHERE st.name ='CountryListCensus'
使用别名来提高查询的可读性
在执行查询的过程中,我们可以为表或列指定临时名称,这样可以提高查询的可读性。当一个表的名字非常长且难以理解时,别名将是非常有用的,因为我们可以很容易地将它们转换为更清晰的名字。例如,如果我们在一个SAP数据库中工作,表名就不容易理解。下面的查询获取了一些材料的库存动向。
SELECT ep.*
FROM sys.extended_properties ep
LEFT JOIN sys.objects AS o
ON o.object_id = ep.major_id
LEFT JOIN sys.schemas AS sch
ON sch.schema_id = o.schema_id
LEFT JOIN sys.columns AS clmns
ON clmns.column_id = ep.minor_id
AND clmns.object_id = ep.major_id
WHERE major_id = object_id('CountryListCensus')
AND clmns.object_id IS NULL
我们可以看到,这个查询在阅读时不容易被理解,因为 。
- 没有包括任何关于查询、表、变量和列的注释
- 查询没有良好的格式化。
- 表名是直接使用的
现在我们要解决这些缺点。首先,我们需要添加关于查询和其他对象的简短描述性注释。在格式化查询并为表名指定别名后,查询将变得更加可读。
/***************************************************************************************
Query Description:
This query aims to retrieve stock movements between a certain date.
****************************************************************************************/
DECLARE @DateFrom AS DATE
DECLARE @DateTo AS DATE
SELECT
MatHeader.MBLNR ----Number of material document
, MatHeader.MJAHR ----Material document Year
, MatData.MATNR ----Material number
, MatData.MATKL ----Material oldNumber
, MatMovement.MENGE ----Movement amount
, MatMovement.BWART ----Movement type
, MatMovement.LGORT ----Movement warehouse number
FROM SAP.MSEG MatMovement--- MSEG stores the material movement details
JOIN SAP.MKPF MatHeader ----MKPF is an standart Material Managment which stores header data of the material movements
ON MatHeader.mandt = MatMovement.mandt
AND MatHeader.MJAHR = MSEG.MJAHR
AND MatHeader.MBLNR = MSEG.MBLNR
JOIN SAP.MARA MatData ----MARA stores general material data
ON MARA.MANDT = MSEG.MANDT
AND MARA.MATNR = MSEG.MATNR
WHERE MKPF.CPUDT BETWEEN @DateFrom AND @DateTo
DRY(Don't Repeat Yourself)原则
DRY原则的主要目标是减少重复编写相同的代码片断。我们可以使用用户定义的函数在T-SQL查询中实现这一原则,因为用户定义的函数允许我们封装查询并从任何地方调用它们。主要来说,有3种不同的用户定义函数类型。
标量值函数返回一个单一的值。
多语句表值 函数返回一个表作为输出,这个输出表结构可以由用户定义。MSTVF可以只包含一条语句,也可以包含一条以上的语句。
Inline Table-Valued函数是一个用户定义的函数,它返回一个表的数据类型,同时它可以接受参数。
下面的查询返回生产表的产品名称,产品编号,以及每个产品的销售总额。
SELECT Name,ProductNumber,
(SELECT ISNULL(SUM(LineTotal),0)
FROM Sales.SalesOrderDetail Sales WHERE Sales.ProductID =P.ProductID) AS SalesTotal
FROM Production.Product P
现在,我们将把这个查询转换为一个更可读的查询。首先,我们创建一个标量值的函数来代替子查询。
CREATE FUNCTION Sales.CalculateProductSales (@ProductId INTEGER)
RETURNS FLOAT
/***************************************************************************************
Function Description:
This function calculates the total sales for each product
****************************************************************************************/
AS
BEGIN
DECLARE @Total FLOAT
SELECT @Total = ISNULL(SUM(LineTotal), 0)
FROM Sales.SalesOrderDetail
WHERE ProductID = @ProductId
RETURN @Total
END
下一步,我们将在查询中实现这个标量值函数,我们还将在查询中添加所需的注释。
/***************************************************************************************
Query Description:
This query aims to fetch each product sales amount
****************************************************************************************/
SELECT P.Name
, --Name of the product
P.ProductNumber
, --Unique product identification number.
Sales.CalculateProductSales(P.ProductID) AS SalesTotal
FROM Production.Product P;
--Products sold or used in the manufacturing of sold products.
不要使用 "SELECT *"语句
SELECT *"语句返回被查询表的所有列,使用起来非常方便。然而,我们必须在查询中明确指定列名。这条规则的原因是为了明确查询需要哪些列,所以它有助于提高查询的可读性。当然,避免使用 "SELECT *"也有其他好处。
- 防止不必要的I/O操作
- 增加网络流量
总结
在这篇文章中,我们学到了一些有用的方法,有助于提高T-SQL查询的可读性。