विभिन्न मुद्राओं में कीमतों की तुलनात्मक रूप से तुलना करना


10

मैं मूल्य सीमा के भीतर उत्पादों को खोजने के लिए उपयोगकर्ता के लिए संभव बनाना चाहता हूं। उपयोगकर्ता को किसी भी मुद्रा (USD, EUR, GBP, JPY, ...) का उपयोग करने में सक्षम होना चाहिए, इससे कोई फर्क नहीं पड़ता कि उत्पाद किस मुद्रा द्वारा निर्धारित है। तो, उत्पाद की कीमत 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';

यदि उपयोगकर्ता अन्य मुद्रा के साथ खोज कर रहा है, तो हम USD कहते हैं, हम EUR में मूल्य की गणना करते हैं और calculated_priceकॉलम खोजते हैं ।

SELECT * FROM "products" WHERE calculated_price > 100.0 AND calculated_price < 200.0;

इस तरह हम कीमतों की तुलना बहुत तेजी से कर सकते हैं, क्योंकि हमें हर पंक्ति के लिए वास्तविक मूल्य की गणना करने की आवश्यकता नहीं है, क्योंकि इसकी गणना एक बार की जाती है।

बुरी बात यह है कि कम से कम हर दिन हमें default_priceसभी पंक्तियों के लिए फिर से गणना करनी होगी , क्योंकि मुद्रा दरों को बदल दिया गया है।

क्या इससे निपटने का एक बेहतर तरीका है?

क्या कोई अन्य चतुर समाधान नहीं है? शायद कुछ गणितीय सूत्र? मेरे पास एक विचार है कि calculated_priceकुछ चर के खिलाफ एक अनुपात है Xऔर, जब मुद्रा बदलती है, तो हम केवल उस चर को अपडेट करते हैं X, न कि calculated_price, इसलिए हमें कुछ भी अपडेट करने की आवश्यकता नहीं है (पंक्तियां) ... शायद कुछ गणितज्ञ इसे हल कर सकते हैं इस तरह?

जवाबों:


4

यहां एक अलग दृष्टिकोण है जिसके लिए पुनर्संयोजन calculated_priceकेवल एक अनुकूलन है, कड़ाई से आवश्यक होने के विपरीत।

मान लीजिए कि currenciesतालिकाओं में, आप एक और कॉलम जोड़ते हैं last_rate, जिसमें calculated_priceअंतिम अपडेट किए जाने के समय विनिमय दर होती है, फिर चाहे यह कोई भी हो।

जल्दी के बीच, कहते हैं, 50 अमरीकी डालर और 100 अमरीकी डालर की कीमत मुद्दा यह है कि के साथ उत्पादों का एक सेट प्राप्त करने के लिए शामिल वांछित परिणाम है, तो आप ऐसा ही कुछ कर सकता है:

  SELECT * FROM products
   WHERE calculated_price > 50.0/(:last_rate*
    (SELECT coalesce(max(value/last_rate),1) FROM currencies
      WHERE value>last_rate))
   AND calculated_price < 100.0/ (:last_rate*
    (SELECT coalesce(min(value/last_rate),1) FROM currencies
      WHERE value<last_rate))

जहां :last_rateअंतिम अपडेट के समय EUR / USD विनिमय दर शामिल है। हर मुद्रा की अधिकतम भिन्नता को ध्यान में रखने के लिए अंतराल बढ़ाने के लिए विचार है। अंतराल के दोनों सिरों के लिए वृद्धि कारक दर अपडेट के बीच स्थिर हैं, इसलिए वे पूर्व-गणना की जा सकती हैं।

चूंकि थोड़े समय के लिए ही दरों में थोड़ा बदलाव होता है, इसलिए उपरोक्त क्वेरी अंतिम परिणाम के करीब होने की संभावना है। अंतिम परिणाम प्राप्त करने के लिए, आइए उन उत्पादों को फ़िल्टर करें जिनके लिए अंतिम अद्यतन के बाद से दरों में परिवर्तन के कारण कीमतें सीमा से बाहर खिसक गई हैं calculated_price:

  WITH p AS (
   SELECT * FROM products
   WHERE calculated_price > 50.0/(:last_rate*
    (SELECT coalesce(max(value/last_rate),1) FROM currencies
      WHERE value>last_rate))
   AND calculated_price < 100.0/ (:last_rate*
    (SELECT coalesce(min(value/last_rate),1) FROM currencies
      WHERE value<last_rate))
  )
  SELECT price,c.value FROM p join currencies c on (p.currency=c.id)
     WHERE price/c.value>50/:current_rate
       AND price/c.value<100/:current_rate;

