如何在过滤器上订购有很多关系

时间:2018-06-29 14:52:47

标签: sql postgresql activerecord

我有users locations和一个联接表visits。每个用户可以访问多个位置,我希望按用户最近一次访问的位置名称对用户进行排序。

CREATE TABLE users
  ("id" int, "name" varchar(128))
;

CREATE TABLE locations
  ("id" int, "name" varchar(128))
;

CREATE TABLE visits
  ("id" int, "user_id" int, "location_id" int, "date" timestamp)
;

INSERT INTO users
    ("id", "name")
VALUES
    (1, 'Alpha'),
    (2, 'Bravo'),
    (3, 'Charlie');

INSERT INTO locations
    ("id", "name")
VALUES
    (4, 'Delta'),
    (5, 'Echo'),
    (6, 'Foxtrott');

INSERT INTO visits
  ("id", "user_id", "location_id", "date")
VALUES
  (1, 1, 4, '2000-02-03 00:00:00'),
  (2, 1, 5, '2000-02-02 00:00:00'),
  (3, 1, 6, '2000-02-01 00:00:00'),
  (4, 2, 6, '2000-01-01 00:00:00'),
  (5, 2, 5, '2000-01-01 00:00:00')
;

我尝试过

SELECT users.id, users.name, max(locations.name) as location_name, max(visits.date) as date
FROM users
LEFT JOIN visits ON users.id = visits.user_id
LEFT JOIN locations ON visits.location_id = locations.id
GROUP BY users.id, users.name
ORDER BY location_name

但是max(locations.name)不依赖于max(visits.date)

另一种尝试是

SELECT users.id, users.name, t.date, locations.name as location_name
FROM users
LEFT JOIN (
  SELECT MAX(date) as date, user_id
  FROM visits
  GROUP BY user_id
) AS t ON users.id = t.user_id
LEFT JOIN visits on visits.date = t.date
LEFT JOIN locations on locations.id = visits.location_id
ORDER BY location_name

但是当一个用户在同一日期有两次访问时,这会产生问题(我不在乎选择哪个位置,但只能是一个)

结果看起来应该像这样,尽管不需要日期。

id  name     date                  location_name
1   Alpha    2000-02-03T00:00:00Z  Delta
2   Bravo    2000-01-01T00:00:00Z  Echo
3   Charlie  (null)                (null)

该解决方案最好是在ActiveRecord中,但纯SQL也可以

http://sqlfiddle.com/#!17/ba2a6/1

3 个答案:

答案 0 :(得分:1)

下面的查询将产生所需的结果。

我已经在CTE中填充了您的示例数据。

也不需要分组和汇总函数,例如max。这里不需要。

      with users(id,name) as (
      select *
      from (
        values
        (1, 'Alpha'),
        (2, 'Bravo'),
        (3, 'Charlie')
      ) t
    ), locations(id,name) as (
      select *
      from(
        values
        (4, 'Delta'),
        (5, 'Echo'),
        (6, 'Foxtrott')
      ) t
    ), visits(id,user_id,location_id,date) as (
      select *
      from(
      VALUES
      (1, 1, 4, '2000-02-03 00:00:00'),
      (2, 1, 5, '2000-02-02 00:00:00'),
      (3, 1, 6, '2000-02-01 00:00:00'),
      (4, 2, 6, '2000-01-01 00:00:00'),
      (5, 2, 5, '2000-01-01 00:00:00')
      ) t
    ), res as (
    select
      distinct on(user_id)
      u.id as user_id,
      u.name,
      l.name as location_name,
      date
    from visits v
    join locations l on l.id=location_id
    right join users u on u.id=v.user_id
    order by user_id,date desc
   )
   select * from res order by location_name

答案 1 :(得分:0)

对分组的小修正:

SELECT users.id, users.name, max(locations.name) as location_name, visits.date as date
FROM users
LEFT JOIN visits ON users.id = visits.user_id
LEFT JOIN locations ON visits.location_id = locations.id
where visits.date = (select max(date) from visits where user_id = users.id)
GROUP BY users.id, users.name,visits.date
ORDER BY location_name

您可以在此处验证其是否提供了正确的结果:http://sqlfiddle.com/#!17/24a0d/27

答案 2 :(得分:0)

干得好@Sabari:

我刚刚编写了一个类似的查询,来到这里看到您已经发布了它。这与仅使用LEFT JOIN的查询基本相同。我还是保留它。

SELECT DISTINCT ON (user_id) u.id
    ,u.name
    ,l.name
    ,DATE
FROM users u 
LEFT JOIN visits v ON u.id = v.user_id 
LEFT JOIN locations l ON l.id = location_id
ORDER BY user_id
    ,DATE DESC

Fiddle Demo