Обход подзапроса BigQuery и применение ограничений - PullRequest
0 голосов
/ 04 июня 2018

У меня есть запрос SQL Server, который я пытаюсь преобразовать для запуска в BigQuery.Здесь задействованы три таблицы:

CalendarMonths

     FirstDayOfMonth        |    FirstDayOfNextMonth
----------------------------+----------------------------
2017-02-01 00:00:00.000 UTC | 2017-03-01 00:00:00.000 UTC
2017-03-01 00:00:00.000 UTC | 2017-04-01 00:00:00.000 UTC

Клиенты

clientid |     name       | etc.
---------+----------------+------
1        |  Bob's Shop    |
2        | Anne's Cookies |

ClientLogs

 id | clientid |   timestamp    | price_current | price_old | license_count_current | license_count_old |
----+----------+----------------+---------------+-----------+-----------------------+---------------
1   |     1    | 2017-02-01 UTC |      1200     |     0     |          10           |          0        |
2   |     1    | 2018-02-03 UTC |      2400     |    1200   |          20           |         10        |
3   |     2    | 2016-07-13 UTC |      1200     |     0     |          10           |          0        |
4   |     2    | 2018-03-30 UTC |       0       |    1200   |           0           |         10        |

Запрос T-SQL выглядит примерно так:

SELECT 
    FirstDayOfMonth, FirstDayOfNextMonth,
    (SELECT SUM(sizeatdatelog.price_current) 
     FROM clients c
     CROSS APPLY (SELECT TOP 1 * 
                  FROM clientlogs 
                  WHERE clientid = c.clientid 
                    AND [timestamp] < cm.FirstDayOfMonth 
                  ORDER BY [timestamp] DESC) sizeatdatelog
     WHERE sizeatdatelog.license_count_current > 0) as StartingRevenue,
    (another subquery for starting client count) as StartingClientCount,
    (another subquery for churned revenue) as ChurnedRevenue,
    (there are about 6 other subqueries)
FROM 
    CalendarMonths cm
ORDER BY 
    cm.FirstDayOfMonth

И окончательный вывод выглядит так:

     FirstDayOfMonth        |    FirstDayOfNextMonth      | StartingRevenue | StartingClientCount | etc
-------------------------------------------------------------------------------------------------------
2017-02-01 00:00:00.000 UTC | 2017-03-01 00:00:00.000 UTC |   68382995.43   |        79430        |
2017-03-01 00:00:00.000 UTC | 2017-04-01 00:00:00.000 UTC |   69843625.12   |        80430        |

В BigQuery я добавилпростой подзапрос в предложении select, и он прекрасно работал:

SELECT FirstDayOfMonth, FirstDayOfNextMonth, (SELECT clientId FROM clientlogs LIMIT 1 ) as cl 
FROM CalendarMonths cm
ORDER BY cm.FirstDayOfMonth

Однако, как только я добавляю предложение where в подзапрос, я получаю это сообщение об ошибке:

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

Как мне поступить с этого момента?Если я не могу получить результаты, которые я ищу, в одном запросе, возможно, мне стоит заняться созданием нескольких запланированных заданий, которые создают временные таблицы, и последним запланированным заданием, которое объединяет все это.Или, может быть, я мог бы посмотреть на это в коде через GCP или использовать API BigQuery в сценариях приложения.Размер данных невелик, и запрос выполняется не часто.Мне нужно удобство обслуживания, а не эффективность, поэтому в идеале есть способ объединить эти данные в один запрос.

Ответы [ 3 ]

0 голосов
/ 05 июня 2018

Коррелированный подзапрос, такой как

ВЫБРАТЬ ТОП 1 * ИЗ клиентских журналов, ГДЕ clientid = c.clientid И [отметка времени]

в BigQuery обычно требуетсяпереписать с помощью агрегации по линиям

SELECT ARRAY_AGG (foo ORDER BY [timestamp] DESC LIMIT 1) [offset (0)] FROM ... as foo ГДЕ коррелированное условие

BigQuery moreможет работать с простыми коррелированными подзапросами в виде

SELECT {необязательное агрегирование} ИЗ таблицы WHERE {коррелированное условие}

0 голосов
/ 09 июня 2018

Ради сообщества я отправляю запрос, которым в итоге воспользовался.Огромное спасибо Михаилу Берлянту за помощь в этом вопросе.

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

