2017-07-27 124 views
1

我嘗試獲取給定日期前7天的天氣數據,並接近某些座標(經度,緯度)。像20公里的半徑。如果有多個工作站,我可能希望對按天分組的數據進行平均。歷史天氣數據BigQuery

有沒有辦法直接用BigQuery來計算所有這些?爲了測試我算最小和最大座標和創建以下查詢

SELECT 
    * 
FROM 
    [bigquery-public-data:noaa_gsod.gsod2016] a 
JOIN 
    [bigquery-public-data:noaa_gsod.stations] b 
ON 
    a.stn=b.usaf 
    AND a.wban=b.wban 
WHERE 
    (b.lat >= 46.248332 
    AND b.lat <= 47.147654) 
    AND (b.lon >= 5.689853 
    AND b.lon <= 7.001115) 
    AND a.mo='03' 

,我不是很高興與查詢尚未

  • 它通過每天多站不平均它選擇所有 數據給定月份。
  • 我怎樣才能超過特定日期的7天?
  • 可以通過查詢直接計算最大和最小經緯度嗎?
  • 很多時候它沒有找到任何數據,因爲20km的半徑 太小,找不到任何數據。我如何修改查詢到 找到最近的車站,如果在20公里範圍內找不到它的話?
  • 我可以得到更好的免費歷史天氣數據嗎?

我這是怎麼計算的最小最大座標:

maxLat = lat + math.degrees(searchRadius/earthRadius) 
minLat = lat - math.degrees(searchRadius/earthRadius) 
maxLon = lon + math.degrees(searchRadius/earthRadius)/math.cos(math.radians(lat)) 
minLon = lon - math.degrees(searchRadius/earthRadius)/math.cos(math.radians(lat)) 
+0

請顯示一些數據,說明您的問題;我不完全遵循它。 –

+0

什麼部分不清楚?如果您運行上面的查詢,您將從多個工作站獲取數據。它們應該按日期和月份進行平均分組。查詢也只顯示給定月份的數據。但我寧願有一個給定日期的過去一週。 – Chris

+0

