postgresql
#postgresql
Вопрос:
Вот в чем вопрос: можно ли просмотреть выходные данные, которые были выбраны, из предыдущей строки выполняемого SQL-запроса в Postgres?
Я знаю, что lag
это существует для просмотра входных данных, «из» запроса. Я также знаю, что CTE, подзапрос или боковое соединение могут решить большинство проблем такого рода. Но я думаю, что проблема, с которой я сталкиваюсь, действительно требует взглянуть на выходные данные предыдущей строки. Почему? Поскольку выходные данные текущей строки зависят от константы из справочной таблицы, и используемое значение также просматривается, эта константа представляет собой совокупность всех предыдущих строк. И если этот поиск возвращает неправильную константу, все последующие строки будут все больше отклоняться от ожидаемого значения.
Весь остальной текст представляет собой упрощенный пример, основанный на проблеме, с которой я столкнулся. Должна быть возможность ввести его в PostgreSQL 12 и выше и поиграть. Мне ужасно жаль, что это так сложно, как есть, но я думаю, что это самое простое, что я могу сделать, сохраняя при этом основную проблему: поиск в таблице поиска на основе совокупности из всех предыдущих строк, а также тот факт, что отслеживаемый «инвентарь» моделируетсякак серию транзакций двух дискретных типов.
Сама база данных существует для отслеживания нескольких рыбных ферм или клеток, полных рыбы. Рыба может быть перемещена / передана между этими фермами, и фермы кормятся примерно ежедневно. Почему бы просто не перенести агрегат в виде поля в таблицу? Потому что должна быть возможность отключить таблицу поиска после окончания сезона, чтобы настроить ее так, чтобы она лучше соответствовала реальности.
-- A listing of all groups of fish ever grown.
create table farms (
id bigserial primary key,
start timestamp not null,
stop timestamp
);
insert into farms
(id, start)
values (
1, '2021-02-01T13:37'
);
-- A transfer of fish from one odling to another.
-- If the source is null the fish is transferred from another fishery outside our system.
-- If the destination is null the fish is being slaughtered, removed from the system.
create table transfers (
source bigint references farms(id),
destination bigint references farms(id),
timestamp timestamp not null default current_timestamp,
total_weight_g bigint not null constraint positive_nonzero_total_weight_g check (total_weight_g > 0),
average_weight_g bigint not null constraint positive_nonzero_average_weight_g check (average_weight_g > 0),
number_fish bigint generated always as (total_weight_g / average_weight_g) stored
);
insert into transfers
(source, destination, timestamp, total_weight_g, average_weight_g)
values
(null, 1, '2021-02-01T16:38', 5, 5),
(null, 1, '2021-02-15T16:38', 500, 500);
-- Transactions of fish feed into a farm.
create table feedings (
id bigserial primary key,
growth_table bigint not null,
farm bigint not null references farms(id),
amount_g bigint not null constraint positive_nonzero_amunt_g check (amount_g > 0),
timestamp timestamp not null
);
insert into feedings
(farm, growth_table, amount_g, timestamp)
values
(1, 1, 1, '2021-02-02T13:37'),
(1, 1, 1, '2021-02-03T13:37'),
(1, 1, 1, '2021-02-04T13:37'),
(1, 1, 1, '2021-02-05T13:37'),
(1, 1, 1, '2021-02-06T13:37'),
(1, 1, 1, '2021-02-07T13:37');
create view combined_feed_and_transfer_history as
with transfer_history as (
select timestamp, destination as farm, total_weight_g, average_weight_g, number_fish
from transfers as deposits
where deposits.destination = 1 -- TODO: This view only works for one farm, fix that.
union all
select timestamp, source as farm, -total_weight_g, -average_weight_g, -number_fish
from transfers as withdrawals
where withdrawals.source = 1
)
select timestamp, farm, total_weight_g, number_fish, average_weight_g, null as growth_table
from transfer_history
union all
select timestamp, farm, amount_g, 0 as number_fish, 0 as average_weight_g, growth_table
from feedings
order by timestamp;
-- Conversion tables from feed to gained weight.
create table growth_coefficients (
growth_table bigserial not null,
average_weight_g bigint not null constraint positive_nonzero_weight check (average_weight_g > 0),
feed_conversion_rate double precision not null constraint positive_foderkonverteringsfaktor check (feed_conversion_rate >= 0),
primary key(growth_table, average_weight_g)
);
insert into growth_coefficients
(average_weight_g, feed_conversion_rate, growth_table)
values
(5.00,0.10,1),
(10.00,10.00,1),
(20.00,1.30,1),
(50.00,1.31,1),
(100.00,1.32,1),
(300.00,1.36,1),
(600.00,1.42,1),
(1000.00,1.50,1),
(1500.00,1.60,1),
(2000.00,1.70,1),
(2500.00,1.80,1),
(3000.00,1.90,1),
(4000.00,2.10,1),
(5000.00,2.30,1);
-- My current solution is a bad one. It does a CTE that sums over all events but does not account
-- for the feed conversion rate. That means that the average weight used too look up the feed
-- conversion rate will diverge more and more from reality the further into the season time goes.
-- This is why it is important to look at the output, the average weight, of the previous row.
-- We start by summing up all the transfer and feed events to get a rough average_weight_g.
with estimate as (
select
timestamp,
farm,
total_weight_g as transaction_size_g,
growth_table,
sum(total_weight_g) over (order by timestamp) as sum_weight_g,
sum(number_fish) over (order by timestamp) as sum_number_fish,
sum(total_weight_g) over (order by timestamp) / sum(number_fish) over (order by timestamp) as average_weight_g
from
combined_feed_and_transfer_history
)
select
timestamp,
sum_number_fish,
transaction_size_g as trans_g,
sum_weight_g,
closest_lookup_table_weight.average_weight_g as lookup_g,
converted_weight_g as conv_g,
sum(converted_weight_g) over (order by timestamp) as sum_conv_g,
sum(converted_weight_g) over (order by timestamp) / sum_number_fish as sum_average_g
from
estimate
join lateral ( -- We then use this estimated_average_weight to look up the closest constant in the growth coefficient table.
(select gc.average_weight_g - estimate.average_weight_g as diff, gc.average_weight_g from growth_coefficients gc where gc.average_weight_g >= estimate.average_weight_g order by gc.average_weight_g asc limit 1)
union all
(select estimate.average_weight_g - gc.average_weight_g as diff, gc.average_weight_g from growth_coefficients gc where gc.average_weight_g <= estimate.average_weight_g order by gc.average_weight_g desc limit 1)
order by diff
limit 1
) as closest_lookup_table_weight
on true
join lateral ( -- If the historical event is a feeding we need to lookup the feed conversion rate.
select case when growth_table is null then 1
else (select feed_conversion_rate
from growth_coefficients gc
where gc.growth_table = growth_table
and gc.average_weight_g = closest_lookup_table_weight.average_weight_g)
end
) as growth_coefficient
on true
join lateral (
select feed_conversion_rate * transaction_size_g as converted_weight_g
) as converted_weight_g
on true;
В самом низу находится мое текущее «решение». С приведенными выше примерами данных sum_conv_g
должно быть 5.6, но из-за того, что агрегат используется в качестве подстановки, не учитывающей коэффициент конверсии sum_conv_g
, вместо этого получается 45.2.
Одна из моих идей заключалась в том, что, возможно, что-то вроде локальных переменных запроса, которые можно было бы использовать для хранения sum_average_g
между строками? Всегда есть выходная дверь, чтобы просто запросить транзакции на моем общем языке программирования Clojure и решить ее там, но было бы здорово, если бы это можно было решить полностью в базе данных.
Комментарии:
1. Типичным решением является вычисление значений в два этапа, используя подзапрос в качестве первого шага. На первом шаге вы вычисляете значение с помощью объединений, агрегатов, боковых запросов и т. Д. И Называете его с помощью
AS
; затем во внешнем запросе вы можете просмотреть его с помощьюLAG()
.2. Вы, кажется, довольно хорошо разбираетесь в CTE, боковых запросах и оконных функциях. Не должно быть слишком сложно разделить запрос на двухэтапное решение. Если вы не можете, я могу попробовать помочь. Что не так в текущем результате? См. fiddle в db-fiddle.com/f/6qHEy5AB4hTfCzXLMH7uZc/0
3. Спасибо, что нашли время, чтобы помочь мне! Проблема в том, что разделение запроса на двухэтапное решение — это то, что я думаю, что я сделал, но я, вероятно, упускаю некоторые детали, которые имеют значение. Данные примера были выбраны неудачно, вместо этого взгляните на это: db-fiddle.com/f/6qHEy5AB4hTfCzXLMH7uZc/2 В этом примере значение
sum_conv_g
должно быть равно 5,6, но из-за того, что агрегат используется в качестве подстановки, не учитывающей коэффициент конверсииsum_conv_g
, вместо этого значение получается 45,2.
Ответ №1:
Вы должны сформулировать рекурсивный подзапрос. Я опубликовал упрощенную версию этого вопроса в администраторе базы данных SE и получил там ответ. Ответ на этот вопрос можно найти здесь и может быть расширен до этого более сложного вопроса, хотя я бы поспорил, что ни у кого никогда не возникнет интереса к этому.