2010-09-22 50 views
1

我认为自己对T-SQL非常熟练,而且我通常能够在不丢失可读性的情况下优化查询。简而言之:我喜欢我的SQL简洁,描述性,声明性和优雅。如何将以下代码转换为SQL Server/T-SQL CTE?

虽然下面的代码工作,我有两个问题是:

  1. 我使用游标,我不能动摇的感觉我在我的后脑勺,它本来可以做更多有效地使用CTE。另外游标不能在视图中工作,所以我不能在客户端或从属SQL中处理结果/范围。
  2. 该代码在存储过程中实现,导致与上面相同的问题。特别是使用LInQ进行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 

由于它可能不是完全明显,我想在这里做的是类似于tothe下列准SQL

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

基本上得到最后独特指(不是唯一的行) ,通常包含重复项,并按降序排列。这绝对是棘手的问题。

这些数据是非常简单的HTTP日志。 ID字段只是一个唯一的行标识符,Url是完整的url requesten,Referer是该请求的HTTP引用者。没有一个值可以为null,但引用者可以为空(即'')。 IsSiteLocal只是一个简单的过滤功能,用于排除源自我自己站点的引用者。

如果有人希望得到一些样本数据,我可以上传一个小型的数据库备份,这样你就可以得到一些东西。

抽样数据可以在这里找到: http://svada.kjonigsen.net/files/IISLogsDBBackup.zip

+0

某些样本数据可能是? – 2010-09-22 11:42:58

+0

尽管这比我的更重要,但它仍然不完全正确,或者不符合我的要求。下面的代码样本使用SQL烘烤成小的测试:DECLARE @Limit INT = 20 ; WITH查阅情况AS ( \t SELECT TOP(@Limit)l.Url,l.Referer,MAX(ID)AS' ID” \t FROM登录升 \t其中0 = dbo.IsLocalSite(l.Referer) \t AND l.Referer <> '' \t GROUP BY l.Url,l.Referer \t ORDER BY 3递减 ) ,JustReferers AS ( \t SELECT DISTINCT Referer \t FROM Referer (*) FROM JustReferers - != @Limit足够大的@Limit数量。将导致不正确的分页。 – 2010-09-22 13:04:36

+0

你可以添加一些我们不需要下载的示例数据吗? – DForck42 2010-09-22 18:12:43

回答

0

试试这个:

;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 
+0

感谢您的回复,但它有点遗漏。我会更新这个问题以便更具描述性。 – 2010-09-22 12:00:43

0
你想每个URL的MAX(ID)

,Referer的只要dbo.IsLocalSite(@Referer) = 0? 您是否可以通过Url,Referer进行分组以获得最大(ID)并将其应用于WHERE子句中?

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;