Как я могу сделать рекурсивный запрос или подзапрос внутри запроса? - PullRequest
0 голосов
/ 19 января 2019

Я не уверен, что мне нужен именно рекурсивный запрос, но это то, что я пытаюсь сделать (запрос A, который вызывает запрос B, который вызывает запрос A ... рекурсивно).

Это мой минимальный полный проверяемый код:

У меня есть такая таблица (MySQL v5.7, InnoDB):

CREATE TABLE transactions 
(
    id INT PRIMARY KEY AUTO_INCREMENT,
    code VARCHAR(10),
    date DATETIME,
    mode ENUM('Buy', 'Sell', 'Count', 'Return'),
    quantity INT,
    price DECIMAL(10,2),
    price_currency ENUM('ARS', 'USD'),
    usd_to_ars DECIMAL(10,2),
    return_id INT NULL DEFAULT NULL
)

А затем я заполняю ее некоторыми элементами:

INSERT INTO transactions (code, date, mode, quantity, price, price_currency, usd_to_ars) 
VALUES 
("a", "20180101", 'Buy', 4, 10, 'ARS', 3.7),
("a", "20180102", 'Buy', 9, 8, 'ARS', 5.8),
("a", "20180103", 'Sell', -3, 0, 'USD', 0),
("b", "20180104", 'Buy', 5, 5, 'USD', 8.9),
("a", "20180105", 'Buy', 2, 7, 'USD', 3.4),
("b", "20180106", 'Buy', 1, 8, 'ARS', 9),
("a", "20180107", 'Sell', -8, 0, 'USD', 4.4),
("a", "20180108", 'Buy', 9, 9, 'ARS', 3.2);

INSERT INTO transactions (code, date, mode, quantity, price, price_currency, usd_to_ars, return_id) 
VALUES ("a", "20180109", 'Return', 6, 2, 'ARS', 2, 2);

Наконец-то я выполняю этот код:

SELECT * 
FROM  
    (SELECT
         id, date, code, mode, quantity, price, price_currency, usd_to_ars, return_id,
       @acm := @acm + quantity as stock,
       @avr := (@avr * (@acm - quantity) +
               if(quantity > 0, quantity *
                   if(mode = "Return", @avr,
                        if(price_currency = 'USD', price, price / usd_to_ars)
                     ),
                   quantity * @avr)
                 ) / @acm as average_price_usd
FROM
    transactions t1,
    (SELECT @acm := 0) x,
    (SELECT @avr := 0) y) t2
ORDER BY id DESC

dbfiddle.uk

Как видите, это не вызывает ошибки, этовозвращает таблицу, она работает ... но не так, как я хочу.

В четвертой строке:

@avr := (@avr * (@acm - quantity) + if(quantity > 0, quantity * if(mode = "Return", @avr, if(price_currency = 'USD', price, price / usd_to_ars)), quantity * @avr)) / @acm as average_price_usd

Я хотел бы изменить параметр @avr для:

If(mode = "Return", @avr, [...])

В настоящий момент, если mode = "Return" равно true, используется текущее значение @avr, но я бы хотел использовать значение @avr записи, где id = return_id.И из-за @avr является вычисленным значением, я должен выполнить подзапрос, чтобы вычислить его снова ... Я думаю.Проблема в том, что я понятия не имею, как это сделать.

enter image description here

Итак, средняя цена id = 9 не должна быть 3.02..., вместо этого это должно быть что-то вроде 2.72....

Итак, как я могу выполнить этот запрос внутри другого запроса, когда mode = "Return", чтобы получить @avr значение записи WHERE subquery.id = return_id?

Если это возможно, я новичок в SQL, поэтому я все еще не уверен, какие вещи возможны, а какие невозможны.Для меня не имеет значения, нужно ли мне создать столбец в таблице, представлении или функции, я думаю, что справлюсь с любым из них.

1 Ответ

0 голосов
/ 20 января 2019

Введение: ваша скрипка БД показывает, что вы, похоже, используете MySQL 8.0.

