MySQL выполняет подзапрос, если счетчик равен нулю
У меня есть три таблицы: monthly_revenue
, currencies
и foreign_exchange
.
Таблица ежемесячных_доступа
|------------------------------------------------------|
| id | product_id | currency_id | value | month | year |
|------------------------------------------------------|
| 1 | 1 | 1 | 100 | 1 | 2015 |
| 2 | 1 | 2 | 125 | 1 | 2015 |
| 3 | 1 | 3 | 115 | 1 | 2015 |
| 4 | 1 | 1 | 100 | 2 | 2015 |
| 5 | 1 | 2 | 125 | 2 | 2015 |
| 6 | 1 | 3 | 115 | 2 | 2015 |
|------------------------------------------------------|
таблица foreign_exchange
|---------------------------------------|
| id | base | target | rate | rate_date |
|---------------------------------------|
| 1 | GBP | USD | 1.6 |2015-01-01 |
| 2 | GBP | USD | 1.62 |2015-01-15 |
| 3 | GBP | USD | 1.61 |2015-01-31 |
| 4 | EUR | USD | 1.2 |2015-01-01 |
| 5 | EUR | USD | 1.4 |2015-01-15 |
| 6 | EUR | USD | 1.4 |2015-01-31 |
| 7 | GBP | EUR | 1.4 |2015-01-01 |
| 8 | GBP | EUR | 1.45 |2015-01-15 |
| 9 | GBP | EUR | 1.44 |2015-01-31 |
|---------------------------------------|
Из этого можно видеть средние значения fx:
- GBP > USD в январе - 1.61
- EUR > USD в январе - 1.33
- GBP > EUR в январе - 1.43
Для доллара США в качестве базовой валюты нет курсов, и в феврале нет ставок.
таблица валют
|-----------|
| id | name |
|-----------|
| 1 | GBP |
| 2 | USD |
| 3 | EUR |
|-----------|
Что я пытаюсь достичь
Каждая строка в таблице monthly_revenue
может иметь другую currency_id
, так как размещены ордера - разные валюты. Я хочу видеть весь доход за данный месяц в единой валюте. Таким образом, вместо того, чтобы смотреть на весь доход в январе в фунтах стерлингов, а затем отдельно глядя на весь доход в январе в долларах США, я хотел бы получить одну стоимость за весь доход в январе - конвертированный в доллары США (например).
Это может быть рассчитано для каждой строки, используя следующее (используя январь для этого примера):
значение дохода x средняя ставка fx за январь между базовой и целевой валютой
Если у меня есть 50 заказов в январе, в 4 разных валютах, это позволит мне видеть весь доход в любой валюте.
Пример - получить весь доход в январе, в долларах США
Это должно возвратиться:
|------------------------------------------------------|
| id | product_id | currency_id | value | month | year |
|------------------------------------------------------|
| 1 | 1 | 1 | 100 | 1 | 2015 |
| 2 | 1 | 2 | 125 | 1 | 2015 |
| 3 | 1 | 3 | 115 | 1 | 2015 |
|------------------------------------------------------|
Однако строки 1 и 3 не указаны в долларах США (это GBP и EUR соответственно).
То, что я хотел бы увидеть, - это каждая строка, возвращенная со средней скоростью обмена валют, которая преобразуется в столбец converted
. Например:
|-------------------------------------------------------------------------|
| id | prod_id | currency_id | value | month | year | fx_avg | converted |
|-------------------------------------------------------------------------|
| 1 | 1 | 1 | 100 | 1 | 2015 | 1.61 | 161 |
| 2 | 1 | 2 | 125 | 1 | 2015 | 1 | 125 |
| 3 | 1 | 3 | 115 | 1 | 2015 | 1.33 | 152.95 |
|-------------------------------------------------------------------------|
Где я нахожусь
В настоящее время я могу получить базовый расчет, используя запрос ниже, но не хватает пары ключевых функций:
-
Если нет доступных курсов валют (например, для будущих дат, где, конечно, курс FX недоступен), то вся строка игнорируется. В этом случае я хотел бы использовать последний средний месяц.
-
Если вычисление выполняется там, где целевая валюта совпадает с базовой валютой, вся строка игнорируется (поскольку в таблице FX нет записи, где база равна цели). В этом случае скорость должна быть жесткой, как 1.
Запрос до сих пор
SELECT
r.value * IFNULL(AVG(fx.rate),1) as converted, AVG(fx.rate) as averageFx,
r.*, fx.*
FROM
foreign_exchange fx, monthly_revenue r, order_headers h
WHERE
fx.base IN (SELECT name FROM currencies WHERE id = r.currency_id) AND
r.order_header_id = h.id AND
fx.target = 'USD' AND
MONTH(fx.rate_date) = r.month AND
YEAR(fx.rate_date) = r.year AND
r.year = 2015
GROUP BY r.id
ORDER BY month ASC
Если для FX нет записей, похоже, что для получения среднего значения последних месячных ставок необходимо выполнить отдельный подзапрос.
Любой ввод будет оценен. Если какая-либо дополнительная информация требуется, напишите комментарий.
Спасибо.
Изменить Вот SQFiddle, в котором есть примеры схем и кода, который подчеркивает проблему.
Ответы
Ответ 1
Вот приблизительная функция, которая вычисляет ваш обмен для данной валюты и начала месяца:
DELIMITER //
CREATE FUNCTION MonthRate(IN _curr CHAR(3) CHARACTER SET ascii,
IN _date DATE)
RETURNS FLOAT
DETERMINISTIC
BEGIN
-- Note: _date must be the first of some month, such as '2015-02-01'
DECLARE _avg FLOAT;
DECLARE _prev FLOAT;
-- First, try to get the average for the month:
SELECT AVG(rate) INTO _avg FROM foreign_exchange
WHERE base = _curr
AND target = 'USD'
AND rate_date >= _date
AND rate_date < _date + INTERVAL 1 MONTH;
IF _avg IS NOT NULL THEN
RETURN _avg;
END;
-- Fall back onto the last rate before the month:
SELECT rate INTO _prev
FROM foreign_exchange
WHERE base = _curr
AND target = 'USD'
AND rate_date < _date
ORDER BY _date
LIMIT 1;
IF _prev IS NOT NULL THEN
RETURN _prev;
END;
SELECT "Could not get value -- ran off start of Rates table";
END;
DELIMITER ;
Возможно, есть синтаксические ошибки и т.д. Но, надеюсь, вы сможете с ним работать.
Вызывать функцию можно из остальной части кода.
Для производительности это было бы полезно:
INDEX(base, target, rate_date, rate)
Ответ 2
Создать представление:
create view avg_rate as
select base, target, year(fx.rate_date) year, month(fx.rate_date) month,
avg(rate) avg_rate
from foreign_exchange group by base, target
Присоедините его дважды, один раз для текущего месяца и один раз для предыдущего
select r.id, r.month,
r.value * avg(coalesce(cr.avg_rate, pr.avg_rate, 1)) converted,
avg(coalesce(cr.avg_rate, pr.avg_rate), 0) rate
from monthly_revenue r, avg_rate cr, avg_rate pr, order_headers h
where
r.year = 2015 and
cr.year = r.year and cr.month = r.month and cr.target='USD' and
pr.year = r.year and pr.month = r.month - 1 and pr.target='USD' and
r.order_header_id = h.id
group by r.id
order by r.month
Также мне лично не нравится этот способ написания запроса и предпочитаю использовать явные объединения при логическом условии группировки и не иметь беспорядка в where where. то есть:.
...
from monthly_revenue r
inner join order_headers h on r.order_header_id = h.id
left join avg_rate cr on cr.year = r.year and cr.month = r.month and cr.target='USD'
left join avg_rate pr on pr.year = r.year and pr.month = r.month - 1 and pr.target='USD'
where r.year = 2015
Ответ 3
http://sqlfiddle.com/#!9/6a41a/1
Эта сценария основана на исходном, но я добавил некоторые значения скорости за февраль и март, чтобы проверить и показать, как это работает.
SELECT t.*,
IF(@first=t.id, @flag := @flag+1,@flag:=1) `flag`,
@first:=t.id
FROM
(SELECT
coalesce(fx.rate,1) `rate`, (r.value * coalesce(fx.rate,1)) as converted,
r.*, fx.base,fx.target, fx.avg_date, fx.rate frate
FROM
monthly_revenue r
LEFT JOIN
currencies
ON r.currency_id = currencies.id
LEFT JOIN
(SELECT AVG(rate) `rate`,
`base`,
`target`,
STR_TO_DATE(CONCAT('1/',MONTH(rate_date),'/',YEAR(rate_date)), '%d/%m/%Y') avg_date
FROM foreign_exchange
GROUP BY `base`, `target`, `avg_date`
) fx
ON currencies.name = fx.base
AND fx.target = 'USD'
AND fx.avg_date <= STR_TO_DATE(CONCAT('1/',r.month,'/',r.year), '%d/%m/%Y')
ORDER BY r.id, fx.avg_date DESC) t
HAVING `flag` = 1
и если вам нужны записи только для определенного месяца, вы можете добавить WHERE
до ORDER
следующим образом:
WHERE r.month = 1 and r.year = 2015
ORDER BY r.id, fx.avg_date DESC) t
Ответ 4
Вы можете проверить этот запрос на приведенной ссылке: http://sqlfiddle.com/#!9/33def/2
select id,product_id,currency_id,currency_name,
value,month,year,@prev_fx_avg:=ifnull(fx_avg,@prev_fx_avg) fx_avg,
value*@prev_fx_avg as converted
from (SELECT
r.id,r.product_id,r.currency_id,c.name as currency_name,
r.value,r.month,r.year,if(c.name="USD",1,temp.avg_rate) as fx_avg
FROM
monthly_revenue r
left join currencies c on r.currency_id=c.id
left join
(select base , avg(rate) as avg_rate, MONTH(fx.rate_date) month,
YEAR(fx.rate_date) year
from foreign_exchange fx
where target="USD"
group by base,target,MONTH(fx.rate_date),
YEAR(fx.rate_date)) temp on(r.month=temp.month and r.year=temp.year and c.name=temp.base)
group by r.id
order by r.currency_id,r.month ASC, r.year ASC) final,(select @prev_fx_avg:=-1) temp2;