返回最大日期小于SQL中一组日期中每个值的行

时间:2013-12-04 22:38:16

标签: mysql sql

考虑下表:

CREATE TABLE foo (
    id INT PRIMARY KEY,
    effective_date DATETIME NOT NULL UNIQUE
)

给定一组日期D,如何从foo中获取所有行,其中effective_date的最大值小于单个查询中D中的每个日期?

为简单起见,假设每个日期只有一个匹配的行。

假设foo有以下行。

---------------------
| id |effective_date|
---------------------
|  0 |    2013-01-07|
---------------------
|  1 |    2013-02-03|
---------------------
|  2 |    2013-04-19|
---------------------
|  3 |    2013-04-20|
---------------------
|  4 |    2013-05-11|
---------------------
|  5 |    2013-06-30|
---------------------
|  6 |    2013-12-08|
---------------------

如果您获得D = {2013-02-20,2013-06-30,2013-12-19},查询应返回以下内容:

---------------------
| id |effective_date|
---------------------
|  1 |    2013-02-03|
|  4 |    2013-05-11|
|  6 |    2013-12-08|

如果D只有一个元素,比如D = {2013-06-30},你可以这样做:

SELECT *
FROM foo
WHERE effective_date = SELECT MAX(effective_date) FROM foo WHERE effective_date < 2013-06-30 

如果D的大小大于1,如何在IN子句中指定D,那么如何推广此查询?

3 个答案:

答案 0 :(得分:3)

实际上,您的问题是 - 在大多数情况下,您有一个值列表,在MySQL中将其视为 - 而不是作为集合。也就是说 - 可能的解决方案之一是在应用程序中正确生成您的集合,使其看起来像:

SELECT '2013-02-20'
UNION ALL
  SELECT '2013-06-30'
UNION ALL
  SELECT '2013-12-19'

- 然后在JOIN内使用生成的集合。此外,如果MySQL可以接受ANY子查询中的静态列表(例如IN关键字),那将会很棒,但它不能。 ANY还要求设置行,而不是列表(将被视为包含N列的行,其中N是列表中项目的数量。)

幸运的是,在您的特定情况下,您的问题有一个重要的限制:列表中的项目可能不会比foo表中的行更多(否则没有任何意义)。因此,您可以动态构建该列表,然后使用它:

SELECT 
  foo.*, 
  final.period 
FROM 
  (SELECT 
    period, 
    MAX(foo.effective_date) AS max_date 
  FROM 
    (SELECT 
      period 
    FROM 
      (SELECT 
        ELT(@i:=@i+1, '2013-02-20', '2013-06-30', '2013-12-19') AS period 
      FROM 
        foo 
          CROSS JOIN (SELECT @i:=0) AS init) AS dates 
      WHERE period IS NOT NULL) AS list 
      LEFT JOIN foo 
        ON foo.effective_date<list.period 
    GROUP BY period) AS final 
    LEFT JOIN foo 
      ON final.max_date=foo.effective_date

- 您的列表将通过ELT()自动迭代,因此您可以直接将其传递给查询而无需任何其他重组。请注意,此方法将遍历所有foo记录以生成行集,因此它将起作用 - 但在应用程序中执行这些操作可能在性能方面更有用。

您的表格的演示可以找到here

答案 1 :(得分:2)

也许这会有所帮助:

SELECT *
FROM foo
WHERE effective_date IN
(
    (SELECT MAX(effective_date) FROM foo WHERE effective_date < '2013-02-20'),
    (SELECT MAX(effective_date) FROM foo WHERE effective_date < '2013-06-30'),
    (SELECT MAX(effective_date) FROM foo WHERE effective_date < '2013-12-19')
)

结果:

---------------------
| id |effective_date|
---------------------
|  1 |    2013-02-03| -- different
|  4 |    2013-05-11|
|  6 |    2013-12-08|

更新 - 12月6日


创建程序:

DELIMITER $$

USE `test`$$ /*change database name*/

DROP PROCEDURE IF EXISTS `myList`$$

CREATE PROCEDURE `myList`(ilist VARCHAR(100))
BEGIN
    /*var*/
    /*DECLARE ilist VARCHAR(100) DEFAULT '2013-02-20,2013-06-30,2013-12-19';*/
    DECLARE delimeter VARCHAR(10) DEFAULT ',';
    DECLARE pos INT DEFAULT 0;
    DECLARE item VARCHAR(100) DEFAULT '';

    /*drop temporary table*/
    DROP TABLE IF EXISTS tmpList;

    /*loop*/
    loop_item: LOOP
       SET pos = pos + 1;

       /*split*/
       SET item =
       REPLACE(
           SUBSTRING(SUBSTRING_INDEX(ilist, delimeter, pos),
                LENGTH(SUBSTRING_INDEX(ilist, delimeter, pos -1)) + 1),
       delimeter, '');

       /*break*/
       IF item = '' THEN
           LEAVE loop_item;
       ELSE
           /*create temporary table*/
           CREATE TEMPORARY TABLE IF NOT EXISTS tmpList AS (
                    SELECT item AS sdate
               );
       END  IF;
    END LOOP loop_item;

    /*view*/
    SELECT * FROM tmpList;
END$$

DELIMITER ;

致电程序:

CALL myList('2013-02-20,2013-06-30,2013-12-19');

查询:

SELECT
   *,
   (SELECT MAX(effective_date) FROM foo WHERE effective_date < sdate) AS effective_date
FROM tmpList

结果:

------------------------------
|    sdate    |effective_date|
------------------------------
| 2013-02-20  |  2013-02-03  | 
| 2013-06-30  |  2013-05-11  |
| 2013-12-19  |  2013-12-08  |

答案 2 :(得分:0)

首先是坏方法(没有有序的分析函数,或者rank / row_number)

sel tmp.min_effective_date, for_id.id
from
 ( 
 Sel crossed.effective_date,max(SRC.effective_date) as min_effective_date
 from
 foo as src
 cross join
 foo as crossed
 where
 src.effective_date <cross.effective_date
 and crossed.effective_date in 
 (given dates here)
 group by 1
 ) tmp inner join foo as for_id on
 tmp.effective_date =for_id.effective_date

接下来,使用row_number

SEL TGT.id, TGT.effective_date
(Sel id, effective_date, row_number() over(order by effective_date asc) as ordered 
) SRC
INNER JOIN 
(Sel id, effective_date, row_number() over(order by effective_date asc) as ordered ) TGT
on
src.ordered+1=TGT.ordered
where src.effective_date in (given dates)

具有有序分析功能:

sel f.id, tmp.eff 
foo as f inner join
(SEL ID, max(effective_date) over(order by effective_date asc  ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) as eff
from foo
) TMP
on f.id = tmp.id
where f.effective_date in (given dates)
and tmp.eff is not null

上面的查询假设需要选择id,并且源中的id不遵循与日期相同的序列(例如,升序)。否则,您可以立即使用有序的分析功能。