MySQL SELECT с LEFT JOIN неожиданно вставляет NULL в первую строку каждого раздела - PullRequest
1 голос
/ 03 ноября 2019

У меня есть две таблицы. Один с ценами на акции и один с номерами акций для каждой акции. Я хочу объединить две таблицы и рассчитать рыночную капитализацию для каждой акции.

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

CREATE TABLE stock_prices (country_exchange_code VARCHAR(2), stock_code VARCHAR(4), date DATE, close FLOAT, PRIMARY KEY (country_exchange_code,stock_code,date));

INSERT INTO stock_prices VALUES
    ("T", "1301",   '2019-10-29',   75.2),
    ("T", "1301",   '2019-10-30',   76.6),
    ("T", "1301",   '2019-10-31',   77.6),
    ("T", "1301",   '2019-11-01',   77.2),
    ("T", "1332",   '2019-10-29',   52.5),
    ("T", "1332",   '2019-10-30',   49.7),
    ("T", "1332",   '2019-10-31',   50.8),
    ("T", "1332",   '2019-11-01',   50.4),
    ("T", "1333",   '2019-10-29',   13.9),
    ("T", "1333",   '2019-10-30',   13.8),
    ("T", "1333",   '2019-10-31',   14.3),
    ("T", "1333",   '2019-11-01',   14.4);

CREATE TABLE stock_shares (country_exchange_code VARCHAR(2), stock_code VARCHAR(4), Num_Shares INT, PRIMARY KEY (country_exchange_code,stock_code));    

INSERT INTO stock_shares VALUES
    ("T", "1301",   241587962),
    ("T", "1332",   369875187),
    ("T", "1333",   958621587);

Следующий запрос объединяет две таблицы с кодом страны и кодом акции, а затем перечисляет количество акций и цену последнего закрытия, которые являются входными данными для рассчитанного значения рыночной капитализации. Я использую оконную функцию last_value для получения цены последнего закрытия.

SELECT Stock_Code, Date, Num_Shares, 
        last_value(Close) OVER (PARTITION BY Stock_Code ORDER BY Date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS Last_Close,
        Num_Shares * last_value(Close) OVER (PARTITION BY Stock_Code ORDER BY Date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS Mkt_Cap
    FROM stock_prices LEFT JOIN stock_shares USING (Country_Exchange_Code, Stock_Code)
    WHERE Country_Exchange_Code = 'T' AND Date >= '2019-10-29' 
    ORDER BY Stock_Code, Date;

Это работает, как и ожидалось, и дает следующий результат:

Результат 1:

Result 1

Далее я хочу использовать инструкцию DISTINCT, чтобы получить только одну строку для каждой акции. Однако сначала мне нужно избавиться от всех столбцов, кроме Stock_Code и Mkt_Cap. Это где проблема возникает. Когда я исключаю столбец Last_Close из оператора select:

SELECT Stock_Code, Date, Num_Shares, 

        Num_Shares * last_value(Close) OVER (PARTITION BY Stock_Code ORDER BY Date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS Mkt_Cap
    FROM stock_prices LEFT JOIN stock_shares USING (Country_Exchange_Code, Stock_Code)
    WHERE Country_Exchange_Code = 'T' AND Date >= '2019-10-29' 
    ORDER BY Stock_Code, Date;

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

Результат 2:

Result 2

Почему это происходит? В моих таблицах нет пустых значений, и, как мы видели из первого результата, все данные, необходимые для вычисления Mkt_Cap, есть.

Дополнительная информация. Когда я удаляю Date и / или Num_Shares из оператора SELECT, проблем не возникает. Проблема заключается только в удалении функции last_value.

Интересно, что когда предложение WHERE будет удалено, проблема исчезнет. Я не могу понять, как это влияет на результат, потому что в моем небольшом примере это предложение WHERE даже ничего не делает. Все мои данные имеют Country_Exchange_Code = 'T' и имеют дату> = '2019-10-29'. Но в моем реальном наборе данных с миллионами строк это предложение WHERE крайне необходимо. Поэтому удаление предложения WHERE не является решением.

Ответы [ 2 ]

0 голосов
/ 04 ноября 2019

Я не вижу, что вы еще можете сделать, я думаю, что это все еще ошибка. Чтобы обойти это:

SELECT
  Stock_Code, `Date`, Num_Shares, (Num_Shares * Mkt_Cap) Mkt_Cap
FROM
(SELECT Stock_Code, Date, Num_Shares, Close,

         (last_value(Close) OVER (PARTITION BY Stock_Code
         ORDER BY `Date` 
         ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)) AS Mkt_Cap
    FROM stock_prices3 LEFT JOIN stock_shares3 USING (Country_Exchange_Code, Stock_Code)
    WHERE Country_Exchange_Code = 'T' AND Date >= '2019-10-29' 
    ) t1
 ORDER BY Stock_Code, `Date`;

Как видно на последнем из избранных внизу https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=43308a7caac9e804e6a65d48b3fa7490

0 голосов
/ 03 ноября 2019

Сначала я бы предложил использовать псевдонимы на ваших таблицах. Пример в вашем коде, где это может быть важно: WHERE Country_Exchange_Code = 'T', потому что у вас есть столбец Country_Exchange_Code в обеих таблицах. Когда вы закончите с этой частью, я предлагаю вам поставить столбец Дата в кавычки. Затем посмотрите на эту документацию здесь https://dev.mysql.com/doc/refman/8.0/en/window-function-descriptions.html, особенно эту часть:

В следующих описаниях функций over_clause представляет предложение OVER, описанное в Разделе 12.21.2, «Концепции оконных функций»и синтаксис ». Некоторые оконные функции допускают предложение null_treatment, которое определяет, как обрабатывать значения NULL при вычислении результатов. Этот пункт не является обязательным. Это часть стандарта SQL, но реализация MySQL разрешает только RESPECT NULLS (что также является значением по умолчанию). Это означает, что значения NULL учитываются при расчете результатов. IGNORE NULLS анализируется, но выдает ошибку.

Также я нашел это объяснение здесь: Неожиданное поведение в FIRST_VALUE () с IGNORE NULLS (Vertica) Это было полезно (этодля Vertica, но ...)

И проверьте, что ваш расчет Last_Close - это то, что вы хотите (ваш запрос, потому что он дает тот же результат для Last_Close), Здесь DEMO гдеЯ играл и, возможно, это поможет другим или вам ... И вот мое предложение:

select sp.Stock_Code
       , sp.`Date`
       , ss.Num_Shares
       , last_value(sp.Close) OVER (PARTITION BY sp.Stock_Code ORDER BY sp.`Date`) AS Last_Close
from stock_prices sp
LEFT JOIN stock_shares ss USING (Country_Exchange_Code, Stock_Code)
    WHERE ss.Country_Exchange_Code = 'T'
    AND sp.`Date` >= '2019-10-29' 
    ORDER BY ss.Stock_Code, sp.`Date`;
...