Странная проблема SQL - PullRequest
1 голос
/ 20 февраля 2009

У меня действительно странная проблема с SQL-запросом, с которым я работал в течение некоторого времени. Я использую SQL Server 2005.

Вот пример таблицы, из которой сделан запрос:

<b>Log:</b>
Log_ID | FB_ID |   Date    | Log_Name | Log_Type

   7   |   4   | 2007/11/8 |   Nina   |  Critical
   6   |   4   | 2007/11/6 |   John   |  Critical
   5   |   4   | 2007/11/6 |   Mike   |  Critical
   4   |   4   | 2007/11/6 |   Mike   |  Critical
   3   |   3   | 2007/11/3 |   Ben    |  Critical
   2   |   3   | 2007/11/1 |   Ben    |  Critical

Теперь идея состоит в том, чтобы вернуть первую дату для Log_Person, работающего с каждым FB_ID, однако, в случае нескольких Log_Names, я хочу только SECOND Log_Name (впервые ответственность передается другой). Результат должен выглядеть так:

<b>Desired result</b>
Log_ID | FB_ID |   Date    | Log_Name | Log_Type

   6   |   4   | 2007/11/6 |   John   |  Critical
   2   |   3   | 2007/11/1 |   Ben    |  Critical

В более ранней теме Питер Лэнг и Кассной дали блестящие ответы, которые приведены ниже. К сожалению, я едва могу понять, что там происходит, но они работали как шарм. Вот код:

<b>Quassnoi</b>

SELECT lo4.*
FROM
    (SELECT CASE WHEN ln.log_id IS NULL THEN lo2.log_id ELSE ln.log_id END
    AS log_id, ROW_NUMBER() OVER (PARTITION BY lo2.fb_id ORDER BY lo2.cdate) AS rn 
    FROM 
        (SELECT lo.*,
            (SELECT TOP 1 log_id
            FROM t_log li WHERE li.fb_id = lo.fb_id AND li.cdate >= lo.cdate
            AND li.log_id  lo.log_id AND li.log_name  lo.log_name
            ORDER BY cdate, log_id)
        AS next_id
        FROM t_log lo)
    lo2 LEFT OUTER JOIN t_log ln ON ln.log_id = lo2.next_id)
lo3, t_log lo4
WHERE lo3.rn = 1 AND lo4.log_id = lo3.log_id
<hr/>
<b>Peter Lang</b>

SELECT *
FROM log
WHERE log_id IN
    (SELECT MIN(log_id) FROM log 
    WHERE
        (SELECT COUNT(DISTINCT log_name)
        FROM log log2 
        WHERE log2.fb_id = log.fb_id ) = 1 OR log.log_name  
        (SELECT log_name FROM log log_3
        WHERE log_3.log_id = 
            (SELECT MIN(log_id)
            FROM log log4
            WHERE log4.fb_id = log.fb_id ))
    GROUP BY fb_id )

Теперь, если вы прочитали это далеко, вот вопрос. Почему они оба работают нормально, но как только я применяю к ним другие фильтры, все смешивается?

Я даже пытался создать временную таблицу с предложением WITH и использовать для этого фильтры Date и Log_Type, но она все равно не работала. Несколько результатов, которые должны были быть включены в фильтры, были внезапно опущены. Изначально я получал первое имя только с датой или второе имя, если их было несколько в столбце Log_Name, теперь я получал случайным образом что угодно, если оно вообще было. Аналогичным образом, использование WHERE (ДАТА МЕЖДУ '2007/11/1' И '2007/11/30') вызовет permaloop, где использование WHERE (MONTH (Date) = ' 11 ') AND (YEAR (Date) =' 2007 ') будет работать нормально. Но если бы я добавил еще один фильтр к последнему параметру, например .. И ГДЕ Log_Type = 'Critical' , он снова был бы на permaloop. Оба permaloops произошли с решением Ланга.

Мне нужно объединить этот тип поиска с другим, используя UNION ALL, поэтому мне интересно, не столкнусь ли я с такими же странными проблемами в будущем с этим? Ясно, что здесь есть что-то, чего я не понимаю в SQL, и мой DL для запроса - сегодня, так что я здесь немного расстроен. Спасибо за помощь. :)

Редактировать: Для уточнения. Требуется результат запросов выше, и эти результаты должны быть отфильтрованы в «критические» случаи ТОЛЬКО в течение определенного времени (месяца).

Затем он будет объединен с другим поиском, который возвращает в первый раз, когда FB_ID со статусом «Поддержка» (Log_Type) был зарегистрирован. Идея состоит в том, чтобы дать представление о том, сколько новых случаев регистрируется в БД каждый месяц.

Редактировать 2: Обновление, приведенное ниже предложение Russ Cam работает, но оно исключает любые FB_ID, которые были впервые датированы за пределами данного диапазона, даже если строка результата запроса, где изменяется Log_Name, будет существовать внутри диапазон.

