【问题标题】:How can I check for average concurrent events in a SQL table based on the date, time and duration of the events?如何根据事件的日期、时间和持续时间检查 SQL 表中的平均并发事件?
【发布时间】:2010-10-20 04:31:18
【问题描述】:

我有一组呼叫详细记录,我应该根据这些记录确定每个系统每小时的平均并发活动呼叫(精度为一分钟)。如果我查询晚上 7 点到晚上 8 点,我应该看到该小时内(对于每个系统)的平均并发呼叫数(平均每分钟的并发呼叫数)。

所以,我需要一种方法来检查 7:00-7:01、7:01-7:02 等的活动呼叫计数,然后平均这些数字。如果通话的时间和持续时间在当前检查的分钟内,则认为通话处于活动状态。

使这更加困难的是它需要跨越 SQL 7.0 和 SQL 2000(2000 中的某些函数在 7.0 中不可用,例如 GetUTCTime()),如果我只能让 2000 工作我会开心。

我可以采取什么方法来解决这个问题?

我想过在检查的一小时内循环通过分钟 (60) 并添加该分钟之间的呼叫计数,然后以某种方式交叉引用持续时间以确保呼叫从晚上 7:00 开始并且有300 秒的持续时间显示在 7:04 处于活动状态,但我无法想象如何解决这个问题。我试图找出一种方法来衡量每个呼叫与特定分钟的权重,这会告诉我呼叫在该分钟内是否处于活动状态,但无法提出有效的解决方案。

这里的数据类型与我必须查询的相同。我对架构没有任何控制权(除了可能转换数据并插入到另一个具有更合适数据类型的表中)。我提供了一些我知道有并发活动调用的示例数据。

CREATE TABLE Records(
  seconds char(10),
  time char(4),
  date char(8),
  dur int,
  system int,
  port int,
)

--seconds is an stime value. It's the difference of seconds from UTC 1/1/1970 00:00:00 to the current UTC time, we use it as an identifier (like epoch).
--time is the time the call was made.
--date is the day the call was made.
--dur is the duration of the call in seconds.
--system is the system number.
--port is the port on the system (not particularly relevant for this question).

INSERT INTO Records(seconds, time, date, dur, system, port) VALUES('1239924228','1923','20090416',105,2,2)
INSERT INTO Records(seconds, time, date, dur, system, port) VALUES('1239923455','1910','20090416',884,1,97)
INSERT INTO Records(seconds, time, date, dur, system, port) VALUES('1239924221','1923','20090416',116,2,15)
INSERT INTO Records(seconds, time, date, dur, system, port) VALUES('1239924259','1924','20090416',90,1,102)
INSERT INTO Records(seconds, time, date, dur, system, port) VALUES('1239923458','1910','20090416',891,2,1)
INSERT INTO Records(seconds, time, date, dur, system, port) VALUES('1239924255','1924','20090416',99,2,42)
INSERT INTO Records(seconds, time, date, dur, system, port) VALUES('1239924336','1925','20090416',20,2,58)
INSERT INTO Records(seconds, time, date, dur, system, port) VALUES('1239924293','1924','20090416',64,2,41)
INSERT INTO Records(seconds, time, date, dur, system, port) VALUES('1239923472','1911','20090416',888,2,27)
INSERT INTO Records(seconds, time, date, dur, system, port) VALUES('1239924347','1925','20090416',25,1,100)
INSERT INTO Records(seconds, time, date, dur, system, port) VALUES('1239924301','1925','20090416',77,2,55)
INSERT INTO Records(seconds, time, date, dur, system, port) VALUES('1239924332','1925','20090416',52,2,43)
INSERT INTO Records(seconds, time, date, dur, system, port) VALUES('1239924240','1924','20090416',151,1,17)
INSERT INTO Records(seconds, time, date, dur, system, port) VALUES('1239924313','1925','20090416',96,2,62)
INSERT INTO Records(seconds, time, date, dur, system, port) VALUES('1239924094','1921','20090416',315,2,16)
INSERT INTO Records(seconds, time, date, dur, system, port) VALUES('1239923643','1914','20090416',788,2,34)
INSERT INTO Records(seconds, time, date, dur, system, port) VALUES('1239924447','1927','20090416',6,2,27)
INSERT INTO Records(seconds, time, date, dur, system, port) VALUES('1239924342','1925','20090416',119,2,15)
INSERT INTO Records(seconds, time, date, dur, system, port) VALUES('1239924397','1926','20090416',76,2,41)
INSERT INTO Records(seconds, time, date, dur, system, port) VALUES('1239924457','1927','20090416',23,2,27)