Поскольку у вас уже есть рабочий запрос, и вам просто нужно обработать конкретный случай mode = 'Return', одним из решений может быть преобразование вашего запроса в CTE и самостоятельное его присоединение, чтобы поместить записи Return в соответствие с их исходным Buy. ,

Это работает в это дБ скрипка :

WITH cte AS (
    SELECT * FROM (SELECT
           id, date, code, mode, quantity, price, price_currency, usd_to_ars, return_id,
           @acm := @acm + quantity as stock,
           @avr := (@avr * (@acm - quantity) +
                   if(quantity > 0, quantity *
                       if(mode = "Return", @avr,
                            if(price_currency = 'USD', price, price / usd_to_ars)
                         ),
                       quantity * @avr)
                     ) / @acm as average_price_usd
    FROM
        transactions t1,
        (SELECT @acm := 0) x,
        (SELECT @avr := 0) y) t2
)
SELECT
    cte.id, 
    cte.date,
    cte.code,
    cte.mode,
    cte.quantity,
    cte.price,
    cte.price_currency,
    cte.usd_to_ars,
    cte.return_id,
    cte.stock,
    COALESCE(cte2.average_price_usd, cte.average_price_usd) average_price_usd
FROM cte LEFT JOIN cte cte2 ON cte.mode = 'Return' AND cte2.id = cte.return_id
ORDER BY cte.id DESC

Кроме того, похоже, что вы, вероятно, также могли бы достичь своей цели, используя оконные функции MySQL 8.0 . Логика для вычисления average_price_usd мне неясна, однако вот запрос, который вычисляет запас, используя эту технику. Возможно, вы могли бы изменить его, добавив логику для средней цены (она должна перейти к части CTE вышеуказанного запроса):

SELECT 
    id,
    code,
    date,
    mode,
    quantity,
    price,
    price_currency,
    usd_to_ars,
    return_id,
    SUM(quantity) OVER (ORDER BY date) stock,
    NULL average_price_usd
FROM transactions
ORDER BY date desc

РЕДАКТИРОВАТЬ : вот (уродливый) обходной путь для MySQL <8.0 с использованием двух подзапросов (см. <a href="https://www.db-fiddle.com/f/cf6KPoAFjaNGibrnBVfqyR/2" rel="nofollow noreferrer"> this db fiddle ):

SELECT
    cte.id, 
    cte.date,
    cte.code,
    cte.mode,
    cte.quantity,
    cte.price,
    cte.price_currency,
    cte.usd_to_ars,
    cte.return_id,
    cte.stock,
    COALESCE(cte2.average_price_usd, cte.average_price_usd) average_price_usd
FROM 
    (
        SELECT
            id, date, code, mode, quantity, price, price_currency, usd_to_ars, return_id,
            @acm := @acm + quantity as stock,
            @avr := (@avr * (@acm - quantity) +
                   if(quantity > 0, quantity *
                       if(mode = "Return", @avr,
                            if(price_currency = 'USD', price, price / usd_to_ars)
                         ),
                       quantity * @avr)
                     ) / @acm as average_price_usd
        FROM
            transactions t1,
            (SELECT @acm := 0) x,
            (SELECT @avr := 0) y
    ) cte
    LEFT JOIN (
        SELECT
            id, date, code, mode, quantity, price, price_currency, usd_to_ars, return_id,
            @acm2 := @acm2 + quantity as stock,
            @avr2 := (@avr2 * (@acm2 - quantity) +
                   if(quantity > 0, quantity *
                       if(mode = "Return", @avr2,
                            if(price_currency = 'USD', price, price / usd_to_ars)
                         ),
                       quantity * @avr2)
                     ) / @acm2 as average_price_usd
        FROM
            transactions t1,
            (SELECT @acm2 := 0) x,
            (SELECT @avr2 := 0) y
    ) cte2 ON cte.mode = 'Return' AND cte2.id = cte.return_id
ORDER BY cte.id DESC
...