我想让用户可以在价格范围内搜索产品。用户应该能够使用任何货币(美元、欧元、英镑、日元、...),无论产品设置什么货币。因此,产品价格为 200USD,如果用户搜索价格为 100EUR - 200EUR 的产品,他仍然可能找到它。如何让它快速有效?
这是我到目前为止所做的。我保存price
,currency code
而calculated_price
这是在欧元(EUR)的价格是默认货币。
CREATE TABLE "products" (
"id" serial,
"price" numeric NOT NULL,
"currency" char(3),
"calculated_price" numeric NOT NULL,
CONSTRAINT "products_id_pkey" PRIMARY KEY ("id")
);
CREATE TABLE "currencies" (
"id" char(3) NOT NULL,
"modified" timestamp NOT NULL,
"is_default" boolean NOT NULL DEFAULT 'f',
"value" numeric NOT NULL, -- ratio additional to the default currency
CONSTRAINT "currencies_id_pkey" PRIMARY KEY ("id")
);
INSERT INTO "currencies" (id, modified, is_default, value)
VALUES
('EUR', '2012-05-17 11:38:45', 't', 1.0),
('USD', '2012-05-17 11:38:45', 'f', '1.2724'),
('GBP', '2012-05-17 11:38:45', 'f', '0.8005');
INSERT INTO "products" (price, currency, calculated_price)
SELECT 200.0 AS price, 'USD' AS currency, (200.0 / value) AS calculated_price
FROM "currencies" WHERE id = 'USD';
Run Code Online (Sandbox Code Playgroud)
如果用户使用其他货币搜索,比如美元,我们以欧元计算价格并搜索calculated_price
列。
SELECT * FROM "products" WHERE calculated_price > 100.0 AND calculated_price < 200.0;
Run Code Online (Sandbox Code Playgroud)
这样我们可以非常快速地比较价格,因为我们不需要计算每一行的实际价格,因为它计算了一次。
不好的是,至少每天我们都必须重新计算default_price
所有行,因为货币汇率已经改变。
有没有更好的方法来处理这个问题?
就没有其他聪明的解决办法吗?也许一些数学公式?我有一个想法,即calculated_price
是与某个变量的比率X
,当货币发生变化时,我们只更新那个变量X
,而不是calculated_price
,所以我们甚至不需要更新任何东西(行)......也许一些数学家可以解决它像这样?
这听起来像是物化视图的工作。虽然 PostgreSQL 没有明确支持它们,但您可以使用普通表上的函数和触发器来创建和维护物化视图。
我会:
products_summary
使用当前表的架构创建一个新表,例如products
;ALTER TABLE products DROP COLUMN calculated_price
删除calculated_price
其中的列products
products_summary
编写一个视图,通过SELECT
ing fromproducts
和JOIN
ing on生成您想要的输出currencies
。我会这样称呼它products_summary_dynamic
,但命名由你决定。如果需要,您可以使用函数而不是视图。products_summary
定期products_summary_dynamic
刷新物化视图表BEGIN; TRUNCATE products_summary; INSERT INTO products_summary SELECT * FROM products_summary_dynamic; COMMIT;
。AFTER INSERT OR UPDATE OR DELETE ON products
触发器,该触发器运行触发器过程来维护products_summary
表,从 中删除行时删除行products
,在添加时添加行products
(通过SELECT
从products_summary_dynamic
视图中添加),并在产品详细信息更改时更新它们。此方法将在更新摘要表的事务products_summary
期间获取排他锁。TRUNCATE ..; INSERT ...;
如果这导致您的应用程序因为花费太长时间而停顿,您可以保留该products_summary
表的两个版本。更新未使用的,然后在事务中更新ALTER TABLE products_summary RENAME TO products_summary_old; ALTER TABLE products_summary_new RENAME TO products_summary;
另一种但非常狡猾的方法是使用表达式索引。因为用这种方法更新货币表可能不可避免地需要在 a 期间锁定DROP INDEX
,而且CREATE INDEX
我不会经常这样做 - 但它可能适合某些情况。
这个想法是将货币转换包装在一个IMMUTABLE
函数中。因为您IMMUTABLE
向数据库引擎保证任何给定参数的返回值始终相同,并且如果返回值不同,它可以自由地执行各种疯狂的操作。调用该函数,例如,to_euros(amount numeric, currency char(3)) returns numeric
. 随心所欲地实现它;按货币分类的大CASE
报表、查找表等等。如果您使用查找表,则绝不能更改查找表,除非如下所述。
在 上创建表达式索引products
,例如:
CREATE INDEX products_calculated_price_idx
ON products( to_euros(price,currency) );
Run Code Online (Sandbox Code Playgroud)
您现在可以通过计算的价格快速搜索产品,例如:
SELECT *
FROM products
WHERE to_euros(price,currency) BETWEEN $1 and $2;
Run Code Online (Sandbox Code Playgroud)
现在的问题是如何更新货币表。这里的技巧是,您可以更改货币表,只需删除并重新创建索引即可。
BEGIN;
-- An exclusive lock will be held from here until commit:
DROP INDEX products_calculated_price_idx;
DROP FUNCTION to_euros(amount numeric, currency char(3)) CASCADE;
-- It's probably better to use a big CASE statement here
-- rather than selecting from the `currencies` table as shown.
-- You could dynamically regenerate the function with PL/PgSQL
-- `EXECUTE` if you really wanted.
--
CREATE FUNCTION to_euros(amount numeric, currency char(3))
RETURNS numeric LANGUAGE sql AS $$
SELECT $1 / value FROM currencies WHERE id = $2;
$$ IMMUTABLE;
-- This may take some time and will run with the exclusive lock
-- held.
CREATE INDEX products_calculated_price_idx
ON products( to_euros(price,currency) );
COMMIT;
Run Code Online (Sandbox Code Playgroud)
我删除并重新定义上面的函数只是为了强调,如果您重新定义不可变函数,则必须删除使用该函数的所有内容。使用CASCADE
水滴是最好的方法。
我强烈怀疑物化视图是更好的方法。这当然更安全。我加入这个主要是为了好玩。
归档时间: |
|
查看次数: |
3473 次 |
最近记录: |