Ответы [ 4 ]

1 голос
/ 20 февраля 2009

Ну, спасибо за комплимент, сначала:)

Мой запрос фактически просматривает все записи, выбирает следующего ответственного за каждую запись и назначает номер строки для каждого перехода ответственности для определенного fb_id.

Если для этого fb_id не было переходов, он будет выбран как переход к NULL.

Затем запрос выбирает каждый первый переход (то есть с ROW_NUMBER из 1), будь то реальный переход или фальшивка, и проверяет, является ли он реальным или фальшивым.

Если это реально (не для NULL), то возвращается идентификатор человека, который получил ответственность; если нет, то возвращается человек, который дал ответственность NULL (т. е. вообще не дал).

Как видите, этот запрос в значительной степени опирается на индекс (fb_id, cdate, id) для поиска следующего ответственного лица. Если вы добавите новые условия, он больше не сможет использовать этот индекс и станет медленным.

Пожалуйста, уточните, какое условие вы хотите добавить, и мы постараемся помочь вам снова:)

Вы сказали, что хотите добавить log_type в запрос.

Как считается first transition? Вам нужно вернуть первый переход, когда оба поля и critical, или только переходы с non-critical на critical, или когда любое из них является critical?

Если вам нужно добавить date range, скажем, только для February, должен ли он считать человека, который получил работу на February, но дал ее на March? Или кто получил работу на January и дал ее на February?

А пока попробуйте это:

SELECT lo4.*
FROM
    (
    SELECT CASE WHEN ln.log_id IS NULL THEN lo2.log_id ELSE ln.log_id END AS log_id,
           ROW_NUMBER() OVER (PARTITION BY lo2.fb_id ORDER BY lo2.cdate) AS rn 
    FROM 
        (
        SELECT
               lo.*,
               (
               SELECT TOP 1 log_id
               FROM t_log li
               WHERE li.fb_id = lo.fb_id 
                     AND li.cdate >= CASE WHEN lo.cdate < @range THEN @range ELSE lo.cdate END
                     AND li.cdate < DATEADD(month, 1, @range)
                     AND li.log_id <> lo.log_id
                     AND li.log_name <> lo.log_name
               ORDER BY
                 cdate, log_id
               ) AS next_id
        FROM t_log lo
        WHERE lo.cdate >= @range
          AND lo.cdate < DATEADD(month, 1, @range)
        ) lo2
    LEFT OUTER JOIN t_log ln ON ln.log_id = lo2.next_id
    ) lo3,
t_log lo4
WHERE lo3.rn = 1
  AND lo4.log_id = lo3.log_id

Как видите, здесь есть две проверки диапазонов дат.

В подзапросе inner отфильтровываются переходы, для которых recipient находится вне диапазона дат.

В запросе outer отфильтровываются переходы, где sender выходит за пределы диапазона.

1 голос
/ 20 февраля 2009

Как тестер, что происходит, когда вы заключаете любое из их утверждений в оператор SELECT, фактически превращаете их в подзапрос, а затем помещаете в него предложение WHERE?

Например,

SELECT log.*
FROM
(
    SELECT lo4.*
    FROM
        (SELECT CASE WHEN ln.log_id IS NULL THEN lo2.log_id ELSE ln.log_id END
        AS log_id, ROW_NUMBER() OVER (PARTITION BY lo2.fb_id ORDER BY lo2.cdate) AS rn 
        FROM 
        (SELECT lo.*,
            (SELECT TOP 1 log_id
            FROM t_log li WHERE li.fb_id = lo.fb_id AND li.cdate >= lo.cdate
            AND li.log_id  lo.log_id AND li.log_name  lo.log_name
            ORDER BY cdate, log_id)
        AS next_id
        FROM t_log lo)
    lo2 LEFT OUTER JOIN t_log ln ON ln.log_id = lo2.next_id)
    lo3, t_log lo4
    WHERE lo3.rn = 1 AND lo4.log_id = lo3.log_id
) AS log
WHERE log.Date BETWEEN @start and @end

Я бы ожидал, что это работает

EDIT:

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

DECLARE @log_type CHAR(20) --Set this to the correct datatype
SET @log_type = 'Critical'

DECLARE @start_date DATETIME
SET @start_date = '20 FEB 2009' -- use whichever datetime format is appropriate