:current_rateउपयोगकर्ता द्वारा चुने गए धन के लिए EUR के साथ अधिक अद्यतित दर कहां है।

दक्षता इस तथ्य से आती है कि दरों की सीमा को छोटा माना जाता है, मूल्यों को एक साथ पास किया जा रहा है।


2

यह एक भौतिकवादी दृश्य के लिए एक नौकरी की तरह लगता है। जबकि PostgreSQL स्पष्ट रूप से उनका समर्थन नहीं करता है, आप सामान्य तालिकाओं पर फ़ंक्शन और ट्रिगर्स का उपयोग करके भौतिक विचारों को बना सकते हैं और बनाए रख सकते हैं।

मैं:

  • products_summaryअपनी वर्तमान productsतालिका के स्कीमा के साथ , एक नई तालिका बनाएं ;
  • ALTER TABLE products DROP COLUMN calculated_pricecalculated_priceकॉलम से छुटकारा पाने के लिएproducts
  • उस दृश्य को लिखें, जो आप जिस आउटपुट से चाहते हैं, products_summaryउसे SELECTआईएनजी productsऔर JOINआईएनजी से उत्पन्न करता है currencies। मुझे लगता है कि products_summary_dynamicनामकरण आप पर निर्भर है। यदि आप चाहते थे तो आप दृश्य के बजाय फ़ंक्शन का उपयोग कर सकते हैं।
  • समय-समय पर materialized दृश्य तालिका ताज़ा 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;


एक वैकल्पिक लेकिन बहुत ही डोडी दृष्टिकोण एक अभिव्यक्ति सूचकांक का उपयोग करना होगा। क्योंकि इस दृष्टिकोण के साथ मुद्रा तालिका को अपडेट करने की संभावना के दौरान अनपेक्षित रूप से लॉक की आवश्यकता होती है 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) );

अब आप गणना की गई कीमत के हिसाब से उत्पादों की खोज कर सकते हैं:

SELECT *
FROM products
WHERE to_euros(price,currency) BETWEEN $1 and $2;

अब समस्या यह हो गई है कि मुद्रा तालिकाओं को कैसे अपडेट किया जाए। यहां चाल यह है कि आप मुद्रा तालिकाओं को बदल सकते हैं, आपको बस इसे करने के लिए सूचकांक को छोड़ना और फिर से बनाना होगा।

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;

मैं ऊपर दिए गए फ़ंक्शन को केवल तनाव के लिए छोड़ता हूं और फिर से परिभाषित करता हूं कि आपको एक अपरिवर्तनीय फ़ंक्शन को फिर से परिभाषित करने पर फ़ंक्शन का उपयोग करने वाली हर चीज को छोड़ना होगा । एक बूंद का उपयोग करना सबसे अच्छा तरीका है।CASCADE

मुझे दृढ़ता से संदेह है कि भौतिक दृष्टिकोण बेहतर दृष्टिकोण है। यह निश्चित रूप से सुरक्षित है। मैं इस एक को ज्यादातर किक के लिए शामिल कर रहा हूं।


अभी मैं इस बारे में सोच रहा हूं - मुझे आखिर क्यों अपडेट करना चाहिए calculated_price? मैं सिर्फ स्टोर कर सकता हूं initial_currency_value(निरंतर मुद्रा दर जो ली जाती है, चलो कहते हैं, आज) और हमेशा उसी के खिलाफ गणना करें! और यूरो में कीमत प्रदर्शित करते समय, वास्तविक मुद्रा दर के खिलाफ गणना करें, निश्चित रूप से। क्या मैं सही हू? या कोई समस्या है जो मुझे दिखाई नहीं दे रही है?
ताई

1

मैं अपने विचार के साथ आया था। मुझे बताओ कि क्या यह वास्तव में काम करने जा रहा है, कृपया!

समस्या।

जब उत्पाद productsतालिका में जोड़ा जा रहा है , तो मूल्य डिफ़ॉल्ट मुद्रा (EUR) में परिवर्तित हो जाता है और calculated_priceकॉलम में संग्रहीत होता है।

हम चाहते हैं कि उपयोगकर्ता किसी भी मुद्रा की कीमतें खोज (फ़िल्टर) कर सके। यह इनपुट मूल्य को डिफ़ॉल्ट मुद्रा (EUR) में परिवर्तित करके और calculated_priceस्तंभ के साथ तुलना करके किया जाता है।

