MySQL:用FROM子句中的相关子查询重写MSSQL?

MySQL:用FROM子句中的相关子查询重写MSSQL?,mysql,sql,database,distinct,correlated-subquery,Mysql,Sql,Database,Distinct,Correlated Subquery,我们有一个包含网站页面视图的表,如: time | page_id ----------|----------------------------- 1256645862| pageA 1256645889| pageB 1256647199| pageA 1256647198| pageA 1256647300| pageB 1257863235| pageA 1257863236| pageC 在我们的生产表中,目前大约有4万行。我们希望为每一天生成过去30天、60天和90天内查

我们有一个包含网站页面视图的表,如:

time      | page_id
----------|-----------------------------
1256645862| pageA
1256645889| pageB
1256647199| pageA
1256647198| pageA
1256647300| pageB
1257863235| pageA
1257863236| pageC
在我们的生产表中,目前大约有4万行。我们希望为每一天生成过去30天、60天和90天内查看的唯一页面数。因此,在结果集中,我们可以查找一天,并查看在这一天之前的60天内访问了多少独特的页面

我们能够在MSSQL中使用查询:

SELECT DISTINCT
 CONVERT(VARCHAR,P.NDATE,101) AS 'DATE', 
 (SELECT COUNT(DISTINCT SUB.PAGE_ID) FROM (SELECT PAGE_ID FROM perflog WHERE NDATE BETWEEN DATEADD(D,-29,P.NDATE) AND P.NDATE) AS SUB) AS '30D',
 (SELECT COUNT(DISTINCT SUB.PAGE_ID) FROM (SELECT PAGE_ID FROM perflog WHERE NDATE BETWEEN DATEADD(D,-59,P.NDATE) AND P.NDATE) AS SUB) AS '60D',
 (SELECT COUNT(DISTINCT SUB.PAGE_ID) FROM (SELECT PAGE_ID FROM perflog WHERE NDATE BETWEEN DATEADD(D,-89,P.NDATE) AND P.NDATE) AS SUB) AS '90D'
FROM PERFLOG P
ORDER BY 'DATE'
注意:由于MSSQL没有FROM_UNIXTIME函数,我们添加了用于测试的数据列,它只是转换的
时间
。生产表中不存在NDATE

将此查询转换为MySQL会导致“未知列p.time”错误:

我理解这是因为我们不能有引用outerfrom子句中的表的相关子查询。但是,不幸的是,我们不知道如何将这个查询转换成MySQL。现在,我们只需返回表中所有不同的行,并在PHP中对其进行后期处理。对于40K行,大约需要2-3秒。当我们有1000行中的100行时,我担心性能

可以在MySQL中实现吗?如果是这样,我们能期望它比我们的PHP后处理解决方案表现得更好吗

更新: 以下是用于创建表的查询:

CREATE TABLE  `perflog` (
    `user_id` VARBINARY( 40 ) NOT NULL ,
    `elapsed` float UNSIGNED NOT NULL ,
    `page_id` VARCHAR( 255 ) NOT NULL ,
    `time` INT( 10 ) UNSIGNED NOT NULL ,
    `ip` VARBINARY( 40 ) NOT NULL ,
    `agent` VARCHAR( 255 ) NOT NULL ,
    PRIMARY KEY (  `user_id` ,  `page_id` ,  `time` ,  `ip`,  `agent` )
) ENGINE MyISAM

到目前为止,我们的生产表大约有4万行

为什么要将子查询埋在这样的第二层中?请尝试以下方法:

SELECT DISTINCT
 FROM_UNIXTIME(P.time,'%Y-%m-%d') AS 'DATE', 
 (SELECT COUNT(DISTINCT SUB.PAGE_ID) FROM perflog WHERE FROM_UNIXTIME(time,'%Y-%m-%d') BETWEEN DATE_SUB(FROM_UNIXTIME(P.time,'%Y-%m-%d'), INTERVAL 30 DAY) AND FROM_UNIXTIME(P.time,'%Y-%m-%d')) AS '30D',
 (SELECT COUNT(DISTINCT SUB.PAGE_ID) FROM perflog WHERE FROM_UNIXTIME(time,'%Y-%m-%d') BETWEEN DATE_SUB(FROM_UNIXTIME(P.time,'%Y-%m-%d'), INTERVAL 60 DAY) AND FROM_UNIXTIME(P.time,'%Y-%m-%d')) AS '60D',
 (SELECT COUNT(DISTINCT SUB.PAGE_ID) FROM perflog WHERE FROM_UNIXTIME(time,'%Y-%m-%d') BETWEEN DATE_SUB(FROM_UNIXTIME(P.time,'%Y-%m-%d'), INTERVAL 90 DAY) AND FROM_UNIXTIME(P.time,'%Y-%m-%d')) AS '90D'
FROM PERFLOG P
ORDER BY 'DATE'

您可以尝试使用单一选择

仅选择日期和之前90天之间的值

然后在每个领域使用案例陈述来检查日期是否在30、60、90之间。对于每个字段,如果大小写为真,则为1,否则为0,并对其进行计数

差不多

SELECT  SUM(CASE WHEN p.Date IN 30 PERIOD THEN 1 ELSE 0 END) Cnt30,
        SUM(CASE WHEN p.Date IN 60 PERIOD THEN 1 ELSE 0 END) Cnt60,
        SUM(CASE WHEN p.Date IN 90 PERIOD THEN 1 ELSE 0 END) Cnt90