【问题讨论】:

    标签: sql sql-server algorithm sql-server-2000 sql-server-7


    【解决方案1】:

    我认为 MarkusQ 有答案,但让我开发一个您可能会发现更容易使用的替代方案。我将使用我的customary method 将其开发为一系列简单的视图转换,类似于程序语言中的functional decomposition

    首先,让我们将所有内容放在共同的单位中。回想一下 record 的列 s 是自 1970 年 1 月 1 日午夜 epoch 以来的秒数。我们可以通过只需将 s 模数取为一天中的秒数:s % (60 * 60 * 24)

    select *, 
    s % (60 * 60 * 24) as start_secs_from_midnight,
    s % (60 * 60 * 24) + dur - 1 as end_secs_from_midnight,
    ;
    

    我们从s + dur 中减去一个,因为从 12:00:00 开始的一秒通话也将在 12:00:00 结束。

    我们可以通过将这些结果除以 60 或仅除以 floor( s / 60 ) % (60 * 24) 来找到自午夜以来的分钟数:

    create view record_mins_from_midnight as
    select *, 
    floor( s / 60 ) % (60 * 24) as start_mins_fm,
    floor( ( s + dur - 1) / 60 ) % (60 * 24) as end_mins_fm 
    from record
    ;
    

    现在我们创建一个分钟表。我们需要其中的 1440 个,编号从 0 到 1439。在不支持任意序列的数据库中,我创建一个人工范围或序列,如下所示:

      create table artificial_range ( 
       id int not null primary key auto_increment, idz int) ;
      insert into artificial_range(idz) values (0);
      -- repeat next line to double rows
      insert into artificial_range(idz) select idz from artificial_range;
    

    所以要创建一个minute 表:

      create view minute as 
       select id - 1 as active_minute 
       from artificial_range 
       where id <= 1440
       ;
    

    现在我们只需将minute 加入我们的记录视图

    create view record_active_minutes as
    select * from minutes a 
    join record_mins_from_midnight b
    on (a.active_minute >= b.start_mins_fm 
    and a.active_minute <= b.end_mins_fm 
     ;
    

    这只是将记录行相乘/相乘,因此我们在通话处于活动状态的每一整分钟都有一个记录行。

    请注意,我通过将活动定义为“(部分)通话在一分钟内发生”来做到这一点。也就是说,根据此定义,从 12:00:59 开始并在 12:01:01 结束的两秒呼叫发生在两个不同的分钟内,但从 12:00:58 开始并在 12 结束的两秒呼叫: 00:59 发生在一分钟内。

    我这样做是因为您指定了“所以,我需要一种方法来检查 7:00-7:01、7:01-7:02 的活动呼叫计数”。如果您希望只考虑在超过一分钟的时间内发生持续时间超过 60 秒的呼叫,则需要调整加入。

    现在,如果我们想找到任何粒度等于或大于分钟粒度的活动记录数,我们只需在最后一个视图上进行分组。为了计算每小时的平均通话次数,我们除以 60 将分钟变为小时:

     select floor( active_minute / 60 ) as hour, 
     count(*) / 60 as avg_concurent_calls_per_minute_for_hour
     from record_active_minutes
     group by floor( active_minute / 60 ) ;
    

    请注意,这是所有电话在所有日子里每小时的平均值;如果我们想将其限制在特定的日期或日期范围内,我们会添加一个where 子句。


    但是等等,还有更多!

    如果我们创建一个执行左外连接的record_active_minutes 版本,我们可以获得一个显示一天中所有小时的平均值的报告:

     create view record_active_minutes_all as
     select * 
     from 
     minutes a 
     left outer join record_mins_from_midnight b
       on (a.active_minute >= b.start_mins_fm 
           and a.active_minute <= b.end_mins_fm) 
     ;
    

    然后我们再次进行选择,但针对的是新视图:

     select floor( active_minute / 60 ) as hour, 
     count(*) / 60 as avg_concurent_calls_per_min
     from record_active_minutes_all
     group by floor( active_minute / 60 ) ;
    
    
    +------+------------------------------+
    | hour | avg_concurrent_calls_per_min |
    +------+------------------------------+
    |    0 |                       0.0000 |
    |    1 |                       0.0000 |
    |    2 |                       0.0000 |
    |    3 |                       0.0000 |
       etc....
    

    我们也可以用 where 来索引它。不幸的是,连接意味着我们将为基础record 表提供空值,其中特定时间不存在调用,例如,

     select floor( active_minute / 60 ) as hour, 
     count(*) / 60 as avg_concurent_calls_per_min
     from record_active_minutes_all
     where month(date) = 1 and year(date) = 2008 
     group by floor( active_minute / 60 ) ;
    

    将在未发生呼叫的几个小时内返回任何行。如果我们仍然想要显示所有时间的“类似报告”视图,我们确保我们还包括那些没有记录的时间:

     select floor( active_minute / 60 ) as hour, 
     count(*) / 60 as avg_concurent_calls_per_minute_for_hour
     from record_active_minutes_all
     where (month(date) = 1 and year(date) = 2008) 
     or date is null 
     group by floor( active_minute / 60 ) ;
    

    请注意,在最后两个示例中,我使用的是 SQL 日期(可以应用 monthyear 函数),而不是记录表中的 char(4) 日期。

    这带来了另一点:记录表中的日期和时间都是多余的并且是非规范化的,因为每个都可以从您的 column s 派生。将它们留在表中可能会出现不一致的行,其中date(s) &lt;&gt; datetime(s) &lt;&gt; time。我更喜欢这样做:

       create table record ( id int not null primary key, s, duration) ; 
    
       create view record_date as 
       select *, dateadd( ss, s, '1970-01-01') as call_date
       from record
      ;
    

    dateadd 函数中,ss 是一个枚举类型,它告诉函数添加秒数; s 是记录中的列。

    【讨论】:

    • 这很棒,帮助我完成了与 OT 类似的任务,布局和解释都很好。谢谢!
    【解决方案2】:

    如果我对您的理解正确,您希望获取开始时间小于 t+60 秒且开始时间加上持续时间小于或等于 t 的所有记录的计数,对于感兴趣的时间间隔(例如,t=7:00、7:01、7:02...等)。

    那么这只是平均这些计数的问题。

    但什么是平均值?这只是总和除以项目数,对吗?在这种情况下,项目的数量将始终等于以分钟为单位的时间范围,并且总和将等于间隔内的持续时间-分钟的总和,您可以根据给定的数据一次性计算出.

    现在听起来不太可能了?在伪 SQL 中:

    select sum( 
         ((time+duration rounded up to next minute, capped at end of period)
        - (time rounded down, bottom-capped at start of period) - 1)
         /(1 minute) )
      from Records
      where date is right
    

    然后将其除以感兴趣期间的分钟数。

    【讨论】:

    • 这或多或少是我想要做的。除了,我需要如果 stime > t
    • 不,我认为您需要按照我说的方式进行比较;我假设您打错了字,意思是“stime
    【解决方案3】:

    我的第一个建议是,如果您发现自己说(在使用 SQL 时)“我可以创建一个循环...”,那么您应该立即开始寻找基于集合的方法。使用 SQL 时要摆脱程序化思维模式。

    您的逻辑仍有一些模糊的部分。如果通话只是在那一分钟内通话的任何部分,那么通话是否算作在一分钟内?例如,如果一个呼叫从 1923 开始并持续 62 秒,是否认为它与从 1924 开始的所有呼叫重叠?我会假设那个是肯定的,但如果这不是真的,你可以调整下面的代码。这应该是一个小调整。

    对于以分钟为单位的细分,我通常会使用带有时间跨度的表格——我感兴趣的每个切片的开始时间和结束时间。在您的情况下,由于您正在处理确切的分钟数并且您的开始时间以分钟为单位(即使数据类型被搞砸了),我将只使用一个包含确切分钟数的列。

    要设置该表:

    CREATE TABLE dbo.Minutes (
        start_time  INT NOT NULL,
        CONSTRAINT PK_Minutes PRIMARY KEY CLUSTERED (start_time)
    )
    
    DECLARE
        @hour   TINYINT,
        @minute TINYINT
    
    SET @hour = 19
    SET @minute = 0
    
    WHILE (@hour <= 20)
    BEGIN
        INSERT INTO dbo.Minutes (start_time) VALUES (@hour * 100 + @minute)
    
        SET @minute = @minute + 1
        IF @minute = 60
        BEGIN
            SET @minute = 0
            SET @hour = @hour + 1
        END
    END
    

    现在我们可以选择平均值等。

    SELECT
        M.start_time,
        COUNT(R.seconds)
    FROM
        dbo.Minutes M
    LEFT OUTER JOIN dbo.Records R ON
        M.start_time BETWEEN CAST(R.time AS INT) AND
            (CAST(SUBSTRING(time, 1, 2) AS INT) * 100) +    -- hours
            (FLOOR((CAST(SUBSTRING(time, 3, 2) AS INT) + FLOOR(dur/60))/60)) +  -- carryover to hours
            (CAST(SUBSTRING(time, 3, 2) AS INT) + dur/60) % 60  -- minutes
    GROUP BY
        M.start_time
    

    您需要将其用作子查询来获取给定时间的平均值。由于星期五很晚,我将把这一步留给你;)

    编辑:最后一个警告:我没有考虑跨天边界的时间跨度(即,超过午夜)。希望代码为您指明正确的方向。更好的方法可能是创建一个视图,将所有这些讨厌的字符串转换为实际的 DATETIME 值,然后使用 Minutes 表就变得非常简单了。

    【讨论】:

    • 我觉得你骂我建议循环很有趣,但你的第一个代码示例有一个(尽管我理解其中的区别)。我也喜欢使用视图将模式“更改”为可用格式的建议,我将来肯定会使用它。这种方法确实让我得到了我正在寻找的数据。
    • 呵呵...我希望你不会注意到我的循环。 :)
    【解决方案4】:

    正如 MarkusQ 所说,您对“并发”的定义可以让您在数学上走捷径。

    • 呼叫 (A) 开始于“12:00:59”并结束于“12:01:01”
    • 呼叫 (B) 开始于“12:01:59”并结束于“12:02:01”
      => 在“12:00”间隔内打了 1 次电话
      => 在“12:01”间隔内调用 2 次
      => 在“12:02”间隔内调用 1 次

    则平均并发调用为 (1+2+1)/intervalCount

    (1+2+1) 可以不同的方式计算,并且更快/更容易:

    • 呼叫 (A) 涵盖 2 个不同的分钟间隔(12:00 和 12:01)
    • 呼叫 (B) 涵盖 2 个不同的分钟间隔(12:01 和 12:02)
      => 总覆盖分钟 = 4

    这里的重要事实(以及为什么我在 MarkusQ 发布后打扰回复)是通话本身的持续时间不足以计算覆盖了多少分钟间隔。在我的示例中,两个调用仅持续 2 秒...

    您需要以下信息:
    - “开始时间”,四舍五入到分钟
    - “结束时间”,四舍五入到分钟
    => 覆盖间隔 = 分钟数差 + 1

    要将“时间”字段四舍五入到我会使用它的分钟...

    DATEADD(minute, DATEDIFF(minute, 0, time), 0)
    

    所以一个电话覆盖的分钟数将是......

    DATEDIFF(
       minute,
       DATEADD(minute, DATEDIFF(minute, 0, time), 0),
       DATEADD(second, dur, time)
    ) + 1
    
    No need to round the "end time" down.
    Using DATEDIFF(minute) gives rounding down anyway.
    

    将该值与您正在查看的范围相加,然后除以该范围内的分钟数,您就会得到答案。

    如果您只是在寻找真正并发的调用,则不能使用此类技巧,但仍然可以(我不得不做类似的事情)。但是对于您对并发的定义,应该这样做...

    DECLARE
       @date DATETIME, @start DATETIME, @end DATETIME
    SELECT
       @date = '2009 Jan 01', @start = '12:00', @end = '13:00'
    
    SELECT
       system,
       SUM(
           DATEDIFF(
              minute,
              CASE WHEN
                 CAST(LEFT(time,2) + ':' + RIGHT(time,2) AS DATETIME) < @start
              THEN
                 @start
              ELSE
                 CAST(LEFT(time,2) + ':' + RIGHT(time,2) AS DATETIME)
              END,
              CASE WHEN
                 DATEADD(second, dur, CAST(LEFT(time,2) + ':' + RIGHT(time,2) AS DATETIME)) > @end
              THEN
                 @end
              ELSE
                 DATEADD(second, dur, CAST(LEFT(time,2) + ':' + RIGHT(time,2) AS DATETIME))
              END
           ) + 1
       )
       /
       CAST(DATEDIFF(minute, @start, @end) AS FLOAT)
    FROM
       records
    WHERE
       date = @date
       AND CAST(LEFT(time,2) + ':' + RIGHT(time,2) AS DATETIME) >= @start
       AND DATEADD(second, dur, CAST(LEFT(time,2) + ':' + RIGHT(time,2) AS DATETIME)) < @end
    GROUP BY
       system
    


    这将故意不包括间隔 13:00->13:01
    只有 60 个“1 分钟长间隔”12:00->12:01 到 12:59->13:00


    编辑:

    我刚刚注意到您的时间和日期存储为字符串,您需要将它们转换为 DATETIME 才能使我的代码正常工作。

    EDIT2:

    已修正错误。如果呼叫开始于“11:59:01”并结束于“12:00:01”,则不应计算“11:59”间隔。添加 CASE 语句以进行补偿。

    各种布局编辑

    【讨论】:

    • 我不认为这对我有用。铸造,比如说 12:00(时间字段),到 datetime 结束于 Jan 1 1900 12:00PM。我认为 datediff 函数被抛弃了。
    • 不,没关系。 TSQL 使用 01/01/1900 作为其日期的“零基”。因此,没有“日期”部分的“时间”始终显示为“Jan 01 1900 ??:??:??.???”
    • 哦,对了,因为您指定的是“分钟”部分,而不是整个日期。
    • 但是,当我指定 date='2009 Apr 16'、start ='19:00' 和 end='20:00' (这是我提供的一组数据)
    • 显然“hhmm”不被视为一种时间格式。但是“hh:mm”很好。代替“时间”使用......CAST(LEFT(time,2) + ':' + RIGHT(time,2) AS DATETIME)
    【解决方案5】:

    我通过将数据转换为更简单的格式来解决这个问题。我创建了一个表格,其中每一行代表一分钟的通话。一旦你知道了每小时每分钟的平均值就很简单了。那里有多个选择来显示中间结果。只要查询的时间范围和时长不是特别大,应该没问题...?

    CREATE TABLE #Records(
      seconds char(10),
      [time] char(4),
      date char(8),
      dur int,
      system int,
      port int
    )
    
    /*
    seconds is an s[time] value. It's the difference of seconds from UTC 1/1/1970 00:00:00 to the current UTC [time], we use it as an identifier (like epoch).
    [time] is the [time] the call was made.
    date is the day the call was made.
    dur is the duration of the call in seconds.
    system is the system number.
    port is the port on the system (not particularly relevant for this question).
    */
    
    INSERT INTO #Records(seconds, [time], date, dur, system, port) VALUES('1239924228','1923','20090416',105,2,2)
    INSERT INTO #Records(seconds, [time], date, dur, system, port) VALUES('1239923455','1910','20090416',884,1,97)
    INSERT INTO #Records(seconds, [time], date, dur, system, port) VALUES('1239924221','1923','20090416',116,2,15)
    INSERT INTO #Records(seconds, [time], date, dur, system, port) VALUES('1239924259','1924','20090416',90,1,102)
    INSERT INTO #Records(seconds, [time], date, dur, system, port) VALUES('1239923458','1910','20090416',891,2,1)
    INSERT INTO #Records(seconds, [time], date, dur, system, port) VALUES('1239924255','1924','20090416',99,2,42)
    INSERT INTO #Records(seconds, [time], date, dur, system, port) VALUES('1239924336','1925','20090416',20,2,58)
    INSERT INTO #Records(seconds, [time], date, dur, system, port) VALUES('1239924293','1924','20090416',64,2,41)
    INSERT INTO #Records(seconds, [time], date, dur, system, port) VALUES('1239923472','1911','20090416',888,2,27)
    INSERT INTO #Records(seconds, [time], date, dur, system, port) VALUES('1239924347','1925','20090416',25,1,100)
    INSERT INTO #Records(seconds, [time], date, dur, system, port) VALUES('1239924301','1925','20090416',77,2,55)
    INSERT INTO #Records(seconds, [time], date, dur, system, port) VALUES('1239924332','1925','20090416',52,2,43)
    INSERT INTO #Records(seconds, [time], date, dur, system, port) VALUES('1239924240','1924','20090416',151,1,17)
    INSERT INTO #Records(seconds, [time], date, dur, system, port) VALUES('1239924313','1925','20090416',96,2,62)
    INSERT INTO #Records(seconds, [time], date, dur, system, port) VALUES('1239924094','1921','20090416',315,2,16)
    INSERT INTO #Records(seconds, [time], date, dur, system, port) VALUES('1239923643','1914','20090416',788,2,34)
    INSERT INTO #Records(seconds, [time], date, dur, system, port) VALUES('1239924447','1927','20090416',6,2,27)
    INSERT INTO #Records(seconds, [time], date, dur, system, port) VALUES('1239924342','1925','20090416',119,2,15)
    INSERT INTO #Records(seconds, [time], date, dur, system, port) VALUES('1239924397','1926','20090416',76,2,41)
    INSERT INTO #Records(seconds, [time], date, dur, system, port) VALUES('1239924457','1927','20090416',23,2,27)
    
    /* convert date + [time] into datetimes */
    select 
        seconds,
        system,
        cast(date + ' ' + left([time], 2) + ':' + right([time], 2) as datetime) as start_date,
        /* end date to the minute */
        dateadd(mi, datediff(mi, 0, dateadd(s, dur, cast(date + ' ' + left([time], 2) + ':' + right([time], 2) as datetime))), 0) as end_date
    into 
        #r
    from
        #Records
    
    select * from #r order by system, seconds, start_date, end_date;
    
    /* create a row for each minute of each call */
    create table #r_min(rnd int, seconds char(10), system int, minute datetime)
    
    declare @maxrnd int;
    select @maxrnd = max(datediff(mi, r.start_date, r.end_date)) from #r r
    declare @i int;
    set @i = 0;
    
    while @i < @maxrnd begin
    
        insert into #r_min
        select @i, r.seconds, r.system, dateadd(mi, @i, r.start_date)
        from #r r
        where dateadd(mi, @i, r.start_date) <= r.end_date
    
    set @i = @i + 1
    end
    
    select * from #r_min order by system, seconds, minute
    
    /* concurrent per minute */
    select  
        system, minute, count(*) as cnt
    from 
        #r_min 
    group by
        system, minute
    order by 
        system, minute
    
    /* avg concurrent per minute by hour */
    select
        m.system,
        dateadd(hh, datediff(hh, 0, m.minute), 0) as hour,
        avg(m.cnt) as average_concurrent_per_minute
    from
        (select  
            system, minute, count(*) as cnt
        from 
            #r_min 
        group by
            system, minute
        ) m
    group by
        m.system,
        dateadd(hh, datediff(hh, 0, m.minute), 0)
    
    
    drop table #Records
    drop table #r
    drop table #r_min
    

    最后的选择产生...

    system  hour    average_concurrent_per_minute
    1   2009-04-16 19:00:00.000 1
    2   2009-04-16 19:00:00.000 3
    

    【讨论】:

      【解决方案6】:

      我只能看到一种从通话记录中提取指定数据的方法:

      创建事件列表,其中事件定义为通话开始或通话结束。 (因此每个通话记录将生成两个事件。)每个事件项应包含:系统、日期时间和布尔值开始/结束。日期时间应向下舍入到最接近的分钟。

      按(系统、日期时间)对该列表进行排序并扫描它。对于每个调用开始,将 CURCNT 加一。每次通话结束,CURCNT 减一。

      如果日期时间值与之前的记录不同,请将 CURCNT 添加到 HOURSUM。如果 datetime 值指示新小时的开始,则将 HOURSUM 除以 60,写入新的结果记录(系统、日期、小时、平均值),并将 HOURSUM 重置为零。

      应该很明显什么时候初始化CURCNT和HOURSUM,当系统值与之前的记录不同时怎么办等。

      -阿尔。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-10-22
        • 1970-01-01
        • 1970-01-01
        • 2013-12-05
        • 1970-01-01
        • 2020-05-05
        相关资源
        最近更新 更多