【问题标题】:MySQL: The right way to calculate MTDMySQL:计算 MTD 的正确方法
【发布时间】:2019-07-16 23:42:06
【问题描述】:

我不断看到这样的答案:https://stackoverflow.com/a/21277074/4236404 是默认接受的答案,用于在 MySQL 查询中获取本月至今的行。

当前答案:

SELECT id, created
  FROM table1
 WHERE MONTH(created) = MONTH(CURDATE())
   AND YEAR(created) = YEAR(CURDATE())

这个查询可以工作,但效率低下,MySQL 将执行完整扫描以获取结果。

因此,获取本月至今(MTD)查询的行的正确方法如下(我将使用 PDO 和 PHP 来演示:

//get the first day of the month
$month_first_day = date( "Y-m-01" );

//select the record where the created date is equal to or bigger than the first day of the current month.
$stmt = $pdo->prepare('SELECT id, created FROM table1 WHERE created >= :first_day_of_month');
$stmt->bindParam(':first_day_of_month', $month_first_day);
$stmt->execute();
$realm_data = $stmt->fetch();

奖励查询:

要获取今天的所有记录,请执行以下操作:

$today = date( "Y-m-d" );
$stmt = $pdo->prepare('SELECT id, created FROM table1 WHERE created >= :today');
$stmt->bindParam(':today', $today);
$stmt->execute();
$realm_data = $stmt->fetch();

要获取昨天的所有记录,请执行以下操作:

$today = date( "Y-m-d" );
$yesterday = date( "Y-m-d", time()-86400 );
$stmt = $pdo->prepare('SELECT id, created FROM table1 WHERE created >= :yesterday AND created < :today');
$stmt->bindParam(':today', $today);
$stmt->bindParam(':yesterday', $yesterday);
$stmt->execute();
$realm_data = $stmt->fetch();

要获取过去 7 天的所有记录,请执行以下操作:

$seven_days_ago = date('Y-m-d', strtotime('-7 days'));
$stmt = $pdo->prepare('SELECT id, created FROM table1 WHERE created >= :seven_days_ago');
$stmt->bindParam(':seven_days_ago', $seven_days_ago);
$stmt->execute();
$realm_data = $stmt->fetch();

假设/提示:

MySQL中的列设置为日期时间格式。

【问题讨论】:

  • 基于日期或日期时间范围过滤行的规范模式是表具有数据类型 DATE、DATETIME 或 TIMESTAMP 的列;然后将其与范围的开始和范围值的结束进行比较。例如WHERE t.mydtcol &gt;= '2019-02-01' AND t.mydtcol &lt; '2019-02-01' + INTERVAL 1 MONTH. 我们可以使用表达式代替文字,例如WHERE t.mydtcol &gt;= DATE_FORMAT(NOW(),'%Y-%m-01') + INTERVAL 0 MONTH AND t.mydtcol &lt; DATE_FORMAT(NOW(),'%Y-%m-01') + INTERVAL 1 MONTH
  • 很确定这个LIKE CONCAT(DATE_FORMAT(&lt;datetime&gt;), '%Y-%m'), "%") 至少它会在 DATETIME 并且可以使用索引...我看到了你的回答基本上你想避免在 anny 运算符的左侧使用函数或像 IN 这样的关键字或LIKE
  • Sjoe,一个试图帮助别人的教程被投了很多票,人们在下面看不到我的回答和解释吗?
  • 好吧,如果你想为新程序员编写一个编写高效查询的教程,你难道不应该自己知道如何去做吗??
  • 我不明白,我在下面写了帖子,然后stackoverflow说我也需要回答。让我删除当前的 OP 并在那里发布我的答案

标签: php mysql pdo


【解决方案1】:

MySQL 不允许创建基于表达式/函数的索引,有一种解决方法可以从 MySQL 5.7+ 中使用generated columns 进行索引。
确保将表设为 InnoDB,这样可以在非存储生成的列上创建索引。

CREATE TABLE t (
 d DATETIME 
 , dy INT AS (YEAR(d)) 
 , dm INT AS (MONTH(d))
 , INDEX(dy, dm)                    
) Engine = InnoDB ; 

解释

EXPLAIN
SELECT 
 *
FROM 
 t
WHERE
    MONTH(d) = MONTH(CURDATE())
  AND
    YEAR(d) = YEAR(CURDATE())

结果

| id  | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra       |
| --- | ----------- | ----- | ---------- | ---- | ------------- | --- | ------- | --- | ---- | -------- | ----------- |
| 1   | SIMPLE      | t     |            | ALL  | dy            |     |         |     | 19   | 78.95    | Using where |

demo

是的,我知道上面写着ALL,但 MySQL 就在这里。结果返回 15 条记录,该表是 19 条记录。

您还需要知道解释输出中输出的行数是 InnoDB 引擎的预期记录数,而不是实际数。

但是随机磁盘 I.O (6ms * 15) 的 15 倍和流式传输比执行一个随机磁盘 I.O (6ms * 1) 并且可以一次性流式传输数据的全表扫描要贵得多。

MySQL 的优化器/执行是基于成本的,它根据最昂贵的随机磁盘 I/O 计算相对成本。

但请注意,它说可能的键 dy MySQL 知道它现在可以将索引用于

WHERE MONTH(d) = MONTH(CURDATE()) AND YEAR(d) = YEAR(CURDATE())

只有 MySQL 认为我不会通过读取索引来做更多的工作。

仍然等到 MySQL 支持表达式/功能索引,也许他们会在以后的 MySQL 8 版本中添加。
MySQL 8 仍在开发中并获得更新。

CREATE INDEX index_name ON table (YEAR(d));

但我认为这不会很快发生,MySQL 确实或多或少地通过我使用的解决方法支持它,所以它不认为他们急于实现它..

MySQL 8.0.16 之前的 MySQL 也不支持 CHECK CONSTRAINTS,他们也没有急于实现它,因为 MySQL 已经或多或少地通过带有 CHECK OPTION 的 VIEW 来支持它,这是一个很好的例子。

编辑:

Seams 我很快就谈到 MySQL 不会很快支持该功能表达式/功能索引。

我注意到 MySQL 8.0.13 更新添加了表达式/功能索引。

功能关键部件

“普通”索引索引列值或列值的前缀。 例如,在下表中,给定 t1 的索引条目 行包括完整的 col1 值和 col2 值的前缀 由前 10 个字符组成:

MySQL 8.0.13 及更高版本支持索引的功能键部分 表达式值而不是列或列前缀值。用于 功能键部分可以索引不直接存储在 桌子。例子:

CREATE TABLE t1 (col1 INT, col2 INT, INDEX func_index ((ABS(col1)))); CREATE INDEX idx1 ON t1 ((col1 + col2)); CREATE INDEX idx2 ON t1 ((col1 + col2), (col1 - col2), col1); ALTER TABLE t1 ADD INDEX ((col1 * 40) DESC);

source

在 MySQL 8.0.13+ 中你可以这样做

CREATE INDEX index_name ON table (YEAR(created), MONTH(created));

【讨论】:

  • @ErikThiart 看到更新 MySQL 8.0.13+ 接缝已经支持表达式/功能索引
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-06-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多