WHERE中的Mysql虚拟列

Ste*_*ini 2

我有以下MySQL查询:

SELECT from_unixtime( `lastlogin` ) AS `last`
FROM `players` WHERE `lastlogin` <> 0 AND `last` < '2012-10-01 00:00:00';
Run Code Online (Sandbox Code Playgroud)

我得到以下结果:

1054 - 'where子句'中的未知列'last'

last不能在WHERE语句中使用,因为这是一个虚拟列并且不存在."lastlogin"包含UNIX时间戳.

但是有没有一种在where语句中使用虚拟列的正确方法?

谢谢你的帮助.

spe*_*593 5

长话短说

所选答案中的查询“应该有效”;对于简单的集合,性能很好。但对于大型场景来说,这将是一场糟糕的表演。

为了获得最佳性能,我们希望使用引用范围条件中的裸列的查询。像这样的东西:

SELECT from_unixtime( `lastlogin` ) AS `last`
  FROM `players`
 WHERE `lastlogin` > 0
   AND `lastlogin` < UNIX_TIMESTAMP('2012-10-01 00:00:00')
Run Code Online (Sandbox Code Playgroud)

原答案

列别名last(在查询中)不能在(查询的)WHERE 子句中引用。这是MySQL 的语法规则。(该规则的原因基本上是,在处理 WHERE 子句中的谓词时,该别名引用的表达式不可用。)

但是该列别名可以在子句的谓词中引用HAVING。该HAVING子句几乎在执行计划结束时得到处理。这是可行的,但从性能的角度来看是有问题的:

SELECT from_unixtime( `lastlogin` ) AS `last`
  FROM `players` WHERE `lastlogin` <> 0 
 HAVING `last` < '2012-10-01 00:00:00';
Run Code Online (Sandbox Code Playgroud)

其执行计划几乎等同于使用内联视图(创建派生表)并针对派生表运行 SELECT(从性能角度来看这也是有问题的):

SELECT d.*
  FROM (
         SELECT from_unixtime( `lastlogin` ) AS `last`
         FROM `players` WHERE `lastlogin` <> 0 
       ) d
WHERE `last` < '2012-10-01 00:00:00';
Run Code Online (Sandbox Code Playgroud)

last此处可以引用列别名,因为内部查询(别名为 d 的内联视图)首先运行,并且结果集存储为 MyISAM 表。然后外部查询针对 MyISAM 表运行。在此查询中, 正在last引用表中的列名,因此可以在 WHERE 子句中引用它。

重要的提示:

这样就回答了您提出的问题。但,

你其实并不想这么做!

对于大型集,此方法将表现出性能问题,因为此查询本质上是创建表的副本,然后在该副本上运行查询。该 from_unixtime 函数将对表中的每一行执行(lastlogin 为 NULL 或等于 0 的行除外)。

从性能的角度来看,最好使用单个查询(无内联视图)并在 WHERE 子句中的裸列上使用谓词。

理想情况下,lastlogin将存储为 DATETIME 或 TIMESTAMP 数据类型。但是,如果该列是整数,那么将谓词中的文字常量转换为整数,然后将其与裸列进行比较会更好(性能方面)。这将允许 MySQL 至少考虑对作为lastlogin前导列的索引进行范围扫描,而不是对from_unixtime每一行执行该函数,以与文字进行比较。

MySQL 提供了一个方便的UNIX_TIMESTAMP()函数来将 DATETIME 转换为整数值(它基本上是该函数的逆FROM_UNIXTIME()函数,但有一些注意事项。

为了获得最佳性能,我们需要以下形式的查询:

SELECT from_unixtime( `lastlogin` ) AS `last`
  FROM `players`
 WHERE `lastlogin` <> 0
   AND `lastlogin` < UNIX_TIMESTAMP('2012-10-01 00:00:00')
Run Code Online (Sandbox Code Playgroud)