6

我有一个包含这样的示例数据的表:

ID  Key   User
--  ----  -----
1   a     test
2   ab    test
3   abc   test
4   abcd  test
5   e     test1
6   ef    test1
7   efg   test1
8   efgh  test1
9   t     test1
10  ty    test1
11  tyu   test1
12  tyui  test1

数据由用户构建的值的顺序“快照”组成。我想为每个用户实例返回最后一行,建立一个不同的最终Key值。请注意大多数行如何Key包含整个前一行Key以及一个附加字母?我只想要终止这样一个序列的行并且是每个链中可能的最长值Keys连续包含前一个Key值。

上述示例数据应返回以下内容:

ID  Key   User
--  ----  -----
4   abcd  test
8   efgh  test1
12  tyui  test1

我该怎么做呢?

4

7 回答 7

6

在没有回答我的问题的情况下,我不得不做出以下假设:

  • ID列代表年表,并且始终无间隔地增加一。
  • SQL Server 2005 或更高版本

(更新:我做了一个小调整,使这项工作与来自不同用户的“交错”数据一起工作,并在我的小提琴中添加了一些交错和一些棘手的数据。)

所以这是我的解决方案。在 SqlFiddle 中查看它。值得注意的是,它模拟了LEAD来自 SQL Server 2012 的分析,没有JOIN.

WITH Info AS (
  SELECT
     Grp = Row_Number() OVER (PARTITION BY UserName ORDER BY ID, Which) / 2,
     *
  FROM
     dbo.UserEntry U
     CROSS JOIN (
        SELECT 1 UNION ALL SELECT 2
     ) X (Which)
)
SELECT
   ID = Max(V.ID),
   DataKey = Max(V.DataKey),
   UserName = Max(V.UserName)
FROM
   Info I
   OUTER APPLY (SELECT I.* WHERE Which = 2) V
WHERE I.Grp > 0
GROUP BY
   I.UserName,
   I.Grp
HAVING
   Max(I.DataKey) NOT LIKE Min(I.DataKey) + '_';

输入:

INSERT dbo.UserEntry (ID, DataKey, UserName)
VALUES
(1, 'a', 'test'),
(2, 'ab', 'test'),
(3, 'e', 'test1'),
(4, 'ef', 'test1'),
(5, 'abc', 'test'),
(6, 'abcd', 'test'),
(7, 'efg', 'test1'),
(8, 'efgh', 'test1'),
(9, 't', 'test1'),
(10, 'ty', 'test1'),
(11, 'tyu', 'test1'),
(12, 'tyui', 'test1'),
(13, 't', 'test1'),
(14, 'a', 'test'),
(15, 'a', 'test'),
(16, 'ab', 'test'),
(17, 'abc', 'test'),
(18, 'abcd', 'test'),
(19, 'to', 'test1'), 
(20, 'abcde', 'test'),
(21, 'top', 'test1');

输出:

ID  DataKey  UserName
--  -------  --------
6   abcd     test
8   efgh     test1
12  tyui     test1
14  a        test
20  abcde    test
21  top      test1

注意:我使用了不同的列名,因为使用保留字作为列名不是最佳做法(它会强制您在任何地方的名称周围加上方括号)。

我使用的技术将适用于单次扫描。它没有连接。使用适当索引的正确构建的基于连接的查询可能在 CPU 和时间上略胜一筹,但此解决方案肯定会具有最少的读取。

更新

虽然我的查询可能很好,但这个问题中的特定数据结构适合我第一次回答时没有考虑的非常优雅的解决方案。感谢 Andriy 的基本思想,这是一个炸药和超简单的查询(与上面相同的小提琴)。

WITH Info AS (
   SELECT
      Grp = Row_Number() OVER (PARTITION BY UserName ORDER BY ID) - Len(DataKey),
         *
   FROM
      dbo.UserEntry U
)
SELECT
   ID = Max(I.ID),
   DataKey = Max(I.DataKey),
   I.UserName
FROM
   Info I
GROUP BY
   I.UserName,
   I.Grp;
于 2013-02-07T23:26:49.583 回答
2

这是另一种方法:

  1. 使用首字母作为序列的分组标准。

  2. 分别对每个用户的行进行排名,按 ID 对它们进行排序,然后从排名中减去 Key 值的长度。将结果用作另一个序列分组标准。

  3. 再次对行进行排序,这次按用户和#1 和#2 的条件对它们进行分区,并按 ID 的降序对它们进行排序。

  4. 获取排名为 1 的行。

这是一个实现:

WITH partitioned AS (
  SELECT
    *,
    SeqKey = LEFT([Key], 1),
    SeqGrp = ROW_NUMBER() OVER (
      PARTITION BY UserName
      ORDER BY ID
    ) - LEN([Key])
  FROM dbo.UserEntry
),
ranked AS (
  SELECT
    ID,
    [Key],
    UserName,
    rnk = ROW_NUMBER() OVER (
      PARTITION BY UserName, SeqKey, SeqGrp
      ORDER BY ID DESC
    )
  FROM partitioned
)
SELECT
  ID,
  [Key],
  UserName
FROM ranked
WHERE rnk = 1
;

与@ErikE 的解决方案一样,假设 ID 列定义了序列的顺序。但是,如果与同一序列相关的 ID 值存在间隙,上述查询仍然可以正常工作。

