DISTINCT ON
is one of the simplest ways:
SELECT DISTINCT ON (buy_curr_code, sell_curr_code) *
FROM fx_rates
ORDER BY buy_curr_code, sell_curr_code, inserted_at DESC;
Have an index on (buy_curr_code, sell_curr_code, inserted_at DESC)
to make this fast. See:
If there are many rows per (buy_curr_code, sell_curr_code)
- which seems likely - other query techniques will be faster. Specifics depend on undisclosed details. See:
Better design
If changing the DB design is an option I would consider an additional table with a single entry per conversion. Like:
CREATE TABLE current_rate (
exchange_id int GENERATED ALWAYS AS IDENTITY PRIMARY KEY
, buy_curr_code int NOT NULL -- or whatever type
, sell_curr_code int NOT NULL
, rate numeric NOT NULL
, inserted_at timestamptz NOT NULL DEFAULT now()
, UNIQUE (buy_curr_code, sell_curr_code)
);
And triggers ON INSERT
and ON UPDATE
insert a new "log" entry in table fx_rates
. All new entries are updates to current_rate
. Only the trigger writes to table fx_rates
. (The trigger might run additional checks.)
INSERT
trigger:
-- function
CREATE OR REPLACE FUNCTION trg_current_rate_insbef()
RETURNS trigger
LANGUAGE plpgsql AS
$func$
BEGIN
INSERT INTO fx_rate (exchange_id, rate, inserted_at)
VALUES (NEW.exchange_id, NEW.rate, NEW.inserted_at);
RETURN NEW;
END
$func$;
-- trigger
CREATE TRIGGER current_rate_insbef
BEFORE INSERT ON current_rate
FOR EACH ROW EXECUTE FUNCTION trg_current_rate_insbef();
Complete demo in this fiddle.
Then the content of current_rate
is always the ready result you tried to generate.
Why?
Each approach has pros and cons. The only declared requirement is the list of current rates. My solution provides that with SELECT * FROM current_rate
- as simple and fast as possible. Adding a new rate is a single UPDATE
. Storage: fx_rates
is orders of magnitude bigger than current_rate
. We don't need any index at all on that big table. It's effectively INSERT
-only, so no table and index bloat. We can make the big table even smaller by adding an integer IDENTITY
column as surrogate PK to current_rate
, and only write this one 4-byte ID to fx_rates
. In fact, nothing in your question even says that we still need fx_rates
once we have current_rates
. (But I'd expect there will be additional purposes.)
The only moderate sophistication is the trigger, which is really simple, too. So unless you have other requirements, the suggested design is simpler, faster, smaller, more reliable.