#sql #date #join
#sql #Дата #Присоединиться
Вопрос:
У меня есть две очень большие таблицы в БД.
Один из них касается членства и когда пользователи были участниками:
User|Date|Membership
1111|2020-12-01 06:00:00|False
1111|2020-12-20 18:00:00|True
1111|2020-12-30 12:00:00|False
2222|2020-12-01 06:00:00|True
2222|2020-12-20 18:00:00|False
2222|2020-12-30 12:00:00|True
...
А другой касается транзакций этих пользователей:
User|Date|Transaction
1111|2020-12-02 06:00:00|3.00
1111|2020-12-19 18:00:00|2.00
1111|2020-12-29 12:00:00|4.00
2222|2020-12-02 06:00:00|1.00
2222|2020-12-19 18:00:00|2.00
2222|2021-01-06 12:00:00|4.00
...
Я хотел бы определить, был ли пользователь участником или нет, когда они совершали транзакцию с новым полем в последней таблице, которое в данном случае будет следующим:
User|Date|Transaction|Was_Member
1111|2020-12-02 06:00:00|3.00|False
1111|2020-12-19 18:00:00|2.00|False
1111|2020-12-29 12:00:00|4.00|True
2222|2020-12-02 06:00:00|1.00|True
2222|2020-12-19 18:00:00|2.00|True
2222|2021-01-06 12:00:00|4.00|True
...
Как я могу эффективно сделать что-то подобное в SQL, где я объединяю временные диапазоны, чтобы определить членство? Подойдет любой язык SQL, просто хочу понять метод.
Ответ №1:
Вы можете объединить две таблицы на основе дат (меньше) и найти последнюю запись с помощью аналитической функции следующим образом:
select user, date, transaction, membership as was_member from
(select t.user, t.date, t.transaction, m.membership,
row_number() over (partition by t.user, t.date order by m.date desc) as rn
from membership m join transactions t
on t.user = m.user and t.date >= m.date) t
where rn = 1
Комментарии:
1. Просто писал то же самое. Должно быть
t.date >= m.date
нет?2. Также может быть записан как коррелированный подзапрос или как apply/lateral. Зависит от размера набора и индексов относительно того, является ли он более производительным
3. Да, в соответствии с требованием. Я написал код, основанный на моем понимании из примеров данных. OP может изменить некоторые условия в соответствии с требованиями.
Ответ №2:
SELECT *,
Was_member =
(SELECT TOP 1 membership
FROM membership m
WHERE m.user = t.user
AND t.date <= m.date)
FROM transaction t;
Для этого требуется хорошая индексация и небольшой набор транзакций, чтобы хорошо работать.
Это также можно сделать с помощью решения с номером строки, которое может быть более эффективным для больших наборов или плохих индексов.
Ответ №3:
Возможно, вам покажется это проще всего с помощью left join
:
select t.*, m.was_member
from transactions t join
(select m.*, lead(date) over (partition by user order by date) as next_date
from members m
) m
on t.user = m.user and
t.date >= m.date and
(t.date < m.next_date or m.next_date is null);
Для повышения производительности вам нужен индекс members(user, date)
.
Могут быть более эффективные методы, в зависимости от того, что вы подразумеваете под «очень большими таблицами», как структурированы данные и какую базу данных вы используете.