postgresql:根据最新的pivot行选择

时间:2016-08-31 14:35:23

标签: postgresql

给出一个简单的架构:

users
-----
- id<int>
- name<string>

organisations
-------------
- id<int>
- name<string>

organisations_users
-------------------
- id<int>
- userId<int>
- organisationId<int>
- joinedAt<date>

用户和组织之间的N:N关系正在跟踪单个用户的组织历史记录。

如何在组织X中获取当前的所有用户?这基本上与按joinedAt对数据透视表进行排序相同,然后仅为每个用户获取最新的organisationId,然后与匹配的用户一起加入。我已经想到了收集我需要的所有信息的各种方法,但我能想到的唯一方法是filter在应用程序代码而不是SQL查询本身(但我确定有一个这样做的方式)。

样本数据

insert into users (id, name) values
    (1, 'Test User One'),
    (2, 'Test User Two'),
    (3, 'Test User Three');

insert into organisations (id, name) values
    (1, 'Test Org One'),
    (2, 'Test Org Two'),
    (3, 'Test Org Three');

insert into organisations_users ("userId", "organisationId", "joinedAt") values
    (1, 1, '2015-01-01'),
    (1, 2, '2016-01-01'),
    (2, 2, '2015-01-01'),
    (2, 3, '2016-01-01'),
    (3, 3, '2015-01-01'),
    (3, 2, '2016-01-01');

此时,用户[1,3]在组织2中,用户2在组织3中。没有人在组织1中。

预期结果

如果我试图让组织2中的每个人都获得用户1和3(他们的joinedAt日期):

id | name            | organisationId | joinedAt
--------------------------------------------------
1  | Test User One   | 2              | 2016-01-01
3  | Test User Three | 2              | 2016-01-01

获取用户2,因为尽管此用户最初在组织2中,但用户随后加入了组织3。

2 个答案:

答案 0 :(得分:1)

这应该这样做:

select u.id, u.name, ou.organisationId, ou.joinedat
from users u
  join (
     select userid, organisationid, joinedat, 
            max(joinedat) over (partition by userid) as last_join 
     from organisations_users
  ) ou on u.id = ou.userid and ou.joinedat = ou.last_join
where ou.organisationid = 2;

这假设单个用户不能同时在两个组织中。因此,最新的joinat值标识用户的当前组织。

如果用户可以同时加入多个组织,则需要将max(joinedat) over (partition by userid)更改为max(joinedat) over (partition by userid, organisationid)

另一种选择是使用distinct on ()而不是窗口函数。通常distinct on ()解决方案表现更好:

select u.id, u.name, ou.organisationId, ou.joinedat
from users u
  join (
     select distinct on (userid) userid, organisationid, joinedat 
     from organisations_users
     order by userid, joinedat desc
  ) ou on u.id = ou.userid 
where ou.organisationid = 2

答案 1 :(得分:1)

替代方案,避免使用窗口函数和max()

SELECT u.id, u.name
        , ou.organisationId
        , ou.joinedAt
FROM users u
JOIN organisations_users ou
        ON u.id = ou.userId
        AND ou.joinedAt <= now()
        AND NOT EXISTS ( -- suppress all but the latest
                SELECT * FROM organisations_users nx
                WHERE nx.userId = ou.userId
                AND nx.organisationId <> ou.organisationId
                AND nx.joinedAt <= now()
                AND nx.joinedAt > ou.joinedAt
                )
WHERE ou.organisationId = 2
        ;