DECLARE @end_date DATETIME
SET @end_date = '21 FEB 2009' -- use whichever datetime format is appropriate

    SELECT lo4.*
    FROM
        (
        SELECT 
            CASE WHEN ln.log_id IS NULL THEN lo2.log_id ELSE ln.log_id END AS log_id, 
            ROW_NUMBER() OVER (PARTITION BY lo2.fb_id ORDER BY lo2.cdate) AS rn 
        FROM 
            (
             SELECT lo.*,
            (SELECT TOP 1 log_id
             FROM t_log li 
             WHERE 
             li.fb_id = lo.fb_id 
             AND li.cdate >= lo.cdate
             AND li.log_id <> lo.log_id 
             AND li.log_name <> lo.log_name
             AND log_type = @log_type
             AND li.cdate BETWEEN @start_date and @end_date
             ORDER BY cdate, log_id
             ) AS next_id
             FROM t_log lo
            ) lo2 
        LEFT OUTER JOIN 
        t_log ln 
        ON ln.log_id = lo2.next_id
        /* AND ln.cdate BETWEEN @start_date and @end_date
           I think that this would be needed for cases where
           the next_id is null
        */
    ) lo3, 
    t_log lo4
    WHERE 
    lo3.rn = 1 
    AND lo4.log_id = lo3.log_id

РЕДАКТИРОВАТЬ 2:

Обдумав это еще раз, важно получить ответы на дополнительные вопросы. Эти вопросы уже были заданы в ответе Quassnoi , что значительно изменит возвращаемый набор результатов. Короче,

1. Для указанного диапазона дат:

  • должны ли дата журнала исходной записи журнала и следующей записи журнала попадать в этот диапазон дат?

  • вы хотите включить результаты только в том случае, если дата следующей записи журнала для каждого fb_id находится в диапазоне дат (то есть дата исходной записи журнала не имеет значения)?

  • хотите ли вы включать результаты только в том случае, если дата исходной записи журнала для каждого fb_id находится в диапазоне дат (т. Е. Дата следующей записи журнала, которая будет возвращена в наборе результатов где произошла передача, может быть после диапазона дат).?

2. Для указанного типа журнала:

  • должен ли тип журнала для исходной записи журнала и следующей записи журнала совпадать с указанным типом журнала?

  • вы хотите включить результаты, в которых следующая запись журнала соответствует указанному типу журнала, независимо от типа журнала исходной записи журнала для каждого fb_id?

  • вы хотите включить результаты только в том случае, если исходная запись журнала соответствует указанному типу журнала, независимо от типа журнала следующей записи журнала?

Ответы на эти вопросы будут иметь решающее значение при построении запроса и выводе того, что на самом деле говорят вам полученные данные.

0 голосов
/ 21 февраля 2009

Как вы думаете, подойдет следующее?



set nocount on

declare @log table
(
log_id int
,fb_id int
,log_date datetime
,log_name nvarchar(25)
,log_type nvarchar(20)
)

insert into @log(log_id, fb_id, log_date, log_name, log_type) values (7, 4, convert(datetime,'8/11/2007', 103), N'Nina', N'Critical')
insert into @log(log_id, fb_id, log_date, log_name, log_type) values (6, 4, convert(datetime,'6/11/2007', 103), N'John', N'Critical')
insert into @log(log_id, fb_id, log_date, log_name, log_type) values (5, 4, convert(datetime,'6/11/2007', 103), N'Mike', N'Critical')
insert into @log(log_id, fb_id, log_date, log_name, log_type) values (4, 4, convert(datetime,'6/11/2007',103), N'Mike', N'Critical')
insert into @log(log_id, fb_id, log_date, log_name, log_type) values (3, 3, convert(datetime,'3/11/2007',103), N'Ben', N'Critical')
insert into @log(log_id, fb_id, log_date, log_name, log_type) values (2, 3, convert(datetime,'1/11/2007',103), N'Ben', N'Critical') 
insert into @log(log_id, fb_id, log_date, log_name, log_type) values (1, 2, convert(datetime,'10/9/2007',103), N'Pat', N'Critical') 
insert into @log(log_id, fb_id, log_date, log_name, log_type) values (0, 2, convert(datetime,'1/9/2007',103), N'Couger', N'Critical') 

declare @result table
(
intid int identity(1,1)
,log_id int
,fb_id int
,log_date datetime
,log_name nvarchar(25)
,log_type nvarchar(20)
,_min_intid int
,_position int
)

insert into @result (log_id, fb_id, log_date, log_name, log_type)
select log_id, fb_id, log_date, log_name, log_type from @log order by fb_id, log_date

update r
set r._min_intId = w.freq
from @result r join 
(select distinct fb_id, min(intid) freq from @result group by fb_id) w
on r.fb_id = w.fb_id

update @result set _position = intid - _min_intid + 1

select log_id, fb_id, log_date, log_name, log_type from @result where _position = 2


0 голосов
/ 20 февраля 2009

Несколько очень общих советов: Попробуйте получить план выполнения для всех вариантов вашего запроса и проверьте, какие объекты используются БД для получения данных.

Возможно, оптимизатор запросов использует совершенно разные наборы ресурсов для каждого варианта запроса и что некоторые объекты недопустимы (например, временные таблицы / материализованные представления, основанные на вашей исходной таблице, которые не обновлены). так далее). Такие ресурсы могут привести к неверным результатам, которые вы видите.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...