Улучшение SQL запроса, чтобы найти диапазон между начальной и конечной датой - PullRequest
0 голосов
/ 30 января 2020

Я работаю с базой данных international_education из набора данных world_bank_intl_education bigquery-public-data.

FIELDS            
country_name      
country_code      
indicator_name    
indicator_code    
value   
year

Моя цель - построить линейный график со странами, которые имели наибольшее и Наименьшее изменение в росте населения (годовой процент) (одно из значений indicator_name).

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

query = """
WITH differences AS
    (
    SELECT country_name, year, value, 
      FIRST_VALUE(value)
      OVER (
          PARTITION BY country_name
          ORDER BY year
          RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
      ) AS small_value,
      LAST_VALUE(value)
      OVER (
          PARTITION BY country_name
          ORDER BY year
          RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
      ) AS large_value
    FROM `bigquery-public-data.world_bank_intl_education.international_education`
    WHERE indicator_name = 'Population growth (annual %)'
    ORDER BY year
    )
SELECT country_name, year, (large_value-small_value) AS total_range, value
FROM differences
ORDER BY total_range
        """

Преобразовать в pandas dataframe.

df= wbed.query_to_pandas_safe(query)
df.head(10)

Результирующая таблица.

    country_name            year    total_range value
0   United Arab Emirates    1970    -13.195183  14.446942
1   United Arab Emirates    1971    -13.195183  16.881671
2   United Arab Emirates    1972    -13.195183  17.689814
3   United Arab Emirates    1973    -13.195183  17.695296
4   United Arab Emirates    1974    -13.195183  17.125615
5   United Arab Emirates    1975    -13.195183  16.211873
6   United Arab Emirates    1976    -13.195183  15.450884
7   United Arab Emirates    1977    -13.195183  14.530119
8   United Arab Emirates    1978    -13.195183  13.033461
9   United Arab Emirates    1979    -13.195183  11.071306

Затем я бы нарисовал это с python следующим образом.

all_countries = df.groupby('country_name', as_index=False).max().sort_values(by='total_range').country_name.values

countries = np.concatenate((all_countries[:3], all_countries[-4:]))

plt.figure(figsize=(16, 8))
sns.lineplot(x='year',y='value', data=df[df.country_name.isin(countries)], hue='country_name')

Ответы [ 3 ]

2 голосов
/ 30 января 2020

Вам не нужен CTE, и вам не нужны определения оконной рамы. Так что это должно быть эквивалентно:

SELECT country_name, year, value,
       (first_value(value) OVER (PARTITION BY country_name ORDER BY YEAR DESC) -
        first_value(value) OVER (PARTITION BY country_name ORDER BY YEAR)
       ) as total_range
FROM `bigquery-public-data.world_bank_intl_education.international_education`
WHERE indicator_name = 'Population growth (annual %)';

Обратите внимание, что LAST_VALUE() привередлива с определениями оконной рамы. Поэтому я обычно использую FIRST_VALUE() с обратным порядком.

Если вы хотите, чтобы в каждой стране был только один ряд, то вам нужно агрегировать. BigQuery не имеет «первой» и «последней» функций агрегирования, но их очень легко сделать с массивами:

SELECT country_name, 
       ((array_agg(value ORDER BY year DESC LIMIT 1))[ordinal(1)] -
        (array_agg(value ORDER BY year LIMIT 1))[ordinal(1)]
       ) as total_range
FROM `bigquery-public-data.world_bank_intl_education.international_education`
WHERE indicator_name = 'Population growth (annual %)'
GROUP BY country_name
ORDER BY total_range;
2 голосов
/ 30 января 2020

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

Я думаю, что ниже приведена наименее подробная версия (BigQuery Standard SQL)

#standardSQL
SELECT 
  country_name, 
  year, 
  (LAST_VALUE(value) OVER(win) - FIRST_VALUE(value) OVER(win)) AS total_range, 
  value
FROM `bigquery-public-data.world_bank_intl_education.international_education`
WHERE indicator_name = 'Population growth (annual %)'
WINDOW win AS (PARTITION BY country_name ORDER BY YEAR RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
ORDER BY total_range   

... с тем же результатом, что и исходный запрос

0 голосов
/ 30 января 2020

Если я правильно понимаю, что вы пытаетесь вычислить, я написал запрос, который делает все в BigQuery без необходимости что-либо делать в pandas. Этот запрос возвращает все строки для страны с отголосками, которые занимают первые 3 или 3 нижних места по изменению роста населения.

WITH differences AS
(
SELECT
  country_name,
  year,
  value, 
  LAST_VALUE(value) OVER (PARTITION BY country_name ORDER BY year) - FIRST_VALUE(value) OVER (PARTITION BY country_name ORDER BY year) AS total_range,
FROM `bigquery-public-data.world_bank_intl_education.international_education`
WHERE indicator_name = 'Population growth (annual %)'
ORDER BY year
)
, differences_with_ranks as (
SELECT
  country_name,
  year,
  value,
  total_range,
  row_number() OVER (PARTITION BY country_name ORDER BY total_range) as rank,
FROM differences
)
, top_bottom  as (
SELECT
  country_name
FROM (
  SELECT
    country_name,
  FROM differences_with_ranks
  WHERE rank = 1
  ORDER BY total_range DESC
  LIMIT 3
  )
UNION DISTINCT
SELECT
  country_name
FROM (
  SELECT
    country_name,
  FROM differences_with_ranks
  WHERE rank = 1
  ORDER BY total_range ASC
  LIMIT 3
  )
)
SELECT
  *
FROM differences
WHERE country_name in (SELECT country_name FROM top_bottom)

Я не совсем понимаю, что вы имеете в виду под словом "оптимизировать", этот запрос выполняется очень быстро (1,5 секунды), если вам нужно решение с меньшей задержкой, BigQuery не является правильным решением.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...