您也可以在 SQL Fiddle上尝试此查询。(注意:演示使用@ErikE 的 DDL。)

于 2013-02-11T22:52:52.677 回答
1

此查询应为您提供正确的结果。我正在考虑这样一个事实,即 ID 之间可能存在一些差距(可能存在一些缺失的 ID,或者超过用户可能同时构建的序列)。

在内部查询中,我将返回表中的每一行,以及prevID指向同一用户的前一个 ID。然后我再次加入此查询的结果yourtable

SELECT
  t.ID, t.DataKey, t.UserName
FROM
  yourtable t LEFT JOIN (
    SELECT t1.ID id,
           max(t1.DataKey) DataKey,
           max(t1.UserName) UserName,
           max(t2.ID) prevID
    FROM
      yourtable t1 LEFT JOIN yourtable t2
      ON
        t1.ID>t2.ID
        AND t1.UserName = t2.UserName
    GROUP BY t1.ID
  ) t2
  ON t2.prevID = t.ID
     AND t2.UserName = t.UserName
     AND t2.DataKey LIKE CONCAT(t.DataKey, '_')
WHERE t2.ID is NULL

只有当前行是序列的一部分,连接才会成功。如果该行是DataKey序列的最后一个连接将不会成功,我将返回该行。

在这里看小提琴。

于 2013-02-12T17:42:34.833 回答
1

那么这个版本假设字符只添加到值中(不删除):

SELECT *
FROM dbo.UserEntry t1
WHERE 
  NOT EXISTS (
      SELECT *
      FROM dbo.UserEntry t2
      WHERE t1.username = t2.username 
      AND t2.dataKey LIKE t1.dataKey + '%'
      AND t2.ID = t1.ID + 1
   )

您可以轻松地将其更改为删除 dataKey 中的字符。

SqlFiddle

这是使用LEAD函数执行此操作的另一种方法。这样,您可以通过使用以下行的值扩展原始行来简化主选择:

WITH UserEntryWithNext AS (
SELECT 
  t1.*, 
  LEAD(t1.DataKey,1,0) OVER (ORDER BY ID) AS NextDataKey
FROM dbo.UserEntry t1
)
SELECT * 
FROM UserEntryWithNext 
WHERE NOT NextDataKey LIKE DataKey + '%' 
于 2013-02-11T19:56:07.160 回答
0

请找到我的部分答案:我们如何知道一个序列是否从新开始?如果能够记录序列 ID,肯定会使答案变得更容易。

select * from UserEntry 
where ID in (
  select max(ID) from UserEntry group by SeqNum
)

-- that assumes seqnum globally unique.  If only unique per person then group by SeqNum, UserName

-- Create data:
CREATE TABLE dbo.UserEntry (
  ID int,
  DataKey varchar(20),
  SeqNum int,
  UserName varchar(10)
);

INSERT dbo.UserEntry (ID, DataKey, SeqNum, UserName)
VALUES
  (1, 'a', 1 , 'test'),
  (2, 'ab', 1 , 'test'),
  (3, 'abc',  1 ,'test'),
  (4, 'abcd',  1 ,'test'),
  (5, 'e', 2 , 'test1'),
  (6, 'ef', 2 , 'test1'),
  (7, 'efg', 2 , 'test1'),
  (8, 'efgh',  2 ,'test1'),
  (9, 't',  3 ,'test1'),
  (10, 'ty', 3 , 'test1'),
  (11, 'tyu', 3 , 'test1'),
  (12, 'tyui',  3 ,'test1'),
  (13, 't',  4 ,'test1'),
  (14, 'to',  4 ,'test1'), 
  (15, 'top',  4 ,'test1');

SQL小提琴

于 2013-02-11T10:32:24.370 回答
0

EXISTS 选项

SELECT *
FROM dbo.test37 t1
WHERE EXISTS (
              SELECT *
              FROM dbo.test37 t2
              WHERE t1.[user] = t2.[user]
              GROUP BY LEFT([Key], 1), [User]
              HAVING MAX([Key]) = t1.[Key]
              )

SQLFiddle上的演示

更新

;WITH cte AS
 (      
  SELECT t1.[Key], t1.[User], ROW_NUMBER() OVER(ORDER BY t1.[User], t1.[Key]) AS Id
  FROM dbo.test37 t1
  )
  SELECT c1.[Key], c1.[User]
  FROM cte c1 LEFT JOIN cte c2 ON c1.ID + 1 = c2.Id
  WHERE ISNULL(c2.[Key], '') NOT LIKE ISNULL(c1.[Key], '') + '%'
于 2013-02-07T08:32:22.130 回答
0

因此,您存储了输入某些数据的整个历史记录,并且您只想获得最终的注册数据。有人在这里看到问题吗?

为什么不只存储最终提交的值?或者编写一个将使用 ajax 在离开字段时推送值的 javascript?

对考虑不周的数据库表进行复杂而昂贵的查询并不是一个好主意。始终尝试以方便获取的形式存储您以后需要的内容,即使您必须花费一些时间来处理数据或以编程方式获取数据。

想象一下在这样的表中有数百万行。如果您必须经常执行双嵌套查询,它将杀死您的数据库。

于 2013-02-11T08:46:28.663 回答