WITH previousMonths AS (
    SELECT *
    FROM (
        SELECT FirstDayOfMonth, FirstDayOfNextMonth, account_c,
          FIRST_VALUE(acl.timestamp_c ) OVER (start_values) timestamp_c,
          FIRST_VALUE(acl.acv_current_c ) OVER (start_values) acv_current_c,
          FIRST_VALUE(acl.license_count_current_c) OVER(start_values) license_count_current_c,
          FIRST_VALUE(acl.price_current_c) OVER (start_values) price_current_c
        FROM warehouse.project.calendar_months cm
        JOIN warehouse.project.account_change_logs acl ON timestamp_c < FirstDayOfMonth
        WINDOW start_values AS (PARTITION BY account_c, FirstDayOfMonth ORDER BY timestamp_c DESC)
    )
    GROUP BY FirstDayOfMonth, FirstDayOfNextMonth, account_c, 
      timestamp_c, acv_current_c, license_count_current_c, price_current_c
  ), 
  currentMonth AS (
    SELECT *
    FROM (
        SELECT FirstDayOfMonth, FirstDayOfNextMonth, account_c,
          FIRST_VALUE(acl.timestamp_c ) OVER (change_values) timestamp_c,
          FIRST_VALUE(acl.acv_current_c ) OVER (change_values) acv_current_c,
          FIRST_VALUE(acl.license_count_current_c) OVER(change_values) license_count_current_c,
          FIRST_VALUE(acl.acv_old_c) OVER(PARTITION BY account_c, FirstDayOfMonth ORDER BY timestamp_c) acv_old_at_start_of_month_c,
          FIRST_VALUE(acl.license_count_old_c) OVER(PARTITION BY account_c, FirstDayOfMonth ORDER BY timestamp_c) license_count_old_at_start_of_month_c,
          FIRST_VALUE(acl.price_current_c) OVER (change_values) price_current_c
        FROM warehouse.project.calendar_months  cm
        JOIN warehouse.project.account_change_logs acl
        ON timestamp_c >= FirstDayOfMonth AND timestamp_c < FirstDayOfNextMonth 
        WINDOW change_values AS (PARTITION BY account_c, FirstDayOfMonth ORDER BY timestamp_c DESC)
    )

    GROUP BY FirstDayOfMonth, FirstDayOfNextMonth, account_c, 
      timestamp_c, acv_current_c, acv_old_at_start_of_month_c, license_count_current_c, 
      license_count_old_at_start_of_month_c, price_current_c
)

SELECT FirstDayOfMonth, FirstDayOfNextMonth, 
  (SELECT COUNT(acv_current_c) FROM previousMonths pm WHERE pm.FirstDayOfMonth = cal.FirstDayOfMonth
    AND license_count_current_c > 0) as StartingAccounts,

  (SELECT COUNT(acv_current_c) FROM currentMonth cm WHERE cm.FirstDayOfMonth = cal.FirstDayOfMonth
    AND license_count_old_at_start_of_month_c = 0 AND license_count_current_c > 0) as NewAccounts,

  (SELECT COUNT(acv_current_c) FROM currentMonth cm WHERE cm.FirstDayOfMonth = cal.FirstDayOfMonth
    AND license_count_current_c = 0) as ChurnAccounts,

  (SELECT SUM(license_count_current_c) FROM previousMonths pm WHERE pm.FirstDayOfMonth = cal.FirstDayOfMonth
    AND license_count_current_c > 0) as StartingUsers,

  (SELECT SUM(license_count_current_c) FROM currentMonth cm WHERE cm.FirstDayOfMonth = cal.FirstDayOfMonth
    AND license_count_old_at_start_of_month_c = 0 AND license_count_current_c > 0) as NewUsers,

  (SELECT SUM(license_count_current_c - license_count_old_at_start_of_month_c) FROM currentMonth cm WHERE cm.FirstDayOfMonth = cal.FirstDayOfMonth
    AND license_count_old_at_start_of_month_c < license_count_current_c
    AND license_count_old_at_start_of_month_c <> 0) as ExpansionUsers,

  (SELECT SUM(license_count_old_at_start_of_month_c - license_count_current_c) FROM currentMonth cm WHERE cm.FirstDayOfMonth = cal.FirstDayOfMonth
    AND license_count_old_at_start_of_month_c > license_count_current_c
    AND license_count_current_c <> 0) as ContractionUsers,

  (SELECT SUM(license_count_old_at_start_of_month_c - license_count_current_c) FROM currentMonth cm WHERE cm.FirstDayOfMonth = cal.FirstDayOfMonth
    AND license_count_old_at_start_of_month_c > license_count_current_c
    AND license_count_current_c = 0) as ChurnUsers,

  (SELECT SUM(acv_current_c) FROM previousMonths pm WHERE pm.FirstDayOfMonth = cal.FirstDayOfMonth
    AND license_count_current_c > 0) as StartingARR 

    --etc, etc,


FROM warehouse.project.calendar_months cal
ORDER BY FirstDayOfMonth
0 голосов
/ 04 июня 2018

Ниже для BigQuery Standard SQL

#standardSQL
SELECT FirstDayOfMonth, FirstDayOfNextMonth, 
  SUM(price_current) StartingRevenue, COUNT(1) StartingClientCount 
FROM (
  SELECT FirstDayOfMonth, FirstDayOfNextMonth, 
    clientid, price_current
  FROM (
    SELECT FirstDayOfMonth, FirstDayOfNextMonth, clientid,
      FIRST_VALUE(price_current) OVER(latest_values) price_current,
      FIRST_VALUE(license_count_current) OVER(latest_values) license_count_current
    FROM `project.dataset.CalendarMonths` cm
    JOIN `project.dataset.ClientLogs` cl
    ON `timestamp` < FirstDayOfMonth 
    WINDOW latest_values AS (PARTITION BY clientid ORDER BY `timestamp` DESC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
  )
  WHERE license_count_current > 0
  GROUP BY FirstDayOfMonth, FirstDayOfNextMonth, clientid, price_current
)
GROUP BY FirstDayOfMonth, FirstDayOfNextMonth
ORDER BY FirstDayOfMonth  

, скорее всего, выше, может быть расширен до остальных ваших подзапросов

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