棘手的更新问题

时间:2009-09-01 15:58:33

标签: sql-server-2005 tsql

我有以下表结构如下:(请忽略blackbox。在Excel中格式化问题)

alt text http://img525.imageshack.us/img525/5788/beforeznj.jpg

我需要做的是使用TSQL将SessionGUID数据转换为以下内容: alt text http://img4.imageshack.us/img4/4553/afterjer.jpg

这是我网站的用户会话表,我们搞砸了SessionGUID,因为它在每个页面加载时生成一个NEWID()。所以现在我们需要通过计时将会话分组(假设如果用户在最后一次页面加载后30分钟没有触发页面加载会话中断),则使用创建的第一个SessionGUID。

为了进一步解释这个简短的例子,用户(00000000-0000-0000-0000-000000000000)在09/01/2009访问了我的网站两次。一个在13:37,另一个在14:46。用户(A107EF1E-00A2-4515-A120-984086BC8368)。请注意,实际表包含需要更新的数百万行。 :(

更新 每个会话的登录页面/起始URL并不总是“主页”,它可以是网站上的任何页面。情节变浓了..

UPDATE2 测试数据

CREATE TABLE Sessions (IPAddress VARCHAR(15), UserGUID UNIQUEIDENTIFIER, DATE DATETIME, URL VARCHAR(200), SessionGUID UNIQUEIDENTIFIER)
INSERT INTO Sessions (
    IPAddress,
    UserGUID,
    DATE,
    URL,
    SessionGUID
)
VALUES ( '192.168.0.1', '00000000-0000-0000-0000-000000000000', '09/01/2009  13:37:34', 'homepage', '2B3A80B1-A247-4BB5-81BB-B54DED0C9C6A')
VALUES ( '192.168.0.1', '00000000-0000-0000-0000-000000000000', '09/01/2009  13:37:36', 'page1', '7FB10E12-5EB9-428C-BE3E-57818DEF8512')
VALUES ( '192.168.0.1', '00000000-0000-0000-0000-000000000000', '09/01/2009  13:37:41', 'page2', 'D12C3539-1239-447E-8BD8-DBA6B7087ADE')
VALUES ( '192.168.0.1', '00000000-0000-0000-0000-000000000000', '09/01/2009  14:56:00', 'homepage', '4FE36C46-640B-464F-8118-AFFE477347A1')
VALUES ( '192.168.0.1', '00000000-0000-0000-0000-000000000000', '09/01/2009  14:56:10', 'page2', 'FF9BF55B-3630-4D05-AB57-1B6ECAB96657')
VALUES ( '192.168.0.1', '00000000-0000-0000-0000-000000000000', '09/01/2009  14:56:18', 'page4', '863D3424-9788-481A-8440-09313ED4F8FE')
VALUES ( '192.168.0.1', '00000000-0000-0000-0000-000000000000', '09/01/2009  14:56:19', 'page3', '105D7FE5-C731-4EB6-B287-720127AAF0A3')
VALUES ( '192.168.0.1', '00000000-0000-0000-0000-000000000000', '09/01/2009  15:00:35', 'page5', '296479D0-3848-4189-94E2-41906BAE580D')
VALUES ( '192.168.0.1', '00000000-0000-0000-0000-000000000000', '09/01/2009  15:00:36', 'page7', 'E3FFEBC6-C11E-4DF4-81FA-B42F1BF7AFD3')
VALUES ( '212.1.1.0', 'A107EF1E-00A2-4515-A120-984086BC8368', '12/01/2009  18:30:22', 'homepage', '1F918AB3-34E1-4343-8462-FA56423B921D')
VALUES ( '212.1.1.0', 'A107EF1E-00A2-4515-A120-984086BC8368', '12/01/2009  18:34:26', 'page1', '801C3DC8-F0F3-4B9C-BD53-BCCBE784CFAE')
VALUES ( '212.1.1.0', 'A107EF1E-00A2-4515-A120-984086BC8368', '12/01/2009  18:38:17', 'page2', 'A9A5C2BD-31B9-4A9B-A8BC-88C460F17282')
VALUES ( '212.1.1.0', 'A107EF1E-00A2-4515-A120-984086BC8368', '15/01/2009  11:42:27', 'page3', 'B29CE754-C7A3-40E8-8CB0-216A3E852762')
VALUES ( '212.1.1.0', 'A107EF1E-00A2-4515-A120-984086BC8368', '15/01/2009  11:42:32', 'page4', 'E291C4B9-A422-4A76-A550-F65C208DD886')
VALUES ( '212.1.1.0', 'A107EF1E-00A2-4515-A120-984086BC8368', '15/01/2009  11:44:51', 'page6', '63D4A636-8336-44E7-8C97-9CD65D21359E')
VALUES ( '212.1.1.0', 'A107EF1E-00A2-4515-A120-984086BC8368', '15/01/2009  11:44:55', 'page2', '7BB814CD-C9B3-4CAF-A45C-4405DC0B07D2')
VALUES ( '212.1.1.0', 'A107EF1E-00A2-4515-A120-984086BC8368', '15/01/2009  11:48:35', 'page4', 'B6DCEC1E-C262-425D-8E46-8F4B47F2921A')

感谢。

不满的DBA新秀。

3 个答案:

答案 0 :(得分:1)

根据您评论中的其他数据,我必须得出结论,表格中没有足够的信息可靠地进行此更新。您可以进行更多分析并找到可以分组的可接受的时间跨度值,并使用Charles Bretana的方法进行一次性修复,但是您将自己进行分析并且您将无法进行分析指望那个数字或任何数字继续长期工作。

答案 1 :(得分:1)

编辑我有很多语法错误,因为我没有测试。 (注意自己,不要在没有测试代码的情况下提交答案。)最重要的是,一旦我修改了语法,并且反对测试数据,我发现我的答案是错误的。在30分钟之前测试存在一行的子查询完全是假的。

更正版本:

; with EliminateTies (UserGuid, SessionGuid, "Date") as
    (select UserGuid
        , cast(min(cast(SessionGuid as varbinary)) as uniqueidentifier)
        , "Date"
    from Sessions
    group by UserGuid, "Date")
, SessionBoundaries (UserGuid, SessionGuid, StartDateTime, SessionNumber) as
    (select UserGuid, SessionGuid, "Date"
        , row_number() over 
            (partition by UserGuid 
            order by "Date") as SessionNumber
    from (select UserGuid, SessionGuid, "Date" from EliminateTies
        union all
        -- Add a set of records at the end of time, to bound the last
        -- of each users sessions
        select distinct UserGuid
            , null as SessionGuid
            , cast('9999-12-31' as datetime) as "Date"
        from Sessions) ET_Out
    where not exists (select *
        from EliminateTies ET_In
        where ET_Out.UserGuid = ET_In.UserGuid
        and ET_Out.SessionGuid <> ET_In.SessionGuid
        and (dateadd(minute, -30, ET_Out."Date") < ET_In."Date" 
            and ET_In."Date" <= ET_Out."Date")))
Update MT
    set SessionGuid = LowBound.SessionGuid
from SessionBoundaries LowBound
inner join SessionBoundaries HighBound
    on LowBound.UserGuid = HighBound.UserGuid
    and LowBound.SessionNumber = HighBound.SessionNumber - 1
inner join Sessions S
    on S.UserGuid = LowBound.UserGuid
    and LowBound.StartDateTime <= S."Date" 
    and S."Date" < HighBound.StartDateTime

编辑2添加说明

  1. ; with EliminateTies ...定义名为EliminateTies的关系。这是为了处理三元组(UserGuid, SessionGuid, "Date")可能包含给定(UserGuid, "Date")的重复项的可能性。 SQL Server日期时间的分辨率为1/300秒,因此重复不太可能,但并非不可能。
    1. EliminateTies将为每个(UserGuid, "Date")group by UserGuid, "Date"包含一行。
    2. 通过agregate函数SessionGuid从该对可用的SessionGuid组中任意挑选min()。正如评论中所指出的那样,MIN(GUID)是不允许的,因此转换为VARBINARY,找到MIN(),然后转换回UNIQUEIDENTIFIER
    3. 注意:
      1. 我以分号开头,因为虽然SQL Server通常在语句结尾处不需要分号,但是在with ...之前的语句的情况下,它不会尝试重新训练自己以结束所有语句用分号语句,我训练自己用分号开始所有with ...。分号实际上终止了之前的任何陈述。
      2. 我引用了“Date”标识符,因为date是SQL Server 2008中的数据类型,因此是一个保留字。 OP是在2005年,因此不是严格必要的。
  2. , SessionBoundaries定义了另一种关系。此关系具有新会话开始的所有点,基于在最近30分钟内Sessions中没有条目时给定UserGuid会话开始的规则。
    1. 内联视图from (select ... union all ...) ET_Out为每个UserGuid添加一个额外的行,其日期位于“结束时间”。这是因为我们想要在高边界和低边界之间对行进行分组,并且我们需要每个用户的最后一个会话的上边界。
    2. where not exists过滤掉不在会话边界开头的行。
    3. 在select子句row_number() ...中,将按行排序,按日期排序,以便每个UserGuid都有SessionNumber s 1..N + 1,其中N是会话数。我们稍后使用SessionNumbers来查找相邻边界。 (可能更好地称为SessionBoundaryNumber。)
  3. 最后是更新。我们添加from子句以允许将关系连接在一起。更新指定了我们希望从from子句更新的表的表别名。 (注意这是T-SQL,而不是在标准视图中一起使用多个关系的ANSI标准方法。)
    1. 首先,我们通过SessionBoundary上的相等性和基于UserGuid的邻居边界自我加入SessionNumber。如果用户A有2个会话,则会有3个SessionNumber s 1,2&amp;这个连接将导致(低,高)对(1,2)和&amp; (2,3)。现在,对于每个UserGuid,我们都有每个会话的下边界和上边界。最后一次会话的上限是“时间结束”。
    2. 接下来,我们加入要更新的表,Sessions连接条件是UserGuid相等,Sessions中行的日期在由{{1}}定义的范围内[LowBound,HighBound)。请记住,边界是会话开始的地方。因此,测试是LowBound&lt; = Sessions。“Date”&lt; HighBound。

答案 2 :(得分:0)

这看起来不错吗?

UPDATE T Set SessionGuid = 
    (Select Min(UserGuid) FROM [mytable] 
     Where IPAddress=T.IPAddress 
       And Date = (Select Min(Date) From MyTable
                   Where IPAddress = T.IPAddress
                       And DateDiff(minute, Date, T.Date) 
                              Between 0 and 30)) 
FROM [mytable] T