FROM    Table
WHERE p.Date IN 90 PERIOD

将子选择更改为联接,如下所示:

select
  FROM_UNIXTIME(P.time,'%Y-%m-%d') AS 'DATE',
  count(distinct p30.page_id) AS '30D',
  count(distinct p60.page_id) AS '60D',
  count(distinct p90.page_id) AS '90D'
from
  perflog p
  join perflog p30 on FROM_UNIXTIME(p30.time,'%Y-%m-%d') BETWEEN DATE_SUB(FROM_UNIXTIME(P.time,'%Y-%m-%d'), INTERVAL 30 DAY) AND FROM_UNIXTIME(P.time,'%Y-%m-%d')
  join perflog p60 on FROM_UNIXTIME(p60.time,'%Y-%m-%d') BETWEEN DATE_SUB(FROM_UNIXTIME(P.time,'%Y-%m-%d'), INTERVAL 60 DAY) AND FROM_UNIXTIME(P.time,'%Y-%m-%d')
  join perflog p90 on FROM_UNIXTIME(p90.time,'%Y-%m-%d') BETWEEN DATE_SUB(FROM_UNIXTIME(P.time,'%Y-%m-%d'), INTERVAL 90 DAY) AND FROM_UNIXTIME(P.time,'%Y-%m-%d')
但是,这可能会运行缓慢,因为一堆函数会消除日期列上的任何标记,更好的解决方案可能是:

create temporary table perf_tmp as
select
  FROM_UNIXTIME(P.time,'%Y-%m-%d') AS 'VIEWDATE',
  page_id
from
  perflog;

create index perf_dt on perf_tmp (VIEWDATE);

select
  VIEWDATE, 
  count(distinct p30.page_id) AS '30D',
  count(distinct p60.page_id) AS '60D',
  count(distinct p90.page_id) AS '90D'
from
  perf_tmp p
  join perf_tmp p30 on p30.VIEWDATE BETWEEN DATE_SUB(P.VIEWDATE, INTERVAL 30 DAY) AND p.VIEWDATE
  join perf_tmp p60 on p60.VIEWDATE BETWEEN DATE_SUB(P.VIEWDATE, INTERVAL 60 DAY) AND p.VIEWDATE
  join perf_tmp p90 on p90.VIEWDATE BETWEEN DATE_SUB(P.VIEWDATE, INTERVAL 90 DAY) AND p.VIEWDATE;

注意:我是在阅读了@astander、@Donnie、@longneck的《解决方案》之后写这篇文章的

我知道性能很重要,但为什么不存储聚合呢?每行10年的天数是3650行,每行只有几列

TABLE dimDate (DateKey int (PK), Year int, Day int, DayOfWeek varchar(10), DayInEpoch....)
TABLE AggVisits (DateKey int (PK,FK), Today int, Last30 int, Last60 int, Last90 int)
这样,您将在一天结束时只运行一次查询,只运行一天。预先计算的聚合是任何高性能分析解决方案(多维数据集)的根

更新

您可以通过引入另一列
DayInEpoch int
(从1990-01-01开始的天数)来加速这些查询。然后您可以删除所有这些日期/时间转换功能。

谢谢您的快速回复。我尝试了你的建议(更正了对SUB的部分引用):几分钟后它仍在运行。我将等待它返回什么,但是,假设它返回正确的数据,在这一点上,它将花费太长的时间来实现(感谢您的回复。我不知道如何将我的条件插入到这样的CASE语句中,因为我从未使用过它们。我的第一次尝试未能通过语法检查。我需要做更多的阅读。请看一下这个CASE语句,谢谢Donnie。查询现在正在运行…大约5分钟。)(我将等待它,看看它是否返回预期/期望的数据。可能的问题是,您被迫将所有日期包装在函数调用中。这意味着它无法在这些字段上使用索引,您最终将进行大量的表扫描。如果您能找到一种方法,您的性能将大大提高。有些地方不太好。)ght。大约30分钟后我们仍在执行。感谢您提供的示例。使用它时,我在多次打开临时表(每次连接一次)时遇到问题。因此,我创建了4个临时表(每个表都相同),并将连接更改为使用这些表。一小时后,查询仍在运行:(还要为每个临时表的page_id添加一个索引。除此之外,我看不出为什么它会这么慢。您可能想发布DDL以创建perflog表。请包括您添加到其中的任何索引。好问题。因为我需要30/60/90天的唯一页面计数,所以我无法存储ea的聚合页面计数。)ch day。我需要每个页面与单个日期关联,以便计算唯一性。如果我将每天的唯一页面相加,我将失去唯一性。表中的数据也用于其他方式。我提供的示例已简化。我还存储单个页面的性能数据(用户加载页面所用的时间)以及浏览器、IP和用户名数据。这些都是我需要每次访问一行(而不是聚合)的其他原因。重新阅读,我看到您现在存储的是“预先计算的聚合”(与您键入的完全相同,很抱歉疏忽).这是一个非常有趣的建议。我喜欢它,但也希望尽量减少我们维护的计划任务数量。
TABLE dimDate (DateKey int (PK), Year int, Day int, DayOfWeek varchar(10), DayInEpoch....)
TABLE AggVisits (DateKey int (PK,FK), Today int, Last30 int, Last60 int, Last90 int)