【问题标题】:MySQL - Count only unique instances between specific datesMySQL - 仅计算特定日期之间的唯一实例
【发布时间】:2014-05-12 12:31:21
【问题描述】:

我一直在研究其他几个 SO 问题,但我无法从中找出解决方案。首先,描述,然后是我从其他线程中丢失的内容。 (请注意:我非常了解我们数据库的非标准化结构,这是我之前在会议上讨论过的问题,但这是我们所拥有的,也是我必须使用的。)

背景说明

我们有一台可以在 25 个位置生产产品的机器。这些产品的生产数据被记录在一个表格中,其中记录了每个位置的电流和电压。这仅在机器实际生产产品(即机器中有产品)时记录。没有产品存在的时间,没有记录任何内容。

这台机器可以运行在两种不同的生产模式:全面生产和研发生产。全面生产意味着连续插入产品,以便每个实例始终都有一个产品(即机器中始终存在 25 个产品)。第二种模式,研发生产,一次只生产一个产品(即一个产品进入机器,一个一个地经过25个实例,当这个完成后,第二个产品进入机器)。

澄清一下:当产品出现时,每个职位每秒都会记录一次数据,这意味着在全面生产运行时每秒记录 25 个实例。当 R&D 模式运行时,位置 1 将有 ~20 个实例持续 20 秒,位置 2 将有 ~20 个实例持续接下来的 20 秒,依此类推。

表结构

生产数据:

  • id(自动增量)
  • 产品ID
  • 位置
  • 时间(记录数据的时间戳)
  • 电流(安培)
  • 电压(伏特)

问题

我们想计算机器的正常运行时间,但是我们想将生产模式和研发模式的正常运行时间分开,并且我们想每周分开这个数据。

猜测的解决方案

由于我们每秒记录一次实例,因此我可以计算表中时间值的 DISTINCT 实例的数量,以找出生产和研发模式的总正常运行时间。为了找到研发模式,我可以肯定地说,只要有一个只有一个条目的时间实例,我就在研发模式下运行(生​​产模式将有 25 个实例)。

目前的进展

我有以下查询,它总结了所有不同的实例以找到生产和研发模式:

SELECT YEARWEEK(time) AS YWeek, COUNT(DISTINCT time) AS Time_Seconds, ROUND(COUNT(DISTINCT time)/3600, 1) AS Time_Hours 
FROM Database.productiondata
WHERE YEARWEEK(time) >= YEARWEEK(curdate()) - 21
GROUP BY YWeek;

此查询找出表中有多少 DISTINCT 时间实例,并按周计算数量和组。

问题

上面的查询计算了表中存在的实例数量,但我只想找到唯一的实例。基本上,我试图找到类似 IF count(time) = 1 的东西,然后计算那个实例,IF count(time) > 1 然后根本不计算它(DISTINCT 仍然计算这个)。

我查看了其他几个 SO 线程,但几乎都解释了如何使用 DISTINCT 查找唯一值,它只完成了我正在寻找的一半。我得到的最接近的是this,它使用了 HAVING 子句。我目前陷入以下困境:

SELECT YEARWEEK(time) as YWeek, COUNT(Distinct time) As Time_Seconds, ROUND(COUNT(Distinct time)/3600, 1) As Time_Hours
FROM 
(SELECT * FROM Database.productiondata
WHERE time > '2014-01-01 00:00:00'
GROUP BY time
HAVING count(time) = 1) as temptime
GROUP BY YWeek
ORDER BY YWeek;

这里的问题是我们在嵌套的 select 子句中有一个 GROUP BY 时间,它需要永远(今年只有大约 500 万行,所以我可以理解)。我的意思是,从语法上讲,我认为这是正确的,但执行起来需要很长时间。甚至解释这个超时。

这就是我所在的地方。这是正确的方法还是有其他更智能/需要更少查询时间/避免按时间分组的方法?

编辑:作为示例,我们有这个表格(对于格式化道歉,不知道如何在 SO 上制作表格格式)

id    position    time
1     1           1
2     2           1
3     5           1
4     19          1
...   ...         ...
25    7           1
26    3           2
27    6           2
...   ...         ...

此表显示了在进行生产运行时的样子。如您所见,在将数据记录到表中时,没有哪个位置获得第一个条目的通用结构;发生的情况是每秒记录 25 个位置,然后根据 PLC 为每个位置发送数据的速度将数据添加到表中。下表显示了该表在研究模式下运行时的外观。

id    position    time
245   1           1
246   1           2
247   1           3
...   ...         ...
269   1           25
270   2           26
271   2           27
...   ...         ...

由于所有数据都合并到一个表中,我们想找出当 COUNT(time) 正好等于 1 时有多少实例,或者我们可以在 COUNT(time) 严格大于时查找每个实例大于 1。

EDIT2:作为对 Alan 的回复,建议给了我

YWeek    Time_Seconds    Time_Hours
201352   1               0.0
201352   1               0.0
201352   1               0.0
...      ...             ...
201352   1               0.0  (1000 row limit)

而我想要的输出是

Yweek    Time_Seconds    Time_Hours
201352   2146            35.8
201401   5789            96.5
...      ...             ...
201419   8924            148.7

EDIT3:到目前为止,我已经收集了尝试和结果here,并在查询上方以灰色显示。

