如何在大查询中使用 ST_CONTAINS 加入地理列

Posted

技术标签:

【中文标题】如何在大查询中使用 ST_CONTAINS 加入地理列【英文标题】:How to JOIN in geography columns using ST_CONTAINS in Big query 【发布时间】:2019-10-17 07:35:47 【问题描述】:

我有一个 BigQuery 表,其地址包括 Lat/Lng 和其他 BQ 表,其中包含从 Census shapefile 导入的工作几何定义。对于地址表中的每一行,我都在尝试查找包含它的 geom 行。

以下查询是我查找 INDIVIDUAL lat/lng 工作正常:

SELECT SLDLST FROM `geographies.tl_2018_sldl_*` sldl WHERE ST_CONTAINS(sldl.geom, ST_GEOGPOINT(-95.221080, 38.974500));

但是当我尝试抽象成一个像这样的连接时

SELECT 
  address_id,
  SLDLST
FROM `launchpad-239920.address_standardization.temp_delete_geo_match_sample` ssgolden
LEFT JOIN `geographies.tl_2018_sldl_*` sldl ON ST_CONTAINS(sldl.geom, ST_GEOGPOINT(ssgolden.longitude, ssgolden.latitude));

我收到一个错误: “如果没有连接两边字段相等的条件,就不能使用左外连接。”

如何重组我的联接查询以便能够为每个地址提取匹配的地理位置?

【问题讨论】:

为什么不直接使用JOIN?除非您需要在结果中保留不匹配的地址。请澄清/确认 哦..很好。我可能确实想保留不匹配的地址,但我完全可以通过简单的连接来完成它,然后将连接返回到我的原始数据中。如果您想将该评论转换为答案,我很乐意考虑将其标记为已接受。否则,谢谢! 刚刚添加了我的答案 【参考方案1】:

以下是 BigQuery 标准 SQL

如果您想在输出中保留不匹配的地址 - 您可以在下面使用

#standardSQL
WITH matched_addresses AS (
  SELECT 
    address_id,
    SLDLST
  FROM `launchpad-239920.address_standardization.temp_delete_geo_match_sample` ssgolden
  JOIN `geographies.tl_2018_sldl_X` sldl 
  ON ST_CONTAINS(sldl.geom, ST_GEOGPOINT(ssgolden.longitude, ssgolden.latitude)) 
)
SELECT * FROM matched_addresses UNION ALL 
SELECT address_id, NULL 
FROM `launchpad-239920.address_standardization.temp_delete_geo_match_sample`
WHERE NOT address_id IN (SELECT address_id FROM matched_addresses)   

但如果您只对匹配感兴趣 - 请使用以下一个

#standardSQL
WITH matched_addresses AS (
  SELECT 
    address_id,
    SLDLST
  FROM `launchpad-239920.address_standardization.temp_delete_geo_match_sample` ssgolden
  JOIN `geographies.tl_2018_sldl_X` sldl 
  ON ST_CONTAINS(sldl.geom, ST_GEOGPOINT(ssgolden.longitude, ssgolden.latitude)) 
)
SELECT * FROM matched_addresses  

【讨论】:

【参考方案2】:

一种自动处理不匹配地址的解决方案,不需要 Mikhail 建议的 UNION_ALL(因此性能可能会更好):

#standardSQL
WITH addresses AS (
  SELECT *, GENERATE_UUID() uuid
  FROM `bigquery-public-data.new_york_taxi_trips.tlc_yellow_trips_2015`  ssgolden
  WHERE DATE(ssgolden.pickup_datetime) = '2015-10-07'
), matched_addresses AS (
  SELECT ARRAY_AGG(
      IF(
        ST_CONTAINS(sldl.zone_geom, SAFE.ST_GEOGPOINT(ssgolden.pickup_longitude, ssgolden.pickup_latitude))
        , sldl.zone_name, null)
      IGNORE NULLs LIMIT 1)[OFFSET(0)] zone_name
  FROM addresses  ssgolden
  CROSS JOIN `bigquery-public-data.new_york_taxi_trips.taxi_zone_geom`  sldl 
  GROUP BY uuid
)

SELECT zone_name, COUNT(*) c
FROM matched_addresses 
GROUP BY 1
ORDER BY c DESC

现在,让我们针对一组更大的几何图形(74,133 个 - 整个美国及更多 - 响应 Michael 的评论)测试性能:

#standardSQL
WITH addresses AS (
  SELECT *, GENERATE_UUID() uuid
  FROM `bigquery-public-data.new_york_taxi_trips.tlc_yellow_trips_2015`  ssgolden
  WHERE DATE(ssgolden.pickup_datetime) = '2015-10-07'
), matched_addresses AS (
  SELECT ARRAY_AGG(
      IF(
        ST_CONTAINS(sldl.tract_geom, SAFE.ST_GEOGPOINT(ssgolden.pickup_longitude, ssgolden.pickup_latitude))
        , FORMAT('%s %s', sldl._table_suffix,sldl.lsad_name), null)
      IGNORE NULLs LIMIT 1)[OFFSET(0)] zone_name
  FROM addresses  ssgolden
  CROSS JOIN `bigquery-public-data.geo_census_tracts.census_tracts_*`   sldl 
  GROUP BY uuid
)

SELECT zone_name, COUNT(*) c
FROM matched_addresses 
GROUP BY 1
ORDER BY c DESC

【讨论】:

如果区域数量较少,该版本是可以的。但它使用常规 CROSS JOIN 和 ST_CONTAINS 每对计算。 Mikhail 的版本使用了空间优化的 JOIN,因此当 zone 的数量更大时,它会更好地扩展。 你是迈克尔·恩廷吗?因为 Michael Entin 在类似的用例(最近点)中教我这样做。 ***.com/questions/53678306/… (我添加了一个与 74k 美国人口普查区连接的查询。.. Mikhail 的表现更好)

以上是关于如何在大查询中使用 ST_CONTAINS 加入地理列的主要内容,如果未能解决你的问题,请参考以下文章

MySQL查询在大表上很慢

如何使用 Go 在大查询中获取表列名列表

如何更优雅地编写这个 SQL 查询(加入 + 最大查询)

我想在大查询中加入两个具有公共列的表?

如果使用命令行或 sql 在大查询中不存在表,如何创建表

如何在不使用窗口函数的情况下在大查询中选择 max(date)?