【问题标题】:SQL: Get latest entries from history tableSQL:从历史表中获取最新条目
【发布时间】:2011-12-13 20:41:34
【问题描述】:

我有 3 张桌子

person (id, name)
area (id, number)
history (id, person_id, area_id, type, datetime)

在此表中,我存储了在特定时间哪个人拥有哪个区域的信息。这就像一个推销员在一个地区旅行了一段时间,然后他得到了另一个地区。他也可以同时拥有多个区域。

历史记录类型 = 'I' 代表 CheckIn 或 'O' 代表 Checkout。 示例:

id    person_id    area_id    type    datetime
1     2            5          'O'     '2011-12-01' 
2     2            5          'I'     '2011-12-31' 
A person started traveling in area 5 at 2011-12-01 and gave it back on 2011-12-31.

现在我想要一份所有人现在拥有的所有区域的列表。

person1.name, area1.number, area2.number, area6.name
person2.name, area5.number, area9.number
....

输出也可以是这样的(没关系):

person1.name, area1.number
person1.name, area2.number
person1.name, area6.number
person2.name, area5.number
....

我该怎么做?

【问题讨论】:

  • 所以您要查找当前已签出某个区域但尚未重新签入的人员列表?
  • 我个人建议不要将type 存储为char 值(或至少,它是孤独的)- 将其存储为对详细说明该值的表的 fk 引用 意味着(例如您的personarea 表)。这将条目限制为有效值,可以(轻松)随时更新,而无需 alter 语句......除其他外。
  • 可能历史示例数据第二行中的 id = 1 是错字,应该是 2 或其他数字?
  • @JonathanLeffler:你是对的。我修好了。

标签: mysql sql group-by


【解决方案1】:

这个问题确实很棘手。您需要一个历史条目列表,其中对于给定的用户和区域,有一个“O”记录,没有后续的“I”记录。仅使用历史记录表,转换为:

SELECT ho.person_id, ho.area_id, ho.type, MAX(ho.datetime)
  FROM History AS ho
 WHERE ho.type = 'O'
   AND NOT EXISTS(SELECT *
                    FROM History AS hi
                   WHERE hi.person_id = ho.person_id
                     AND hi.area_id   = ho.area_id
                     AND hi.type = 'I'
                     AND hi.datetime > ho.datetime
                 )
 GROUP BY ho.person_id, ho.area_id, ho.type;

那么,既然你真的只是在人名和地区号码之后(虽然我不确定为什么地区号码不能与其ID相同),你需要稍微适应一下,加入额外的两张表:

SELECT p.name, a.number
  FROM History AS ho
  JOIN Person  AS p  ON ho.person_id = p.id
  JOIN Area    AS a  ON ho.area_id   = a.id
 WHERE ho.type = 'O'
   AND NOT EXISTS(SELECT *
                    FROM History AS hi
                   WHERE hi.person_id = ho.person_id
                     AND hi.area_id   = ho.area_id
                     AND hi.type = 'I'
                     AND hi.datetime > ho.datetime
                 );

NOT EXISTS 子句是一个相关子查询;这往往是低效的。您也许可以使用适当的连接和过滤条件将其重铸为 LEFT OUTER JOIN:

SELECT p.name, a.number
  FROM History AS ho
  JOIN Person  AS p  ON ho.person_id = p.id
  JOIN Area    AS a  ON ho.area_id   = a.id
  LEFT OUTER JOIN History AS hi
    ON hi.person_id = ho.person_id
   AND hi.area_id   = ho.area_id
   AND hi.type = 'I'
   AND hi.datetime > ho.datetime
 WHERE ho.type = 'O'
   AND hi.person_id IS NULL;

所有 SQL 未经验证。

【讨论】:

    【解决方案2】:

    您正在寻找每行可能有不同列数的结果?我想你可能想看看 GROUP_CONCAT()

    SELECT p.`id`, GROUP_CONCAT(a.`number`, ',') AS `areas` FROM `person` a LEFT JOIN `history` h ON h.`person_id` = p.`id` LEFT JOIN `area` a ON a.`id` = h.`area_id`
    

    我还没有测试过这个查询,但我之前以类似的方式使用过 group concat。自然,您会希望根据自己的需要对其进行定制。当然,group concat 会返回一个字符串,所以需要后期处理才能使用数据。

    编辑我认为自从我开始回复以来,您的问题已被编辑。我的查询不再符合您的要求...

    【讨论】:

      【解决方案3】:

      试试这个:

      select *
      from person p
      inner join history h on h.person_id = p.id
      left outer join history h2 on h2.person_id = p.id and h2.area_id = h.area_id and h2.type = 'O'
      inner join areas on a.id = h.area_id
      where h2.person_id is null and h.type = 'I'
      

      【讨论】:

      • 如果一个人已经签出一个区域一次,签入,然后再次签出,这将不起作用。 编辑:也许添加and h2.datetime > h.datetime 可以解决这个问题?我还没想好。
      猜你喜欢
      • 1970-01-01
      • 2012-10-16
      • 2014-11-22
      • 2020-10-04
      • 1970-01-01
      • 2016-01-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多