हमें मुद्रा दरों को अपडेट करने की आवश्यकता है, जिससे उपयोगकर्ता ताजा मुद्रा दर से खोज कर सकेंगे। लेकिन समस्या यह है - calculated_priceकुशलता से अपडेट कैसे करें ।

समाधान (उम्मीद है)।

कैसे calculated_priceकुशलता से अद्यतन करें ।

नहीं करें! :)

विचार यह है कि हम कल के मुद्रा दर (ले एक ही तिथि के सभी में) calculated_priceउपयोग केवल उन। जैसे हमेशा के लिए! कोई दैनिक अद्यतन नहीं। कीमतों की तुलना / फ़िल्टर / खोज करने से पहले हमें केवल उसी चीज़ की आवश्यकता है जो आज की मुद्रा दरों को लेना है जैसे वे कल थे।

तो, calculated_priceहम निश्चित तारीख की मुद्रा की दर का उपयोग करेंगे (हमने चुना है, चलो कहते हैं, कल)। हमें आज की कीमत को कल की कीमत में बदलने की आवश्यकता है। दूसरे शब्दों में, आज की दर लें और इसे कल की दर में परिवर्तित करें:

cash_in_euros * ( rate_newest / rate_fixed )

और यह मुद्राओं की तालिका है:

CREATE TABLE "currencies" (
  "id" char(3) NOT NULL, -- currency code (EUR, USD, GBP, ...)
  "is_default" boolean NOT NULL DEFAULT 'f',

  -- Set once. If you update, update all database fields that depends on this.
  "rate_fixed" numeric NOT NULL, -- Currency rate against default currency
  "rate_fixed_updated" timestamp NOT NULL,

  -- Update as frequently as needed.
  "rate_newest" numeric NOT NULL, -- Currency rate against default currency
  "rate_newest_updated" timestamp NOT NULL,

  CONSTRAINT "currencies_id_pkey" PRIMARY KEY ("id")
);

यह है कि 200 USD की लागत वाले उत्पाद को कैसे जोड़ा calculated_priceजाए और इसकी गणना कैसे की जाए: USD से नवीनतम EUR दर और निश्चित (पुरानी) दर में

INSERT INTO "products" (price, currency, calculated_price)
  SELECT
  200.0 AS price,
  'USD' AS currency,

  ((200.0 / rate_newest) * (rate_newest / rate_fixed)) AS calculated_price

    FROM "currencies" WHERE id = 'USD';

यह क्लाइंट पक्ष में पूर्व-परिकलित किया जा सकता है और यही मैं करने जा रहा हूं - calculated_priceक्वेरी करने से पहले संगत मूल्य पर उपयोगकर्ता इनपुट मूल्य की गणना करें , इसलिए अच्छा पुराना उपयोग किया जाएगाSELECT * FROM products WHERE calculated_price > 100.0 AND calculated_price < 200.0;

निष्कर्ष।

यह विचार मेरे पास कुछ घंटे पहले आया था और वर्तमान में मैं आपको यह जांचने के लिए कह रहा हूं कि क्या मैं इस समाधान के बारे में सही हूं। तुम क्या सोचते हो? क्या यह काम करने वाला है? या मैंने गलती की है?

मुझे उम्मीद है कि आप यह सब समझ गए होंगे। मैं एक देशी अंग्रेजी वक्ता नहीं हूँ, यह भी देर हो चुकी है और मैं थक गया हूँ। :)

अपडेट करें

ठीक है, ऐसा लगता है कि यह एक समस्या को हल करता है, लेकिन दूसरे का परिचय देता है। बहुत बुरा। :)


समस्या यह है कि rate_newest / rate_fixedप्रति मुद्रा अलग है, और यह समाधान खोज में उपयोगकर्ता द्वारा चुने गए पैसे के लिए केवल एक पर विचार करता है। एक अलग मुद्रा में किसी भी कीमत की तुलना अप-टू-डेट दरों के साथ नहीं की जाएगी। उत्तर मैंने किसी तरह प्रस्तुत किया है, लेकिन मुझे लगता है कि मैंने इसे अद्यतन संस्करण में तय कर लिया है।
डैनियल वेरिटा

उस दृष्टिकोण के साथ मुझे जो मुख्य समस्या दिख रही है, वह यह है कि यह मूल्य पर डेटाबेस इंडेक्स का लाभ नहीं उठाता है (ORDER BY परिकलित_प्रकार खंड)।
रोजसेनफील्ड
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.