【问题标题】:SQL Server: How to select the second to last entry given a group of items?SQL Server:如何选择给定一组项目的倒数第二个条目?
【发布时间】:2016-07-05 21:40:12
【问题描述】:

我有一个有五列的表格:

  • mykey (INT pk)
  • 名称(VARCHAR)
  • 其他数据 (VARCHAR)
  • 组ID (INT)
  • 日期日期时间
  • 有效 (INT)

样本数据:

mykey   name        otherdata   groupID  date      active
----------------------------------------------------------
1      123_abc_nt   cat         1        6-6-16    0
2      123_abc_nt   dog         1        6-7-16    0
3      123_abc_nt   car         1        6-8-16    1
4      123_xyz_nt   red         2        6-9-16    0
5      123_xyz_nt   blue        2        6-10-16   1

此表中的所有条目均按groupID 和名称分组。换句话说,如果名称是 123_abc_nt,则该组将是组“#insertgroupidhere”。由于使用此表的过程的性质(我无法以任何方式更改),对“名称”或“其他数据”列的任何更改都会产生一个新行。

假设用户从使用此表的单独应用程序将第 5 行列“其他数据”从 blue 更改为 green。在这种情况下,该单独的应用程序将生成一个行:6, 123_xyz_nt, green,并将旧列标记为无效0

mykey   name        otherdata   groupID  date      active
-----------------------------------------------------------
1      123_abc_nt   cat         1        6-6-16    0
2      123_abc_nt   dog         1        6-7-16    0
3      123_abc_nt   car         1        6-8-16    1
4      123_xyz_nt   red         2        6-9-16    0
5      123_xyz_nt   blue        2        6-10-16   0 <--- old row deactivated
6      123_xyz_nt   green       2        6-11-16   1 <--- new row inserted and activated

表中任何列的更改都会发生这种情况。因此,如果我将名称从 _nt 更改为 _rt,表格将如下所示:

mykey   name        otherdata   groupID  date      active
---------------------------------------------------------
1      123_abc_nt   cat         1        6-6-16    0
2      123_abc_nt   dog         1        6-7-16    0
3      123_abc_nt   car         1        6-8-16    1
4      123_xyz_nt   red         2        6-9-16    0
5      123_xyz_nt   blue        2        6-10-16   0 
6      123_xyz_nt   green       2        6-11-16   0 <--- old row deactivated
7      123_xyz_rt   green       2        6-12-16   1 <--- new row inserted and activated

我需要编写一个查询,专门检测从 _nt_rt 的名称更改(反之亦然从 _rt_nt)并返回 active 行已经发生了这种变化。所以在最后一种情况下,我的查询需要返回7, 123_xyz_rt, green, 2, 1。此外,它需要忽略任何其他列更改,只检测 nt/rt 名称列更改。在某些情况下,我也会同时更改多行,而我的查询需要检测到这一点。

例子:

mykey   name        otherdata   groupID  date      active
----------------------------------------------------------
1      123_abc_nt   cat         1        6-6-16    0
2      123_abc_nt   dog         1        6-7-16    0
3      123_abc_nt   car         1        6-8-16    0  <-- deactivated
4      123_xyz_nt   red         2        6-9-16    0
5      123_xyz_nt   blue        2        6-10-16   0 
6      123_xyz_nt   green       2        6-11-16   0 
7      123_xyz_rt   green       2        6-12-16   0  <-- deactivated
8      123_xyz_nt   green       2        6-13-16   1  <-- activated
9      123_abc_rt   car         1        6-13-16   1  <-- activated (note the groupID)

我一直在尝试确定执行此操作的查询,但这非常困难。我也不能在这个数据库的表上使用触发器,所以这需要严格基于查询。这是我尝试过的:

我有一个临时表,可以捕获任何新插入的行:

DECLARE @NEWROWS_TEMP TABLE(mykey, name, active)

INSERT INTO @NEWROWS_TEMP
--PROCESS TO CATCH NEW ROWS....

