使用联合优化按查询分组
Posted
技术标签:
【中文标题】使用联合优化按查询分组【英文标题】:Optimizing group by query with union 【发布时间】:2020-08-28 17:08:58 【问题描述】:我有一个如下所示的 mysql 表:
我想找到一个这样对我的表进行分组的查询:
详情:
a_id
= 地图上的分隔区域
is_flag
= 1 - 如果传感器在区域内/0 - 如果传感器不在区域内
基本上,第一个表格描述了我的传感器在每个时间戳所在的区域。
第二张表告诉我传感器在每个区域进出的时间。
我对每个 area_id 使用 union all 的以下查询,以便在单个表中输出我的传感器如何在区域之间移动的时间段以及它在每个区域内/外的停留时间。
select t.a_id, min(t.timestamp) starttime,max(t.timestamp) endtime,
t.is_flag from(SELECT *,
ROW_NUMBER() OVER(ORDER BY a.timestamp) - ROW_NUMBER() OVER(PARTITION BY
a.is_flag ORDER BY a.timestamp) as GRP
FROM tablename a where areas_id=25 ) t
group by is_flag , GRP, a_id
这是我的 dbfiddle:https://www.db-fiddle.com/f/5pHiYKyx4yHoirRbGX4kP4/0
我的查询可以满足我的需要,但需要一整天的时间。
【问题讨论】:
我看到“它必须是什么样子”,但我不明白为什么会这样。详细解释。 请描述应该实现什么登录 我用更多细节编辑了我的问题。 请看meta.***.com/questions/333952/…。 这是我的数据库小提琴:db-fiddle.com/f/5pHiYKyx4yHoirRbGX4kP4/0 【参考方案1】:WITH
cte1 AS (SELECT CAST(JSON_UNQUOTE(`timestamp`) AS DATETIME) ts,
areas_id,
is_in_or_out
FROM inouts),
cte2 AS (SELECT ts,
areas_id,
is_in_or_out,
CAST(ROW_NUMBER() OVER (PARTITION BY areas_id ORDER BY ts ASC) AS SIGNED)
-CAST(ROW_NUMBER() OVER (PARTITION BY areas_id ORDER BY is_in_or_out, ts ASC) AS SIGNED) AS grp
FROM cte1)
SELECT areas_id,
ANY_VALUE(is_in_or_out) is_in_or_out,
MIN(ts) min_ts,
MAX(ts) max_ts
FROM cte2
GROUP BY areas_id,
grp
ORDER BY areas_id, min_ts;
fiddle
PS1。源数据略有改动。
PS2。在 MySQL 中需要 CAST
,因为 ROW_NUMBER()
产生 bigint unsigned。可以替换为0.0 + ...
。
【讨论】:
MariaDB 得到“FUNCTION range1.ANY_VALUE 不存在”。但是,MIN
可能是一个合适的解决方法。 (有一个功能请求,但没有估计何时实现。)
@RickJames 请指出 OP 在哪里讲述了有关 MariaDB 的信息...谈到 MariaDB,人们不仅可以用 MIN/MAX 替换 ANY_VALUE (或完全删除聚合函数,还可以将 is_in_or_out
添加到 GROUP BY表达式),但也会删除 CAST。
这不是对您的代码的批评,而是对拥有 MariaDB 的人的警告。
如果对于 area_id,is_in_or_out
对于所有最后一个值都为 1,则它不起作用,因为您的 ORDER BY is_in_or_out, ts ASC
不会看到更改。见this。我将 grp 值更改为 CAST(ROW_NUMBER() OVER (PARTITION BY areas_id ORDER BY ts ASC) AS SIGNED) - (CAST(ROW_NUMBER() OVER (PARTITION BY areas_id, is_in_or_out ORDER BY ts ASC) AS SIGNED)+CAST(ROW_NUMBER() OVER (PARTITION BY areas_id ORDER BY is_in_or_out, ts ASC) AS SIGNED) ) /2 AS grp
以解决此问题。但这并不好。【参考方案2】:
这是 sql server 的语法,但在主要 dbms 中应该相同
with
x as (
-- find start/end of each period
select areas_id, is_in_or_out is_flag, timestamp t1
, ISNULL(ABS(is_in_or_out - LAG(is_in_or_out, 1) over (partition by areas_id order by timestamp)), 1) T_START
, ISNULL(ABS(is_in_or_out - LEAD(is_in_or_out, 1) over (partition by areas_id order by timestamp)), 1) T_END
from inouts
),
y as (
select *, LEAD(t1, 1) over (partition by areas_id order by t1) t2
from x
WHERE T_START<>0 OR T_END<>0
)
select areas_id, is_flag, t1 starttime, t2 endtime
from y
WHERE T_START<>0
order by areas_id, t1
应该做的伎俩
【讨论】:
如果你把ISNULL
改成IFNULL
应该是兼容mysql的。在这种情况下,您甚至可以使用COALESCE
,因为所有值都是整数。【参考方案3】:
一些更多信息(例如示例数据和失败的查询)会有所帮助,但看起来您可以只是分组。
select a_id, is_flag, min(timestamp) as starttime, max(timestamp) as endtime
from tablename
group by a_id, is_flag
【讨论】:
这没有帮助,因为它为每个 a_id 返回一行。 这是我的 dbfiddle:db-fiddle.com/f/5pHiYKyx4yHoirRbGX4kP4/0;我的查询可以满足我的需要,但需要一整天的时间。【参考方案4】:我在这里缺少什么?你有没有可能“想太多”的事情?下面的 SQL 给出了与您的示例 db-fiddle 相同的结果集(我在副本上进行了测试),非常简单并且运行速度更快。它为每个 area_id/is_in_or_out 组合提供一行(根据 GROUP BY)。我不太明白为什么需要 UNION 和 ROW_NUMBER() OVER 来使查询复杂化。希望这可以帮助。自己尝试一下,如果有任何问题,请告诉我!
SELECT areas_id,
starttime,
endtime,
is_in_or_out
FROM (SELECT areas_id,
MIN(timestamp) starttime,
MAX(timestamp) endtime,
is_in_or_out
FROM inouts
GROUP BY is_in_or_out,
areas_id) x
ORDER BY starttime;
附:我认为MBeale的解决方案实际上也是正确的(虽然它错过了ORDER BY)。
【讨论】:
如果你的 area_id 有间隙,它就不起作用:例如:1 1 0 0 1 1
,你的 group by 只会留下 2 行,而我们需要 3。
那么您还没有给出您正在寻找的“最小可重现示例”。如前所述,我能够使用此代码重现您的结果集。您需要扩展您的示例数据以阐明此要求。以上是关于使用联合优化按查询分组的主要内容,如果未能解决你的问题,请参考以下文章