奇怪的SQL问题

时间:2009-02-20 12:41:19

标签: sql

我有一个非常奇怪的问题,我已经使用了一段时间的SQL查询。我正在使用SQL Server 2005。

以下是进行查询的示例表:

Log:
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(第一次将责任移交给另一个)。结果应如下所示:

Desired result
Log_ID | FB_ID |   Date    | Log_Name | Log_Type

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

在早期的帖子中,Peter Lang和Quassnoi给出了下面的精彩答案。可悲的是,我几乎无法理解那里发生了什么,但他们的工作就像一个魅力。这是代码:

Quassnoi

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

Peter Lang 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(DATE BETWEEN'2007 / 11/1和'2007/11/30')将导致permaloop,其中使用 WHERE(MONTH(Date)= '11')和(年(日期)='2007')可以正常工作。但是如果我在后一个选项中添加了一个过滤器,例如 .. AND WHERE Log_Type ='Critical',它将再次出现在permaloop上。两个permaloops都发生在Lang的解决方案中。

我需要使用UNION ALL将这种类型的搜索与另一种搜索结合起来,所以我想知道我将来是否会遇到任何类似的奇怪问题呢?这里显然有一些我对SQL不了解的东西,今天查询的DL也是如此,所以我在这里有点压力。感谢所有帮助。 :)

编辑:澄清一下。需要上述查询的结果,并且这些结果只需要在给定时间(月)内过滤为“关键”案例。

然后,这将与另一个搜索结合,该搜索在第一次记录具有“支持”状态(Log_Type)的FB_ID时返回。我们的想法是每月在数据库中记录有多少新案例。

编辑2:更新,Russ Cam的建议正在发挥作用,但它排除了任何首次在给定范围之外的FB_ID,即使Log_Name更改的查询结果行将存在在范围内。

4 个答案:

答案 0 :(得分:1)

就像测试人员一样,当你在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

我期待这个工作

修改

试试这个版本。将变量设置为所需的值。本质上,我们只想在每个子查询中检索满足条件的结果,以便我们不会尝试在未编制索引的列上运行大型结果集上的条件。

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's answer上提出,并将大大改变返回的结果集。简而言之,

<强> 1。对于指定的日期范围,

  • 原始日志记录的日志日期和下一个日志记录都需要落入该日期范围吗?

  • 您是否希望仅包含每个fb_id的下一个日志记录的日期在日期范围内的结果(即原始日志记录的日期无关紧要)?

  • 您是否希望仅包含每个fb_id的原始日志记录的日期在日期范围内的结果(即下一个日志记录的日期,即将在结果集中返回的日期在发生移交的地方,可以在日期范围之后)。?

<强> 2。对于指定的日志类型,

  • 原始日志记录和下一个日志记录的日志类型是否需要与指定的日志类型相同?

  • 您是否希望包含下一个日志记录与指定日志类型匹配的结果,而不管每个fb_id的原始日志记录的日志类型是什么?

  • 您是否希望仅包含原始日志记录与指定日志类型匹配的结果,而不考虑下一个日志记录的日志类型?

这些问题的答案对于如何构建查询以及推断结果数据实际告诉您的内容至关重要。

答案 1 :(得分:1)

嗯,谢谢你的赞美,首先:)

我的查询实际查看了所有记录,为每条记录选择了下一个负责人,并为某个fb_id的每个责任转换分配了行号。

如果此fb_id没有转化,则会选择转换为NULL

然后查询选择每个第一次转换(即ROW_NUMBER 1),无论是真正的过渡还是假的,并检查它是真的还是假的。

如果它是真实的(对于非NULL,则返回获得责任的人的ID;如果没有,它会将给予责任的人返回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超出范围的过渡。

答案 2 :(得分:0)

一些非常一般的建议: 尝试获取查询的所有变体的执行计划,并检查DB使用哪些对象来获取数据。

查询优化器可能会为每个查询变量使用完全不同的资源集,并且某些对象无效(例如,基于原始表的临时表/物化视图不是最新的等等)。此类资源可能会导致您看到的无效结果。

答案 3 :(得分:0)

您认为以下情况会怎样?



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