如何编写可读的T-SQL查询

44 阅读8分钟

本文打算给出一些有益的建议,以帮助编写一个更可读的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中重新格式化查询的一个选项。

Using query formatting tool

在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非常容易地创建列描述。要做到这一点,我们在对象资源管理器中找到我们要创建列描述的表。用右键选择一个表,然后选择设计

SQL Server Management Studio design table

作为下一步,我们选择我们要添加描述的列。所选列的所有属性将在 "列属性"标签中被激活。在这个选项卡中,我们找到描述字段,为该列添加一个简短的描述性解释。

Adding column description to use 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'

Listing column descriptions

扩展属性是SQL Server中的一项功能,用于存储关于数据库对象的更多信息。我们可以使用这个功能来添加更多关于表的描述。要为一个表添加扩展属性,我们右键单击该表,然后选择属性。在表的属性窗口中,我们点击扩展属性并添加表的描述。首先,我们给一个名称,然后添加它的值。

Using Extended Properties in 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'
 

Formatting a T-SQL query

使用别名来提高查询的可读性

在执行查询的过程中,我们可以为表或列指定临时名称,这样可以提高查询的可读性。当一个表的名字非常长且难以理解时,别名将是非常有用的,因为我们可以很容易地将它们转换为更清晰的名字。例如,如果我们在一个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查询的可读性。