【问题标题】:How do I convert the following code to a SQL Server/T-SQL CTE?如何将以下代码转换为 SQL Server/T-SQL CTE?
【发布时间】:2012-11-30 21:45:12
【问题描述】:

我认为自己对 T-SQL 相当精通,而且我通常能够很好地优化查询而不会失去可读性。简而言之:我喜欢我的 SQL 简短、描述性、声明性和优雅。

虽然以下代码有效,但我有两个问题:

  1. 我正在使用游标,我无法摆脱我脑后的感觉,即使用 CTE 可以更有效地完成它。游标在视图中也不起作用,因此我无法在客户端或依赖 SQL 中操作结果/范围。
  2. 代码是在存储过程中实现的,这会导致与上述相同的问题。尤其是使用 LInQ to SQL 和自动分页。

因此,鉴于以下 SP,是否有人看到任何明显的方法可以使用递归 CTE 将其转换为普通选择?我试过了,但失败了,我想看看堆栈溢出社区可能会想出什么。

SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE PROC [dbo].[usp_GetLastReferers]
(
 @Limit int = NULL
)
AS
BEGIN
 SET NOCOUNT ON

 CREATE TABLE #Referer
 (
  ID   int,
  Url   nvarchar(500),
  Referer  nvarchar(500)
 )

 DECLARE @ID   int
 DECLARE @Url  nvarchar(500)
 DECLARE @Referer nvarchar(500)
 DECLARE @Count  int
 SET @Count = 0

 DECLARE LogCursor CURSOR FORWARD_ONLY READ_ONLY FOR
 SELECT ID, Url, Referer FROM Log WHERE Referer <> '' ORDER BY ID DESC

 OPEN LogCursor

 FETCH NEXT FROM LogCursor INTO @ID, @Url, @Referer

 WHILE @@FETCH_STATUS = 0 AND (@Count < @Limit OR @Limit IS NULL)
 BEGIN
  DECLARE @Hits int
  SELECT @Hits = COUNT(*)
  FROM #Referer
  WHERE Referer = @Referer

  DECLARE @IsLocal bit
  SELECT @IsLocal = dbo.IsLocalSite(@Referer)

  IF (@Hits = 0 OR @Hits IS NULL) AND @IsLocal = 0
  BEGIN
   INSERT INTO #Referer(ID,Url,Referer) VALUES (@ID,@Url,@Referer)
   SET @Count = @Count + 1
  END

  FETCH NEXT FROM LogCursor INTO @ID, @Url, @Referer  
 END

 CLOSE LogCursor
 DEALLOCATE LogCursor

 SELECT *
 FROM #Referer

 DROP TABLE #Referer

 SET NOCOUNT OFF
END

由于它可能并不完全明显,我在这里尝试做的类似于以下准 SQL

SELECT DISTINCT TOP(@Limit) ID, Url, Referer
FROM Log 
ORDER BY ID DESC

基本上是获取最后一个唯一引用(不是唯一的行),其中通常包含重复项,并且按降序排列。这绝对是棘手的地方。

数据是非常简单的 HTTP 日志。 ID 字段只是一个唯一的行标识符,Url 是完整的 url requesten,Referer 是该请求的 HTTP referer。任何值都不能为空,但referer 可以为空(即'')。 IsSiteLocal 只是一个简单的过滤功能,用于排除来自我自己网站的引荐来源。

如果有人想要完整的一些示例数据,我可以上传一个小的数据库备份,这样你就可以玩弄一些东西了。

可在此处找到示例数据: http://svada.kjonigsen.net/files/IISLogsDBBackup.zip

【问题讨论】:

  • 虽然这比我的要简洁得多,但它的行为仍然不完全正确或如我所愿。以下代码示例使用您的 SQL 烘焙到一个小测试中: DECLARE @Limit int = 20 ;WITH Referers AS ( SELECT TOP(@Limit) l.Url, l.Referer, MAX(ID) AS 'ID' FROM Log l WHERE 0 = dbo.IsLocalSite(l.Referer) AND l.Referer '' GROUP BY l.Url, l.Referer ORDER BY 3 desc ) , JustReferers AS ( SELECT DISTINCT Referer FROM Referers ) SELECT COUNT(*) FROM JustReferers -- != @Limit 用于足够多的@Limit。会导致分页不正确。
  • 你能添加一些我们不需要下载的示例数据吗?
  • 博文底部链接了具有足够数据的数据库备份。

标签: sql-server tsql common-table-expression


【解决方案1】:

为什么要将其转换为递归 CTE ? 它没有理由不能作为普通选择运行。

我下载了测试数据库,它缺少你的 dbo.IsLocalSite 函数,所以为了我的测试,我创建了自己的同名函数并假设它总是返回 0。

这段代码在运行时产生了与上面给出的存储过程完全相同的结果:

SELECT TOP (@Limit) ID, Url, Referer
FROM (
    SELECT ID, Url, Referer, RANK() OVER (PARTITION BY Referer ORDER BY ID DESC) _RANK_
    FROM LOG
    WHERE dbo.IsLocalSite(Referer) = 0
    AND Referer != ''
) TT
WHERE _RANK_ = 1
ORDER BY ID DESC;

【讨论】:

    【解决方案2】:

    试试这个:

     ;with Referers as (
      SELECT 
      row_number() over (order by id desc) rn 
      ,ID, Url, Referer FROM Log 
      WHERE dbo.IsLocalSite(Referer) = 0 
    ) 
    select * from Referers
    where rn <= @limit
    

    【讨论】:

    • 感谢您的回复,但有点错过。我将更新问题以使其更具描述性。
    【解决方案3】:

    只要 dbo.IsLocalSite(@Referer) = 0,您想要每个 Url,Referer 的 max(ID) 吗? 您可以按 Url,Referer 分组以获取 max(ID) 并在 WHERE 子句中应用您的函数吗?

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-04-15
      • 1970-01-01
      • 2017-06-03
      • 1970-01-01
      相关资源
      最近更新 更多