2010-09-22 2 views
1

Я считаю себя достаточно опытным с T-SQL, и я обычно могу оптимизировать запрос довольно хорошо, не теряя удобочитаемости. Короче: мне нравится мой SQL короткий, описательный, декларативный и элегантный.Как преобразовать следующий код в SQL Server/T-SQL CTE?

Хотя следующий код работает, у меня есть две проблемы, связанные с ним:

  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 

Поскольку она не может быть совершенно очевидно, что я пытаюсь сделать здесь сродни TOTHE следующие квази SQL

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

В основном, чтобы получить последний уникальный относится (не уникальные строки) , которые часто содержат дубликаты и в порядке убывания. Это определенно, где это становится сложно.

Данные довольно простые протоколы HTTP. Поле идентификатора - это просто уникальный идентификатор строки, Url - полный запрос URL, а Referer - HTTP-референт для этого запроса. Ни одно из значений не может быть нулевым, но референт может быть пустым (т.е. ''). IsSiteLocal - это просто простая функция фильтрации, чтобы исключить ссылки, исходящие из моих собственных сайтов.

Если кто-то хочет, чтобы некоторые образцы данных были заполнены, я могу загрузить небольшую резервную копию базы данных, чтобы у вас было что-то, с чем можно было обмануть.

Sample-данные можно найти здесь: http://svada.kjonigsen.net/files/IISLogsDBBackup.zip

+0

Возможно, что-то в некоторых примерах данных? –

+0

Хотя это значительно больше, чем мое, оно все еще не ведет себя абсолютно правильно или, как я хочу.Следующий код выборки использовать SQL запеченный в небольшой тест: DECLARE @Limit Int = 20 ; С Referers А.С. ( \t Выбор TOP (@Limit) l.Url, l.Referer, MAX (ID) AS ' ID» \t из журнала л \t где 0 = dbo.IsLocalSite (l.Referer) \t И l.Referer <> '' \t GROUP BY l.Url, l.Referer \t ORDER BY 3 убывание ) , JustReferers AS ( \t SELECT DISTINCT Referer \t ОТ Referer s ) SELECT COUNT (*) ОТ JustReferers -! = @Limit для достаточно большого количества @Limit. Вызывает неправильный пейджинг. –

+0

вы можете добавить некоторые примеры данных, которые нам не нужно загружать? – DForck42

ответ

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

Спасибо за ответ, но это прозвище. Я уточню вопрос, чтобы описать его более подробно. –

0

вы хотите макс (ID) для каждой страницы, Referer, пока dbo.IsLocalSite (@Referer) = 0? Можете ли вы просто сгруппировать по Url, Referer, чтобы получить max (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; 
Смежные вопросы