2012-01-13 115 views
2

我有一个表格,用于跟踪发生在任意时间点的机器维护情况。这里有一个简单的表结构:选择表格中每一行的前一个日期

Maintenance Table 
---------------------------------------- 
ID   - integer 
DateCompleted - date 
MachineName - varchar 

和这里的一些示例表数据:

ID DateCompleted MachineName 
---------------------------------------- 
1  1/6/2011  'Machine 1' 
2  1/13/2011 'Machine 2' 
3  1/14/2011 'Machine 1' 
4  2/2/2011  'Machine 3' 
5  2/26/2011 'Machine 1' 
6  3/9/2011  'Machine 2' 
7  4/20/2011 'Machine 3' 

我想要做的就是创建一个查询,将返回从以前的维修任务的日期为每个任务。所以结果集是这样的:

ID MachineName CurDate  PrevDate 
---------------------------------------- 
1 'Machine 1' 1/6/2011 NULL 
2 'Machine 2' 1/13/2011 NULL 
3 'Machine 1' 1/14/2011 1/6/2011 
4 'Machine 3' 2/2/2011 NULL 
5 'Machine 1' 2/26/2011 1/14/2011 
6 'Machine 2' 3/9/2011 1/13/2011 
7 'Machine 3' 4/20/2011 2/2/2011 

什么是最好的方式来写这样的查询?我唯一的想法到目前为止是这样的:

SELECT ID, MachineName, DateCompleted AS CurDate, 
    (
    SELECT TOP 1 DateCompleted FROM Maintenance m2 
    WHERE m1.MachineName = m2.MachineName 
     AND m1.DateCompleted > m2.DateCompleted 
    ORDER BY DateCompleted DESC 
) AS PrevDate 

FROM Maintenance m1 

ORDER BY ID 

任何想法,建议或修正将是非常欢迎的。

+0

您使用的是什么RDBMS? – Lamak 2012-01-13 19:39:49

+0

微软,但我欢迎任何解决方案。 – Sparafusile 2012-01-13 19:44:34

+2

“Microsoft”不是RDBMS。 “SQL Server”是一个dbms。我为你加了标签。 – 2012-01-13 20:00:55

回答

1

至于你说的 “,但我欢迎任何解决方案。”

这与ANSI SQL的溶液:

SELECT ID, 
     DateCompleted, 
     MachineName, 
     lag(DateCompleted) over (partition by MachineName order by DateCompleted) as PrevDate 
FROM Maintenance 
ORDER BY id; 

工程PostgreSQL中,甲骨文,DB2和Teradata。

SQL Server尚不支持lag()函数,但即将推出的“Denali”版本(2012)将拥有它。

+0

这是一个整洁的功能。太糟糕了,这不是每个人都支持的。 – Sparafusile 2012-01-14 02:32:48

+0

@Sparafusile:窗口函数是一个重要的功能(如递归查询),现在我认为DBMS不支持它们不再是“最先进的”(火鸟3.0也会有它们) – 2012-01-14 11:56:04

+0

正确,我误解了你的陈述。我以为你在微软说“Windows”。更多的阅读后,我意识到我的错误。这是我正在寻找的解决方案类型,非常感谢。 – Sparafusile 2012-01-14 19:41:51

1

如何:

SELECT 
    m.ID, m.MachineName, m.DateCompleted AS CurDate, MAX(m_past.DateCompleted) AS PrevDate 

FROM Maintenance m 

    LEFT JOIN Maintenance m_past 
    ON m.MachineName = m_past.MachineName 

WHERE m_past.DateCompleted < m.DateCompleted 

GROUP BY m.ID 
+0

您的'GROUP BY'将阻止您选择'm.MachineName,m.DateCompleted AS CurDate'。我建议从select中删除'm.ID',并用'm.MachineName,m.DateCompleted'分组。 – jzila 2012-01-13 19:49:13

+0

这比我原来的解决方案更好,性能更好吗?如果是这样,为什么? – Sparafusile 2012-01-13 19:49:56

+0

