Env*_*vek 6 postgresql database-design types currency
我有一个应用程序可以处理不同货币的产品和销售。因此,数据库中同一表中的每一行都可以存储不同货币的价格。如何正确地做到这一点?
最直接的方法是定义一个数字price_amount列和varchar price_currencyoolumn,但我觉得本质上单值(price)的两个技术上独立的列是错误的。就像物理测量是没有单位的毫无意义的数字一样,货币的数量如果没有其单位——货币,也是毫无意义的。
在我看来,money 应该是一个包含数量和货币的单一值。
我开始搜索并惊讶地发现搜索结果中没有现成的解决方案或好的文章。有pg-currency扩展可以做我想要的,但它在大约 10 年前被放弃了。
我创建了以下复合数据类型作为起点:
CREATE TYPE true_money AS (
currency varchar,
amount numeric
);
Run Code Online (Sandbox Code Playgroud)
然后开始为它编写支持性的东西:验证、算术、聚合……并意识到这个兔子洞真的很深。
可以在此处找到我对这种复合类型的所有当前(部分)结果以供参考:https : //gist.github.com/Envek/780b917e72a86c123776ee763b8dd986?fbclid=IwAR2GxGUVPg5FtN3SSPhQv2uFA7oPNNixFRZJBZij5oPNNixFRJbZije-
现在我可以做以下事情:
INSERT INTO "products" ("title", "price") VALUES ('?????????', ('RUB',100500));
INSERT INTO "products" ("title", "price") VALUES ('???? ? ????????', ('RUB',12100.42));
INSERT INTO "products" ("title", "price") VALUES ('Gravizapa', ('USD',19999.99));
-- You can access its parts if you need to extract them or do filtering or grouping
SELECT SUM(price) FROM test_monetaries WHERE (price).currency = 'RUB';
-- (RUB,112600.42)
-- And if you forget filtering/grouping then custom types can save you from nonsense results
SELECT SUM(price) FROM test_monetaries;
ERROR: (USD,19999.99) can not be added to (RUB,112600.42) - currencies do not match
Run Code Online (Sandbox Code Playgroud)
这是正确的方法吗?怎么做才对?
一点背景:在我们的应用程序中,用户(卖家)可以用他们想要的任何货币(例如,美元、欧元、日元、卢布等)管理他们的库存(产品)。该应用程序将转换货币并在本地站点(如英国或澳大利亚)上发布产品。买家还将以当地货币(英镑、澳元等)购买这些商品,这些商品最终将转换为卖家货币并支付给他们(费用除外)。所以在应用程序的很多地方,几乎任何支持的货币都可以出现。最后,卖家应该能够更改他们的货币,所有产品都应该批量转换为新货币(由于某些原因,交易中的单一更新不能使用)。所以我们不能说“只在products表格中保留数值并与sellers表格连接以获得货币”(我相信这本身就是反模式)。
Yes, creating your own type is quite a lot of work if you want to integrate it seamlessly with PostgreSQL.
If an item can be sold in different countries and has a different price everywhere, you should model the data accordingly. Having an exchange rate is not good enough, because the same item might be more expensive in Japan than in China.
If you are only interested in the current price, that could look like this:
CREATE TABLE currency (
currency_id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
denomination text CHECK (length(denomination) = 3) NOT NULL
);
CREATE TABLE exchange (
from_curr_id bigint REFERENCES currency NOT NULL,
to_curr_id bigint REFERENCES currency NOT NULL,
rate numeric(10,5) NOT NULL
);
CREATE TABLE country (
country_id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name text UNIQUE NOT NULL,
currency_id bigint REFERENCES currency NOT NULL
);
CREATE TABLE product (
product_id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
title text NOT NULL,
);
CRATE TABLE price (
country_id bigint REFERENCES country NOT NULL,
product_id bigint REFERENCES product NOT NULL,
amount numeric(10,2) NOT NULL,
PRIMARY KEY (product_id, country_id)
);
CREATE INDEX ON price (country_id); -- for the foreign key
Run Code Online (Sandbox Code Playgroud)
This way, each product can have a certain price in each country, and the price is associated with a currency via the country.
Of course, the real world might be still more complicated:
The main thing is that you can always follow a chain of foreign keys that leads you to the desired amount and currency unambiguously.
For converting between currencies