从第一个和最后一个记录匹配条件中选择列

时间:2012-07-23 23:00:22

标签: mysql sql

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

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天)

5 个答案:

答案 0 :(得分: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

答案 1 :(得分: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(公用表表达式),模拟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出现了,但你很快就会拥有它!

答案 2 :(得分: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 

答案 3 :(得分:-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 {TEMP2 {1}}

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

select * from table1 where free_spots>0

您可以在每次查询后检查是否有0行更新。如果他们是,你已经完成了,你可以打破while循环。

答案 4 :(得分:-1)

这适用于sql server(如果我正确理解你的问题)。它也适用于mysql:

从(选择a.group_id,a.min_date,b.max_date) 选择s1.group_id,min(s1.date)min_date 从斑点s1 分组由s1.group_id)a 内连接 (通过group_id从spot group中选择group_id,max(date)max_date)b 在a.group_id = b.group_id