不能以任何方式直接回答,但[遷移指南中的示例](https://cloud.google.com/bigquery/docs/reference/standard-sql/migrating-from-legacy-sql#correlated_subqueries)可能會有用。 –

回答

2

這是我能想出的最好的解決辦法:

#standardSQL 
CREATE TEMP FUNCTION distance(lat1 FLOAT64, lat2 FLOAT64, lon1 FLOAT64, lon2 FLOAT64) AS((
WITH data AS(
SELECT POW(SIN((ACOS(-1)/180 * (lat1 -lat2))/2), 2) + COS(ACOS(-1)/180 * (lat1)) * COS(ACOS(-1)/180 * (lat2)) * POW(SIN((ACOS(-1)/180 * (lon1 -lon2))/2), 2) a 
) 
SELECT 6371 * 2 * ATAN2(SQRT((SELECT a FROM data)), SQRT(1 - (SELECT a FROM data))) 
)); 

WITH temperature_data AS(
SELECT 
    CONCAT(year, mo, da) date, 
    temp, 
    b.lat lat, 
    b.lon lon 
FROM `bigquery-public-data.noaa_gsod.gsod2016` a 
JOIN `bigquery-public-data.noaa_gsod.stations` b 
ON a.stn = b.usaf AND a.wban = b.wban 
WHERE concat(year, mo, da) BETWEEN FORMAT_DATE('%Y%m%d', DATE_SUB(PARSE_DATE('%Y%m%d', '20160725'), INTERVAL 7 DAY)) AND '20160725' 
) 

SELECT 
    date, 
    STRUCT(AVG(IF(distance(t.lat, 10.1, t.lon, 10.2) < 20, temp, NULL)) AS avg_temp, STDDEV_SAMP(IF(distance(t.lat, 10.1, t.lon, 10.2) < 20, temp, NULL)) AS std_temp) data_20km, 
    STRUCT(AVG(IF(distance(t.lat, 10.1, t.lon, 10.2) < 50, temp, NULL)) AS avg_temp, STDDEV_SAMP(IF(distance(t.lat, 10.1, t.lon, 10.2) < 50, temp, NULL)) AS std_temp) data_50km, 
    STRUCT(AVG(IF(distance(t.lat, 10.1, t.lon, 10.2) < 100, temp, NULL)) AS avg_temp, STDDEV_SAMP(IF(distance(t.lat, 10.1, t.lon, 10.2) < 100, temp, NULL)) AS std_temp) data_100km, 
    STRUCT(AVG(IF(distance(t.lat, 10.1, t.lon, 10.2) < 200, temp, NULL)) AS avg_temp, STDDEV_SAMP(IF(distance(t.lat, 10.1, t.lon, 10.2) < 200, temp, NULL)) AS std_temp) data_200km, 
    STRUCT(AVG(IF(distance(t.lat, 10.1, t.lon, 10.2) < 500, temp, NULL)) AS avg_temp, STDDEV_SAMP(IF(distance(t.lat, 10.1, t.lon, 10.2) < 500, temp, NULL)) AS std_temp) data_500km 
FROM temperature_data t 
WHERE 
distance(t.lat, 10.1, t.lon, 10.2) < 2000 
GROUP BY date 
ORDER BY date 

我會試着解釋以及您的問題:

我怎樣才能過去特定日期的7天?

查詢temperature_data內,發現有WHERE子句條件:

WHERE concat(year, mo, da) BETWEEN FORMAT_DATE('%Y%m%d', DATE_SUB(PARSE_DATE('%Y%m%d', '20160725'), INTERVAL 7 DAY)) AND '20160725' 

這是過去7天指定日期的選擇。只需更改值「20160725」即可選擇要分析的日期。

可以通過查詢直接計算最大和最小經緯度嗎?

是的。我想你的意思是,如果可以選擇給定範圍內的空間點(比如說20公里)。這樣做的 一種方法是定義一個臨時的函數來計算所需點和車站分,這是在查詢中所表達之間的距離:

CREATE TEMP FUNCTION distance(lat1 FLOAT64, lat2 FLOAT64, lon1 FLOAT64, lon2 FLOAT64) AS((
WITH data AS(
SELECT POW(SIN((ACOS(-1)/180 * (lat1 -lat2))/2), 2) + COS(ACOS(-1)/180 * (lat1)) * COS(ACOS(-1)/180 * (lat2)) * POW(SIN((ACOS(-1)/180 * (lon1 -lon2))/2), 2) a 
) 
SELECT 6371 * 2 * ATAN2(SQRT((SELECT a FROM data)), SQRT(1 - (SELECT a FROM data))) 
)); 

可以隨意修改並測試這個功能,比如:

SELECT distance(50, 60, 30, 10) # result is ~ 1680km 

該功能在這裏使用:

WHERE 
distance(t.lat, 10.1, t.lon, 10.2) < 2000 

濾除點多於2000公里遠離(10.1°,10.2°)。在您的查詢中,您可以選擇不同的輸入值而不是(10.1°,10.2°)。

很多時候它並沒有找到任何數據,因爲最有可能的是20km的半徑太小而無法找到電臺。我如何修改查詢到 找到最近的車站,如果在20公里範圍內找不到它的話?

一種可能的解決方案是在一次查詢幾個不同的距離:

SELECT 
    date, 
    STRUCT(AVG(IF(distance(t.lat, 10.1, t.lon, 10.2) < 20, temp, NULL)) AS avg_temp, STDDEV_SAMP(IF(distance(t.lat, 10.1, t.lon, 10.2) < 20, temp, NULL)) AS std_temp) data_20km, 
    STRUCT(AVG(IF(distance(t.lat, 10.1, t.lon, 10.2) < 50, temp, NULL)) AS avg_temp, STDDEV_SAMP(IF(distance(t.lat, 10.1, t.lon, 10.2) < 50, temp, NULL)) AS std_temp) data_50km, 
    STRUCT(AVG(IF(distance(t.lat, 10.1, t.lon, 10.2) < 100, temp, NULL)) AS avg_temp, STDDEV_SAMP(IF(distance(t.lat, 10.1, t.lon, 10.2) < 100, temp, NULL)) AS std_temp) data_100km, 
    STRUCT(AVG(IF(distance(t.lat, 10.1, t.lon, 10.2) < 200, temp, NULL)) AS avg_temp, STDDEV_SAMP(IF(distance(t.lat, 10.1, t.lon, 10.2) < 200, temp, NULL)) AS std_temp) data_200km, 
    STRUCT(AVG(IF(distance(t.lat, 10.1, t.lon, 10.2) < 500, temp, NULL)) AS avg_temp, STDDEV_SAMP(IF(distance(t.lat, 10.1, t.lon, 10.2) < 500, temp, NULL)) AS std_temp) data_500km 
FROM temperature_data t 
WHERE 
distance(t.lat, 10.1, t.lon, 10.2) < 2000 
GROUP BY date 

注意,這個查詢中提取站點範圍從輸入點(10.1°,10.2°)到2000公里程。然後應用過濾器來選擇20km,50km,100km,200km和500km範圍內的點。

您可以根據需要更改這些值。如果你想從另一個角度得到平均溫度,比如說(40°,30°),只需將數值(10.1,10.2)改爲(40,30)即可。另外,如果您希望距離此點不同,則可以將表達式IF(distance(t.lat, 10.1, t.lon, 10.2) < 200更改爲更適合您需要的範圍。該WHERE子句的條件

說明:

distance(t.lat, 10.1, t.lon, 10.2) < 2000 

因此,這是由多於2000公里濾除所有站遠離點(10.1,10.2)。您也可以根據需要更改此值。

關於這個的最後說明:我還帶了STDDEV_SAMP這是standard deviation of a sampling。這對您來說可能有一定的價值,並且它可以讓您瞭解平均值在平均值附近傳播的程度(通過採樣數據大小的影響進行校正)。如果我們不知道我們真的有多接近正確的價值,平均數本身並不是那麼有價值。

有沒有更好的免費歷史天氣數據可以得到?

不知道。希望這個公共數據集對你來說足夠好。

+0

偉大的解決方案,像一個魅力工作! – Chris

+0

@Chris高興地知道它工作:)! –

0

有了你給了我不知道,如果你可以計算在查詢中的最大/最小數據的信息。在傳統SQL中工作我可能會嘗試嵌套多個查詢,或者加入到計算它們的查詢中,或者兩者兼而有之。

您也許可以在必要時編寫一些調整搜索查詢的內容,但我只是沒有獲得足夠的內容來編寫建議。

對於其他問題:

獲得平均 - 而不是使用*給你打電話要去一切必須單獨調用平均哪些列通過給忽略或組。

選擇特定日期的過去7天 - 這是非常不幸的,似乎沒有時間戳列,所以你必須強制一個。

在LegacySQL我會寫這樣的事:

SELECT dte, avg_temp, avg_cnt_temp 
FROM 
(SELECT CAST(CONCAT(a.year, '-', a.mo, '-', a.da) AS timestamp) AS dte, 
/* This is calling the separate year, month, and day strings as a 
datetime funtion so I can use date_add later */ 
AVG(a.temp) AS avg_temp, AVG(a.count_temp) AS avg_cnt_temp /* You'll 
want to include all of the data you're wanting to call here, I 
only tested with these two */ 
FROM [bigquery-public-data:noaa_gsod.gsod2016] AS a 
JOIN [bigquery-public-data:noaa_gsod.stations] AS b 
ON a.stn=b.usaf AND a.wban=b.wban 
GROUP BY dte, mo, da) 
WHERE dte >= (DATE_ADD('2016-12-31 00:00:00', -7, "DAY")) AND dte <= 
TIMESTAMP('2016-12-31 00:00:00') /* replace with your date */ 

我想在標準的SQL不要嵌套的方式相同。

如果要跨站的數據結合起來,不要叫站標識符等