Нахождение кратчайшего геопространственного расстояния от одной точки до всех других точек в SQL

#sql #snowflake-cloud-data-platform #window-functions

#sql #snowflake-cloud-data-platform #window-functions

Вопрос:

Существует два типа пользователей, которые покупают билеты в кино либо в городе А, городе В, городе С, либо онлайн.

У меня есть следующие таблицы, как:

Местоположения: Эта таблица состоит из местоположений киноцентров

 |--------------|------------|------------| | Towns | latitude | longitude | |--------------|------------|------------| | Town_A | 72.92629 | -12.89272 | | Town_B | 93.62789 | -83.10172 | | Town_C | 68.92612 | -67.17242 | |--------------|------------|------------|  

Пользователи: Эта таблица содержит историю покупок пользователя, т. е. онлайн или в городах. Также состоит из широты/долготы пользователя во время покупки.

 |------------|------------|------------|--------------| | user_id | latitude | longitude | Towns | |------------|------------|------------|--------------| | 1 | 21.89027 | -53.03772 | Town_A | | 1 | 23.87847 | -41.78172 | Town_C | | 1 | 39.62847 | -80.19892 | online | | 1 | 77.87092 | -96.39242 | Town_A | | 2 | 71.87782 | -38.03782 | online | | 2 | 83.37847 | -62.78278 | Town_B | | 3 | 89.81924 | -80.73892 | Town_B | | 3 | 27.87282 | -18.39183 | Town_A | |------------|------------|------------|--------------|  

Я хочу найти ближайший город на основе lat/long пользователя во время его покупки. Итоговая таблица будет выглядеть следующим образом:

 |------------|------------|------------|--------------|-----------------| | user_id | latitude | longitude | Towns | nearest_town | |------------|------------|------------|--------------|-----------------| | 1 | 21.89027 | -53.03772 | Town_A | Town_B | lt;--- Town_B is near based on his lat/long (Irrespective of his purchase town) | 1 | 23.87847 | -41.78172 | Town_C | Town_A | lt;--- Town_A is near based on his lat/long | 1 | 39.62847 | -80.19892 | online | Town_Online | | 1 | 77.87092 | -96.39242 | Town_A | Town_A | | 2 | 71.87782 | -38.03782 | online | Town_Online | | 2 | 83.37847 | -62.78278 | Town_B | Town_C | | 3 | 89.81924 | -80.73892 | Town_B | Town_A | | 3 | 27.87282 | -18.39183 | Town_A | Town_A | |------------|------------|------------|--------------|-----------------|  

SQL — запрос (Снежинка) моя попытка:

 With specific_location as (  select user_id,  latitude,  longitude,  case when Towns in ('Town_A','Town_B','Town_C') then 'Town' else 'Town_Online' end as purchase_in  from Locations )  select *,   case when purchase_in = 'Town' then  (select Towns from Location qualify row_number() over (order by haversine(user.latitude,user.longitude,location.latitude,location.longitude))=1)  else purchase_in  end as nearest_town  from specific_location  

I’m getting an error: syntax error unexpected 'when' and unexpected 'else'

Ответ №1:

В вашем CTE specific_location отсутствует СОЕДИНЕНИЕ USERS , так как в самих местоположениях нет user_id столбца.

Я бы также сделал обогащенного пользователя, чтобы добавить последовательность, чтобы позже совпадение местоположения могло быть четко для каждой строки пользователя, а затем присоединить пользователя/местоположение во втором CTE, и, таким образом, выбор, который вы делаете в конце, с предварительно вычисленными значениями:

Я также поменял ваши два заявления о ценностях на заявления IFF

 WITH enriched_user AS (  SLECT   u.user_id,  u.latitude,  u.longitude,  u.town,  seq4() as seq,  IFF(towns IN ('Town_A','Town_B','Town_C'), 'Town', 'Town_Online') AS purchase_in  FROM user AS u ), user_and_closest_location AS (  SELECT   u.user_id,  u.latitude,  u.longitude,  u.town,  u.purchase_in  l.town as closest_town  haversine(u.latitude, u.longitude, l.latitude, l.longitude)  FROM enriched_user AS u,  location AS l  QUALIFY row_number() OVER (PARTION BY u.seq ORDER BY haversine(u.latitude, u.longitude, l.latitude, l.longitude)) = 1 ) SELECT   u.user_id,  u.latitude,  u.longitude,  u.town,  IFF(u.purchase_in = 'Town', u.closest_town, u.purchase_in) AS nearest_town FROM user_and_closest_location AS u ORDER BY 1,2,3;   

Логика расчета соединения на основе расстояния для всех строк заключается в том, что это будет быстрее, и если есть вещи, для которых вы не хотите этого делать, было бы лучше сократить ввод там, но затем вам нужно будет вернуться к вводу, чтобы зафиксировать пропущенные значения.

 WITH enriched_user AS (  SLECT   u.user_id,  u.latitude,  u.longitude,  u.town,  seq4() as seq,  IFF(towns IN ('Town_A','Town_B','Town_C'), 'Town', 'Town_Online') AS purchase_in  FROM user AS u ), user_and_closest_location AS (  SELECT   u.user_id,  u.latitude,  u.longitude,  u.town,  u.purchase_in  l.town as closest_town  haversine(u.latitude, u.longitude, l.latitude, l.longitude)  FROM enriched_user AS u,  location AS l  WHERE u.purchase_in = 'Town'  QUALIFY row_number() OVER (PARTION BY u.seq ORDER BY haversine(u.latitude, u.longitude, l.latitude, l.longitude)) = 1 ) SELECT   u.user_id  u.latitude,  u.longitude,  u.town,  IFF(u.purchase_in = 'Town', ucl.closest_town, u.purchase_in) AS nearest_town FROM enriched_user user_and_closest_location AS u LEFT JOIN user_and_closest_location AS ucl   ON u.seq = ucl.seq ORDER BY 1,2,3;  

кроме того, в городах можно было бы перевернуть, чтобы они не были «онлайн»

 IFF(towns IN ('Town_A','Town_B','Town_C'), 'Town', 'Town_Online') AS purchase_in  

становящийся:

 IFF(towns != 'online', 'Town', 'Town_Online')  

в этот момент фактический тест может быть перенесен туда, где он будет использоваться позже.

Комментарии:

1. Спасибо тебе, Симеон. Мне любопытно, почему и как вам пришла в голову мысль об использовании seq4() ?

2. @R0bert Я думал о том, как вы хотели найти «самое близкое» местоположение для каждого пользователя, но заметили, что похоже на несколько покупок для каждого пользователя, но нет идентификатора предварительной покупки, и, следовательно, нужно было что-то сгруппировать/РАЗДЕЛИТЬ по номеру строки. Учитывая, что у вас был предварительный этап подготовки к тренировке, я просто ткнул туда seq4 (). Но если у этой транзакции есть идентификатор, я бы использовал его (предполагая, что ваши данные не так просты, как в приведенном примере).