@jzilla:你可能是对的,但我没有看到它。另外,如果这是真的,我不能只使用'GROUP BY m.ID,m.MachineName,m.DateCompleted'? @Sparafusile:我认为你的方法将执行内部查询(每行一个),而我的不会。虽然'JOIN'可能会让它检索更多的数据,你需要测试。别忘了在'MachineName'上放一个索引! – mbillard 2012-01-13 20:10:34

1

试试这个:

SELECT A.Id, A.MachineName, A.DateCompleted [CurDate], B.DateCompleted PrevDate 
FROM Maintenance A 
OUTER APPLY (SELECT TOP 1 * 
      FROM Maintenance 
      WHERE MachineName = A.MachineName AND DateCompleted < A.DateCompleted 
      ORDER BY DateCompleted DESC) B 
+0

Downvoter关心评论? – Lamak 2012-01-13 19:53:38

1

无论是前N个工作取决于您的DBMS。 MAX()将跨平台工作。索引DateCompleted和MachineName;它们都用在WHERE子句中。

select m1.id, m1.machinename, m1.datecompleted as curdate, 
    (select max(datecompleted) 
    from maintenance 
    where machinename = m1.machinename 
     and datecompleted < m1.datecompleted) as prevdate 
from maintenance m1 
order by machinename, curdate 

如果你的DBMS支持窗口功能,您可以使用

select m1.id, m1.machinename, m1.datecompleted as curdate, 
     max(datecompleted) over (partition by machinename 
           order by m1.datecompleted 
           rows between unbounded preceding 
             and 1 preceding) as prevdate 
from maintenance m1 

我不会试图去猜测这会更快。我会加载一张你想要的具有尽可能多样本数据的表格,然后测试它们。然后我用10倍的数据重新加载并再次测试。

在测试过程中,您想了解如何generate and read an execution plan

+0

谢谢。尽管如此,这与我的解决方案基本相同。我希望能有一些我以前还没有的深刻的东西。 – Sparafusile 2012-01-13 19:51:51

+0

你知道窗口函数的好处。生成数据总是比存储数据更昂贵。 – 2012-01-13 20:07:01

0

您的查询对我来说似乎很合理,而且很容易理解。忽略最终排序的可能成本,我认为假设存在适当的索引,复杂度本质上是O(n log n)。对于表中的每个条目,查询引擎都必须定位先前的日期条目,该条目应该是O(log n)且具有正确的索引。

单向也许以代码复杂度为代价提高性能可能是编写存储过程以产生结果。我认为无序的结果可以在O(n)中产生。该过程可以通过MachineName,然后由DateCompleted按顺序遍历表上的两个游标。它可以在O(n)中构建结果集,因为它跨过两个游标。但是,结果将需要在ID上排序,这将是O(n log n)。所以我认为理论上的复杂性与查询相同,但是这个过程可能会有更少的开销并且运行速度会更快。但我绝对不会推荐这个解决方案,因为它会变得丑陋难以维护。

+0

这实际上很简单,可以通过单个表扫描来完成支持窗口功能的任何DBMS。看到我的答案。 – 2012-01-13 21:45:37

1

SOLUTION:

declare @tmp table (Id int, DateCompleted datetime, MachineName varchar(100)) 
insert into @tmp 
select 1,'1/6/2011','Machine 1' 
union select 2,'1/13/2011', 'Machine 2' 
union select 3,'1/14/2011', 'Machine 1' 
union select 4,'2/2/2011',  'Machine 3' 
union select 5,'2/26/2011', 'Machine 1' 
union select 6,'3/9/2011',  'Machine 2' 
union select 7,'4/20/2011', 'Machine 3' 



select t.Id, t.DateCompleted, t.MachineName, max(t2.DateCompleted) PrevDate 
from @tmp t 
left join @tmp t2 
    on t.MachineName = t2.MachineName 
    and t.DateCompleted > t2.DateCompleted 
group by t.Id, t.DateCompleted, t.MachineName 
1

从SQL Server 2012开始,您可以使用窗口集合来编写您需要的查询。只需使用以下代码:

select 
    ID, 
    MachineName, 
    DateCompleted AS CurDate, 
    min(DateCompleted) 
     over (partition by MachineName order by DateCompleted 
      rows between 1 preceding and 1 preceding) as PrevDate 
from Maintenance 
order by Id 
相关问题