如果您只想返回一行,最简单的方法是:
SELECT t.*
FROM table_name t
WHERE t.name = '$username'
AND t.theDate >= CAST(DATE_FORMAT(NOW(),'%Y-%m-01') AS DATE)
AND t.theDate < DATE_ADD(DATE_FORMAT(NOW(),'%Y-%m-01'), INTERVAL 1 MONTH)
ORDER BY s.name DESC, s.theDate DESC
LIMIT 1
ORDER BY 是基于您在(或具有前导列)上有索引的假设(name,theDate)
。那将是谓词的最合适的索引(即 WHERE 子句中的条件。我们确实不需要对name
列进行排序,因为我们知道它将等于某物……但是以这种方式指定 ORDER BY使 MySQL 更有可能对索引执行反向扫描操作,以正确的顺序返回行,避免文件排序操作。
注意:我theDate
在 WHERE 子句的条件中指定了裸列,而不是将其包装在任何函数中……通过指定裸列和有界范围,我们使 MySQL 能够使用索引范围扫描操作。还有其他可能的方法可以将此条件包含在 WHERE 子句中,例如...
DATE_FORMAT(t.theDate,'%Y-%m') = DATE_FORMAT(NOW(),'%Y-%m')
这将返回一个等效的结果,但这样的谓词是不可分割的。也就是说,MySQL 不能/不会对索引进行范围扫描来满足这一点。
如果您打算为给定用户获取一个月内“最少”日期的所有行(您的问题似乎并不表明您只需要一行),这是获得该结果的一种方法:
SELECT t.*
FROM table_name t
JOIN ( SELECT s.name
, s.theDate
FROM table_name s
WHERE s.name = '$username'
AND s.theDate >= CAST(DATE_FORMAT(NOW(),'%Y-%m-01') AS DATE)
AND s.theDate < DATE_ADD(DATE_FORMAT(NOW(),'%Y-%m-01'), INTERVAL 1 MONTH)
ORDER BY s.name DESC, s.theDate DESC
LIMIT 1
) r
ON r.name = t.name
AND r.theDate = t.theDate
同样,MySQL 可以使用具有前导列 (name,theDate) 的索引(如果可用)来满足谓词,并执行反向扫描操作(避免排序)和执行 JOIN 操作。
注意:我们在这里假设“theDate”是数据类型 DATE(没有时间组件)。如果它是 DATETIME 或 TIMESTAMP,则可能存在时间分量,并且如果具有相同“日期”的行的时间分量不同,则该查询可能不会返回给定“日期”值的所有行。(例如,'2012-07-13 17:30' 和 '2012-07-13 19:55' 是不同的日期时间值。)如果我们想返回这两行(因为两者都是“7 月 13 日”的日期) ,我们需要进行范围扫描而不是相等测试。
SELECT t.*
FROM table_name t
JOIN ( SELECT s.name
, s.theDate
FROM table_name s
WHERE s.name = '$username'
AND s.theDate >= CAST(DATE_FORMAT(NOW(),'%Y-%m-01') AS DATE)
AND s.theDate < DATE_ADD(DATE_FORMAT(NOW(),'%Y-%m-01'), INTERVAL 1 MONTH)
ORDER BY s.name DESC, s.theDate DESC
LIMIT 1
) r
ON t.name = r.name
AND t.theDate >= r.theDate
AND t.theDate < DATE_FORMAT(DATE_ADD(r.theDate,INTERVAL 1 DAY),'%Y-%m-%d')
请注意最后两行......我们正在寻找任何theDate
值大于或等于当前月份找到的“最小”值并且也小于第二天午夜的行。