然后使用该列表,我试图在 while 循环中查找已更改名称的行:

--Loop Variables:
DECLARE @TOTALCOUNT INT = (SELECT COUNT(*) FROM @TEMP)
DECLARE @NAME_INVERTED VARCHAR(MAX)
DECLARE @NAME VARCHAR(MAX)
DECLARE @LOOPNUM INTEGER = 1
DECLARE @CURRENT_KEY INT = 0

--Loop to find entries that changed from (nt->rt) OR (rt->nt):
WHILE ((SELECT COUNT(*) FROM @TEMP) > 0 AND (SELECT @LOOPNUM) <= @TOTALCOUNT)
BEGIN 
    SET @CURRENT_KEY = (SELECT TOP 1 mykey FROM @TEMP)
    SET @NAME = (SELECT name
                FROM @TEMP 
                WHERE CLASSIC_KEY = @CURRENT_KEY)
    SET @NAME_INVERTED = (SELECT CASE WHEN (CHARINDEX('_rt_',@NAME)  > 0)
                        THEN 
                            REPLACE(@NAME,'_rt_', '_nt_')
                        ELSE 
                            CASE WHEN (CHARINDEX('_nt_',@NAME)  > 0)
                            THEN 
                                REPLACE(@NAME,'_nt_', '_rt_')
                            ELSE
                                NULL
                                END
                        END)

    IF(EXISTS(
                SELECT TOP 1 mykey
                FROM mytable
                WHERE name = @NAME_INVERTED AND 
                active = 0)
             )
        BEGIN
            INSERT INTO @RESULT_LIST
            SELECT @CURRENT_KEY
        END 

    DELETE FROM @TEMP WHERE mykey = @CURRENT_KEY
    SET @LOOPNUM = @LOOPNUM +1
END

这适用于行名从未更改但otherdata 更改且存在过去的 nt-rt 更改的情况。循环只是跳过了行名没有改变的事实。

例如,如果我这样做:

mykey   name        otherdata   groupID  date      active
----------------------------------------------------------
1      123_abc_nt   cat         1        6-6-16    0
2      123_abc_nt   dog         1        6-7-16    0
3      123_abc_nt   car         1        6-8-16    0  
4      123_xyz_nt   red         2        6-9-16    0
5      123_xyz_nt   blue        2        6-10-16   0 
6      123_xyz_nt   green       2        6-11-16   0 
7      123_xyz_rt   green       2        6-12-16   0  
8      123_xyz_nt   green       2        6-13-16   1  
9      123_abc_rt   car         1        6-13-16   0  
10     123_abc_rt   bike        1        6-13-16   1 <-- Name did not actually change. Otherdata did, but current query picks this up anuway since there once was a change from "123_abc_nt" to "123_abc_rt" 

有没有更简单的方法可以在不使用循环的情况下获得类似的结果?事实证明,我的方法非常不准确且难以维护。

【问题讨论】:

  • 每组总是只有一行(最新的)active = 1?当上一行有不同的名称时,你想返回它吗?
  • 绝对正确,每组始终只有一个活动行。这是一个硬性规定。我只想在前一行名称更改时返回(特别是 _nt_rt 或反之亦然)。其他的,我不在乎。

标签: sql sql-server sql-server-2008


【解决方案1】:

这应该会返回预期的结果,如果不一样,用active=0检查上一行的名称:

select * from mytable as t1
where active = 1
and name <>
 ( select top 1 name
   from mytable as t2
   where t1.groupID = t2.groupID
     and active = 0
   order by t2.date desc
 ) 

【讨论】:

  • 谢谢,我认为这应该有效。非常简单!我会试一试。它比我之前的实现简单很多倍。
  • 我不得不对它进行高度修改以适应我的确切表结构,但这个查询的基础工作绝对完美,你摇滚!我现在只有一个带有嵌套子查询的查询,它完成了所有难看的 while 循环试图做的事情(但没有做)。再次感谢。
猜你喜欢
  • 2012-01-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-27
  • 2016-09-25
相关资源
最近更新 更多