给出一个简单的架构:
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。
答案 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
;