0

在处理资产调度的应用程序中考虑下表:

date       group_id  free_spots
2011-01-01   1         0
2011-01-01   2         0
2011-01-08   1         1
2011-01-08   2         0
2011-01-15   1         1
2011-01-15   2         1
2011-01-22   1         2
2011-01-22   2         2
2011-01-29   1         1
2011-01-29   2         0
2011-02-05   1         0
2011-02-05   2         1
2011-02-12   1         0
2011-02-12   2         1
2011-02-19   1         0
2011-02-19   2         0

使用不同表的相当昂贵的查询(~100ms)将这些信息放在一起。结果可以放入临时表中,也可以直接内联使用。

我想要的是找到提供位置的第一个日期(free_spots > 0)。然后在同一个记录中,我想要提供一个位置的最后一个日期。所有这些都由 group_id 分组。

为了说明,在给定的示例表中,我希望得到以下输出:

group_id  start_date  end_date
1         2011-01-08  2011-01-29
2         2011-01-15  2011-01-22
2         2011-02-05  2011-02-12

现在,我已经想出了一个粗略的解决方案。使用给定的表我会:

  • 获取前面*为 NULL 或 free_spots <= 0 (开始日期)的记录的所有记录
  • 对于所有这些记录,获取第一个后继*,该后继 * 由 NULL 或 free_spots <= 0 的行所接
  • 以某种方式在这里混合了 group_id 分组。

但是,这似乎是不可能的,因为我不能再次使用相同的子查询来查找后继或先前的记录。同样处理临时表。这些我不能多次打开和重复使用。

( * 之前或之后是基于日期。对于每个组,日期是相等的、连续的并且均匀(但任意)间隔。通常是 7 或 14 天)

4

5 回答 5

1

它可能不是超级高效,但它适用于您的数据。(请注意,如果需要,我为日期范围添加了 WHERE 约束):

SELECT group_id,MIN(`date`) AS start_date,
       (SELECT `date` FROM Slots s3 
        WHERE s3.group_id=t.group_id 
        AND s3.`date`<t.next_stop_date
        AND s3.free_spots > 0
        ORDER BY s3.`date`DESC
        LIMIT 1) as end_date
FROM 
  (SELECT s1.*, MIN(s2.`date`) AS next_stop_date 
   FROM 
     Slots s1 LEFT JOIN Slots s2 
       ON s2.`date` > s1.date AND s1.group_id=s2.group_ID AND s2.free_spots = 0
   WHERE s1.free_spots > 0 
   GROUP BY s1.group_id, s1.`date`
   ORDER BY s1.group_id ASC, s1.`date` ASC
  ) AS t
GROUP BY group_id, next_stop_date
于 2012-07-24T00:01:40.543 回答
1

我可以在 SQL Server 中编写它,并且知道它可以转换为 MySQL。首先我会给你 SQL Server 版本,然后在下面的翻译上给你一个提升。我会跳过这个问题,但起初并没有意识到它是针对 MySQL 的。

这允许日期之间任何长度的可变间隙。

WITH IDs AS (
   SELECT *, Row_Number() OVER (PARTITION BY GroupID ORDER BY AvailableDate) ID
   FROM Availability
), Data AS (
   SELECT
      GroupID,
      AvailableDate,
      ID - Dense_Rank() OVER (PARTITION BY GroupID ORDER BY ID) G
   FROM IDs
   WHERE FreeSpots > 0
)
SELECT
   GroupID,
   Min(AvailableDate) FromDate,
   Max(AvailableDate) ToDate
FROM Data
GROUP BY GroupID, G;

这是设置脚本:

CREATE TABLE Availability (
   AvailableDate datetime,
   GroupID tinyint,
   FreeSpots tinyint
)
INSERT Availability
SELECT '20110101', 1, 0
UNION ALL SELECT '20110101', 2, 0
UNION ALL SELECT '20110108', 1, 1
UNION ALL SELECT '20110108', 2, 0
UNION ALL SELECT '20110115', 1, 1
UNION ALL SELECT '20110115', 2, 1
UNION ALL SELECT '20110122', 1, 2
UNION ALL SELECT '20110122', 2, 2
UNION ALL SELECT '20110129', 1, 1
UNION ALL SELECT '20110129', 2, 0
UNION ALL SELECT '20110205', 1, 0
UNION ALL SELECT '20110205', 2, 1
UNION ALL SELECT '20110212', 1, 0
UNION ALL SELECT '20110212', 2, 1
UNION ALL SELECT '20110219', 1, 0
UNION ALL SELECT '20110219', 2, 0

MySQL 翻译

下面应该等价于我的第一个 CTE(Common Table Expression),模拟 Row_Number() 函数。稍作调整,您可以将其用作派生表,用于执行第二个 CTE 以模拟 Dense_Rank(),并且您有一个有效的查询!

SELECT
   GroupID,
   AvailableDate,
   FreeSpots,
   @rownum=CASE WHEN @grpset <> GroupID THEN 0 ELSE @rownum + 1 END AS rownum,
   @grpset=GroupID AS grpset
FROM
   (SELECT @grpset= -1) g,
   (SELECT @rownum:= -1) r,
   (SELECT *
    FROM Availability
    ORDER BY GroupID, AvailableDate
   ) a

我一点也不知道 MySQL,所以我从网上的一个例子中抄袭。这应该可行,但我可能会让语法错误溜进来。如果此查询有效并且您需要更多帮助,请告诉我,我将尝试将其添加到 MySQL 的完整查询中。虽然如果@Quassnoi 出现,你很快就会拥有它!

于 2012-07-24T00:03:36.847 回答
0

查询不是很好,但似乎有效:

SELECT *
FROM (

SELECT a.group_id, a.`date` AS
start_date , max( b.`date` ) AS
end_date
FROM test AS a
LEFT JOIN test AS b ON a.group_id = b.group_id
AND b.free_spots >0
AND a.date < b.date
WHERE a.free_spots >0
AND (

SELECT count( * )
FROM test AS c
WHERE c.group_id = a.group_id
AND c.date > a.date
AND c.date < b.date
AND free_spots =0
) =0
GROUP BY group_id,
start_date
) AS d
WHERE end_date IS NOT NULL
GROUP BY d.end_date
ORDER BY `d`.`group_id` ASC 
于 2012-07-24T00:24:04.933 回答
-1

我想不出一个超级简单的方法来做到这一点。这是我能想到的一种方式的草图(使用多个查询)

create temporary table temp1 select group_id,min(date) as start_date from table1 where free_spots>0 group by group_id

alter table temp1 add column end_date datetime default null

create temporary table温度2select * from table1 where free_spots>0

然后在一个 while 循环中(使用某种编程语言),我会执行以下操作,直到 temp2 为空。您应该在循环中的每个步骤将日期增加到第二天(称为 $curDate):

update temp2,temp1 set temp1.end_date=temp2.date where temp1.group_id=temp2.group_id and temp2.date='$curDate' and temp2.free_spots>0

您可以在每次查询后检查是否更新了 0 行。如果是这样,你就完成了,你可以打破 while 循环。

于 2012-07-23T23:24:56.047 回答
-1

这适用于 sql server(如果我正确理解了您的问题)。它也应该在 mysql 上工作:

select a.group_id, a.min_date, b.max_date from (select s1.group_id, min(s1.date) min_date from spot s1 group by s1.group_id) 内部连接
​​(select group_id, max(date) max_date from spot group by group_id) b on a.group_id = b.group_id

于 2012-07-23T23:46:40.673 回答