【问题讨论】:

  • 我很困惑。我看不到这里面的“正常化”缺陷!?!?
  • 我添加了该评论作为预防措施,因为我之前在这里用类似结构提出的问题说“为什么你没有一个单独的表格,其中包含位置?为什么它们在同一个表格中?”等等:)
  • 请看我更新的答案。
  • 感谢您的更新,我在下面回复了其他信息。

标签: mysql sql mysql-workbench


【解决方案1】:

您可能会通过消除子选择来获得更好的结果:

SELECT YEARWEEK(time) as YWeek, 
       COUNT(time) As Time_Seconds, 
       ROUND(COUNT(time)/3600, 1) As Time_Hours
FROM Database.productiondata
WHERE time > '2014-01-01 00:00:00'
GROUP BY YWeek
HAVING count(time) = 1)
ORDER BY YWeek;

我假设time 上面有一个index,但如果没有,您可以期望通过添加一个来显着提高性能。

更新:

根据最近添加的示例数据,我不确定您的方法是否正确。 time 列似乎是一个 INT 代表秒,而您将其视为 DATETIMEYEARWEEK。下面我有一个 SQL 中的工作示例,它完全按照您的要求 IF time 实际上是 DATETIME 列:

DECLARE @table TABLE
    (
      id INT ,
      [position] INT ,
      [time] DATETIME
    )


INSERT  INTO @table
VALUES  ( 1, 1, DATEADD(week, -1, GETDATE()) )
INSERT  INTO @table
VALUES  ( 1, 1, DATEADD(week, -2, GETDATE()) )
INSERT  INTO @table
VALUES  ( 1, 1, DATEADD(week, -2, GETDATE()) )
INSERT  INTO @table
VALUES  ( 1, 1, DATEADD(week, -2, GETDATE()) )
INSERT  INTO @table
VALUES  ( 1, 1, DATEADD(week, -2, GETDATE()) )
INSERT  INTO @table
VALUES  ( 1, 1, DATEADD(week, -3, GETDATE()) )
INSERT  INTO @table
VALUES  ( 1, 1, DATEADD(week, -3, GETDATE()) )

SELECT  CAST(DATEPART(year, [time]) AS VARCHAR)
        + CAST(DATEPART(week, [time]) AS VARCHAR) AS YWeek ,
        COUNT([time]) AS Time_Seconds ,
        ROUND(COUNT([time]) / 3600, 1) AS Time_Hours
FROM    @table
WHERE [time] > '2014-01-01 00:00:00'
GROUP BY DATEPART(year, [time]) ,
        DATEPART(week, [time])
HAVING COUNT([time]) > 0
ORDER BY YWeek;

【讨论】:

  • 大大减少了查询时间!问题是,通过这样做,我每次实例都会得到一行,而我只对按周对它们进行分组感兴趣。它每秒给我一行(因为它按时间和周分组)。就像我想要这个查询的输出,然后计算结果中每周的实例数量,但我不确定那个查询会是什么样子。
  • 我已经更新了我的答案,将分组反转为YWEEK, TIME。如果我理解正确,理论上这应该可以为您提供所需的分组。如果您真的只想按WEEK 分组,请删除, TIME 分组。
  • 仍然得到相同的结果:S 我想知道这是否是因为它在同一个查询中执行它?比如,我首先必须获取所有实例 HAVING count(time) = 1,然后在我的输出中包含该数据后,我使用新数据集按周对其进行分组?
  • 您能否在原始问题中提供一些与所需输出配对的示例数据?另外我要提一下,如果您删除 , TIME 分组,您需要在 YEARWEEK(MAX(time))ROUND(COUNT(Distinct MAX(time))/3600, 1) 上使用聚合函数。
  • 可以,但必须在今晚晚些时候,让我回复您。
【解决方案2】:
SELECT pd1.* 
FROM Database.productiondata pd1
LEFT JOIN Database.productiondata pd2 ON pd1.time=pd2.time AND pd1.id<pd2.id
WHERE pd1.time > '2014-01-01 00:00:00' AND pd2.time > '2014-01-01 00:00:00'
  AND pd2.id IS NULL

你可以LEFT JOIN到同一张表,只留下没有关联的行

UPDATE 查询使用 SQL fiddle 工作

SELECT pd1.* From productiondata pd1
left Join productiondata pd2
ON pd1.time = pd2.time and pd1.id < pd2.id
Where pd1.time > '2014-01-01 00:00:00' and pd2.id IS NULL;

【讨论】:

  • 我不确定我是否理解您在这里所做的事情。 pd1 和 pd2 指的是什么?
  • pd1pd2 是表别名,它们允许您将 productiondata 与自身进行比较。
  • 此查询给了我“where 子句中的时间不明确”,所以我尝试将其更改为 pd1.time(查询超时)和 pd2.time(快速但给出 0 个结果)。澄清(即,看看我是否理解正确):此查询创建两个临时表(pd1 和 pd2),然后它采用两个表中时间相等的实例,但仅当该时间至少有两个实例时。此查询仅针对 2014 年期间的日期执行,但我没有遵循最后一个“and pd2.id is null”语句 - 这是否正确理解?
  • 将时间条件检查替换为 pd1.time>'2014-01-01 00:00:00' 和 pd2.time>'2014-01-01 00:00:00'
  • 该查询再次给了我 0 个结果:S 我将添加一个示例数据,说明我得到了什么,而不是今晚下班时我正在寻找的数据。
猜你喜欢
  • 2021-12-27
  • 1970-01-01
  • 2014-10-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-05
  • 2018-08-17
相关资源
最近更新 更多