2010-02-21 7 views
10

दो तालिकाओं पर विचार करेंएसक्यूएल तिथि सीमाओं के खिलाफ शामिल हो?</p> <p><strong>लेनदेन</strong>, मात्रा के साथ एक विदेशी मुद्रा में:

 Date Amount 
========= ======= 
1/2/2009 1500 
2/4/2009 2300 
3/15/2009  300 
4/17/2009 2200 
etc. 

ExchangeRates, प्राथमिक मुद्रा के मूल्य के साथ विदेशी मुद्रा में (के डॉलर मान लीजिए):

 Date Rate 
========= ======= 
2/1/2009 40.1 
3/1/2009 41.0 
4/1/2009 38.5 
5/1/2009 42.7 
etc. 

विनिमय दर मनमाने ढंग से तिथियों के लिए दर्ज की जा सकती है - उपयोगकर्ता उन्हें दैनिक आधार पर, सप्ताहांत में प्रवेश कर सकता है वाई आधार, मासिक आधार, या अनियमित अंतराल पर।

आदेश डॉलर तक विदेशी मात्रा में अनुवाद करने के लिए में, मैं इन नियमों का पालन करने की जरूरत है:

ए संभव हो तो, सबसे हाल ही में पिछले दर का उपयोग करें; इसलिए 2/4/2009 पर लेनदेन 2/1/2009 के लिए दर का उपयोग करता है, और 3/15/2009 को लेनदेन 3/1/2009 के लिए दर का उपयोग करता है।

बी। यदि पिछली तारीख के लिए परिभाषित दर नहीं है, तो उपलब्ध सबसे पुरानी दर का उपयोग करें। तो 1/2/2009 को लेनदेन 2/1/2009 के लिए दर का उपयोग करता है, क्योंकि पहले की दर परिभाषित नहीं की गई है।

यह काम करता है ...

Select 
    t.Date, 
    t.Amount, 
    ConvertedAmount=( 
     Select Top 1 
      t.Amount/ex.Rate 
     From ExchangeRates ex 
     Where t.Date > ex.Date 
     Order by ex.Date desc 
    ) 
From Transactions t 

... लेकिन (1) ऐसा लगता है की तरह एक में शामिल होने के और अधिक कुशल हो जाएगा & सुरुचिपूर्ण, और (2) यह ऊपर नियम बी के साथ सौदा नहीं करता है।

क्या उचित दर खोजने के लिए उपकुमारी का उपयोग करने का कोई विकल्प है? और क्या गठबंधन में खुद को बांधने के बिना, नियम बी को संभालने का एक शानदार तरीका है?

+0

क्या वे वास्तव में आपके पास टेबल हैं, या आपने सरल बना दिया है? क्या आप अधिक जानकारी प्रदान कर सकते हैं? क्या आपके पास अन्य पहचानकर्ता, पीके आदि हैं? – van

+0

बेशक वास्तविकता में कई और चल रहे हैं (एकाधिक मुद्राएं, आदि) लेकिन यह अनिवार्य रूप से इसे प्राप्त करना चाहता था। –

+1

बस जानकारी के लिए ... कृपया ध्यान दें कि मेरा समाधान मूल रूप से विभिन्न मुद्राओं के लिए भी काम करता है, लेकिन आपको 'इंडेक्सेड एक्सचेंजरेट' में पंक्ति संख्या को प्रति मुद्रा ('भाग' का उपयोग करके) में बदलना होगा और इसे 'LEFT' में जोड़ें 'RangedExchangeRate' में जॉइन की स्थिति। – Lucero

उत्तर

16

आप पहले विनिमय दर पर स्वयं-जुड़ सकते हैं जो तिथि के अनुसार आदेश दिया जाता है ताकि आपके पास प्रत्येक विनिमय दर की शुरुआत और समाप्ति तिथि हो, बिना किसी ओवरलैप या अंतराल में अंतर (शायद इसे देखें आपके डेटाबेस में - मेरे मामले में मैं सिर्फ एक सामान्य टेबल अभिव्यक्ति का उपयोग कर रहा हूं)।

अब लेनदेन के साथ उन "तैयार" दरों में शामिल होना सरल और कुशल है।

कुछ की तरह:

WITH IndexedExchangeRates AS (   
      SELECT Row_Number() OVER (ORDER BY Date) ix, 
        Date, 
        Rate 
      FROM ExchangeRates 
     ), 
     RangedExchangeRates AS (    
      SELECT CASE WHEN IER.ix=1 THEN CAST('1753-01-01' AS datetime) 
        ELSE IER.Date 
        END DateFrom, 
        COALESCE(IER2.Date, GETDATE()) DateTo, 
        IER.Rate 
      FROM IndexedExchangeRates IER 
      LEFT JOIN IndexedExchangeRates IER2 
      ON IER.ix = IER2.ix-1 
     ) 
SELECT T.Date, 
     T.Amount, 
     RER.Rate, 
     T.Amount/RER.Rate ConvertedAmount 
FROM Transactions T 
LEFT JOIN RangedExchangeRates RER 
ON (T.Date > RER.DateFrom) AND (T.Date <= RER.DateTo) 

नोट्स:

  • आप दूर भविष्य की किसी तिथि के साथ GETDATE() की जगह सकता है, मैं यहाँ यह सोचते कर रहा हूँ भविष्य के लिए कोई दरों ज्ञात हैं कि ।

  • नियम (बी) एसक्यूएल सर्वर datetime, द्वारा समर्थित न्यूनतम तारीख करने के लिए पहली ज्ञात विनिमय दर की तारीख की स्थापना द्वारा कार्यान्वित किया जाता है जो होना चाहिए (परिभाषा अगर यह प्रकार आप Date स्तंभ के लिए उपयोग कर रहे हैं द्वारा) सबसे छोटा मूल्य संभव हो।

+0

यह पूरी तरह से काम करता है, बहुत बहुत धन्यवाद। –

+1

क्या अंतिम कारण में BETWEEN का उपयोग न करने का कोई कारण है? –

+1

हां, क्योंकि BETWEEN झूठे परिणाम लौटाएगा, यह '(टी.डेट> = आरईआर.डेटेट्रॉम) और (टी.डेट <= आरईआर.डेटेटो) के बराबर है, जो आप नहीं चाहते हैं, क्योंकि लेन-देन की तारीख विनिमय दर के बराबर होती है तिथियों के परिणामस्वरूप 2 पंक्तियां जुड़ जाएंगी (जिनमें से एक गलत विनिमय दर का उपयोग करेगी)। – Lucero

1

मैं इसका परीक्षण नहीं कर सकता, लेकिन मुझे लगता है कि यह काम करेगा। यह नियम ए या नियम बी द्वारा दर लेने के लिए दो उप-प्रश्नों के साथ सहवास का उपयोग करता है।

Select t.Date, t.Amount, 
    ConvertedAmount = t.Amount/coalesce( 
    (Select Top 1 ex.Rate 
     From ExchangeRates ex 
     Where t.Date > ex.Date 
     Order by ex.Date desc) 
    , 
    (select top 1 ex.Rate 
     From ExchangeRates 
     Order by ex.Date asc) 
    ) 
From Transactions t 
0
SELECT 
    a.tranDate, 
    a.Amount, 
    a.Amount/a.Rate as convertedRate 
FROM 
    (

    SELECT 
     t.date tranDate, 
     e.date as rateDate, 
     t.Amount, 
     e.rate, 
     RANK() OVER (Partition BY t.date ORDER BY 
         CASE WHEN DATEDIFF(day,e.date,t.date) < 0 THEN 
            DATEDIFF(day,e.date,t.date) * -100000 
           ELSE DATEDIFF(day,e.date,t.date) 
         END) AS diff 
    FROM 
     ExchangeRates e 
    CROSS JOIN 
     Transactions t 
     ) a 
WHERE a.diff = 1 

ट्रॅन और दर तारीख के बीच का अंतर गणना की जाती है, तो नकारात्मक मूल्यों (हालत ख) -10,000 से गुणा किया जाता है ताकि वे अभी भी स्थान पर रहीं जा सकता है लेकिन सकारात्मक मूल्यों (हालत एक हमेशा प्राथमिकता दी जाएगी। हम तो खंड से अधिक रैंक का उपयोग कर प्रत्येक लेनदेन की तारीख के लिए न्यूनतम तारीख अंतर का चयन

+0

ठीक है, अब ए लागू किया गया प्रतीत होता है। हालांकि - मैं इसके साथ सहमत हूं कि यह बहुत ही सुरुचिपूर्ण नहीं है, और इस तरह के गणना के साथ एक क्रॉस जॉइन को ध्यान में रखकर लेनदेन और विनिमय दरों की बढ़ती संख्या के साथ धीमी कार्यक्षमता होगी, क्योंकि पंक्तियों की गणना की गणना उनकी गणना के बराबर होती है गुणा, उदाहरण के लिए प्रति दिन एक विनिमय दर के साथ 10 साल और प्रति दिन 3 लेनदेन आपको 40 मिलियन पंक्तियों की गणना करने के लिए अनुमति देंगे। – Lucero

+0

~ लूरसरो, हाँ आपका बिल्कुल सही। –

2

मान लीजिए आप एक विस्तारित विनिमय दर तालिका कि निहित था:।

Start Date End Date Rate 
========== ========== ======= 
0001-01-01 2009-01-31 40.1 
2009-02-01 2009-02-28 40.1 
2009-03-01 2009-03-31 41.0 
2009-04-01 2009-04-30 38.5 
2009-05-01 9999-12-31 42.7 

हम कर सकते हैं इस बारे में चर्चा करें कि पहले दो पंक्तियों को संयुक्त किया जाना चाहिए, लेकिन सामान्य विचार यह है कि किसी दिए गए दिनांक के लिए विनिमय दर को खोजना मुश्किल है। यह संरचना SQL 'BETWEEN' ऑपरेटर के साथ काम करती है जिसमें श्रेणियों के सिरों को शामिल किया गया है। अक्सर, श्रेणियों के लिए एक बेहतर प्रारूप 'खुला-बंद' होता है; सूचीबद्ध पहली तारीख शामिल है और दूसरा शामिल है। ध्यान दें कि डेटा पंक्तियों पर एक बाधा है - (ए) तिथियों की सीमा के कवरेज में कोई अंतराल नहीं है और (बी) कवरेज में कोई ओवरलैप नहीं है। उन बाधाओं को लागू करना पूरी तरह से तुच्छ नहीं है (विनम्र अल्पसंख्यक - मीओसिस)।

अब बुनियादी क्वेरी तुच्छ है, और मामले बी नहीं रह गया है एक विशेष मामला है:

SELECT T.Date, T.Amount, X.Rate 
    FROM Transactions AS T JOIN ExtendedExchangeRates AS X 
     ON T.Date BETWEEN X.StartDate AND X.EndDate; 

मुश्किल हिस्सा मक्खी पर दिए गए ExchangeRate मेज से ExtendedExchangeRate तालिका पैदा कर रही है। यदि यह एक विकल्प है, तो विस्तारित एक्सचेंजरेट तालिका से मेल खाने के लिए मूल एक्सचेंजरेट तालिका की संरचना को संशोधित करना एक अच्छा विचार होगा; जब आप किसी विनिमय दर को निर्धारित करने की आवश्यकता होती है (दिन में कई बार) की आवश्यकता होती है तो डेटा गड़बड़ी को हल करता है (महीने में एक बार)।

विस्तारित विनिमय दर तालिका कैसे बनाएं? यदि आपका सिस्टम अगले या पिछले दिन प्राप्त करने के लिए दिनांक मूल्य से 1 जोड़ने या घटाने का समर्थन करता है (और इसमें एक एकल पंक्ति तालिका 'डुअल' है), तो इस पर एक भिन्नता काम करेगी (किसी भी ओलाप फ़ंक्शन का उपयोग किए बिना):

CREATE TABLE ExchangeRate 
(
    Date DATE NOT NULL, 
    Rate DECIMAL(10,5) NOT NULL 
); 
INSERT INTO ExchangeRate VALUES('2009-02-01', 40.1); 
INSERT INTO ExchangeRate VALUES('2009-03-01', 41.0); 
INSERT INTO ExchangeRate VALUES('2009-04-01', 38.5); 
INSERT INTO ExchangeRate VALUES('2009-05-01', 42.7); 

प्रथम पंक्ति:

SELECT '0001-01-01' AS StartDate, 
     (SELECT MIN(Date) - 1 FROM ExchangeRate) AS EndDate, 
     (SELECT Rate FROM ExchangeRate 
     WHERE Date = (SELECT MIN(Date) FROM ExchangeRate)) AS Rate 
FROM Dual; 

परिणाम:

0001-01-01 2009-01-31  40.10000 

अंतिम पंक्ति:

+०१२३५१६४१०
SELECT (SELECT MAX(Date) FROM ExchangeRate) AS StartDate, 
     '9999-12-31' AS EndDate, 
     (SELECT Rate FROM ExchangeRate 
     WHERE Date = (SELECT MAX(Date) FROM ExchangeRate)) AS Rate 
FROM Dual; 

परिणाम:

2009-05-01 9999-12-31  42.70000 

मध्य पंक्तियों:

SELECT X1.Date  AS StartDate, 
     X2.Date - 1 AS EndDate, 
     X1.Rate  AS Rate 
    FROM ExchangeRate AS X1 JOIN ExchangeRate AS X2 
     ON X1.Date < X2.Date 
WHERE NOT EXISTS 
     (SELECT * 
      FROM ExchangeRate AS X3 
     WHERE X3.Date > X1.Date AND X3.Date < X2.Date 
     ); 

परिणाम:

2009-02-01 2009-02-28  40.10000 
2009-03-01 2009-03-31  41.00000 
2009-04-01 2009-04-30  38.50000 

ध्यान दें कि मौजूद नहीं उप क्वेरी बल्कि महत्वपूर्ण है। इसके बिना, 'मध्य पंक्तियों' परिणाम है:

2009-02-01 2009-02-28  40.10000 
2009-02-01 2009-03-31  40.10000 # Unwanted 
2009-02-01 2009-04-30  40.10000 # Unwanted 
2009-03-01 2009-03-31  41.00000 
2009-03-01 2009-04-30  41.00000 # Unwanted 
2009-04-01 2009-04-30  38.50000 

अवांछित पंक्तियों की संख्या के आकार में तालिका बढ़ने के साथ नाटकीय रूप से बढ़ (एन> 2 पंक्तियों के लिए, देखते हैं (एन 2) * (एन - 3)/2 अवांछित पंक्तियां, मुझे विश्वास है)।

SELECT DATE '0001-01-01' AS StartDate, 
     (SELECT MIN(Date) - 1 FROM ExchangeRate) AS EndDate, 
     (SELECT Rate FROM ExchangeRate 
     WHERE Date = (SELECT MIN(Date) FROM ExchangeRate)) AS Rate 
FROM Dual 
UNION 
SELECT X1.Date  AS StartDate, 
     X2.Date - 1 AS EndDate, 
     X1.Rate  AS Rate 
    FROM ExchangeRate AS X1 JOIN ExchangeRate AS X2 
     ON X1.Date < X2.Date 
WHERE NOT EXISTS 
     (SELECT * 
      FROM ExchangeRate AS X3 
     WHERE X3.Date > X1.Date AND X3.Date < X2.Date 
     ) 
UNION 
SELECT (SELECT MAX(Date) FROM ExchangeRate) AS StartDate, 
     DATE '9999-12-31' AS EndDate, 
     (SELECT Rate FROM ExchangeRate 
     WHERE Date = (SELECT MAX(Date) FROM ExchangeRate)) AS Rate 
FROM Dual; 

परीक्षण डीबीएमएस पर (आईबीएम इन्फोर्मिक्स गतिशील सर्वर 11.50.FC6 MacOS एक्स 10.6.2 पर), मैं करने में सक्षम था:

ExtendedExchangeRate के लिए परिणाम (संबंध तोड़ना) तीन प्रश्नों का मिलन है एक दृश्य में जिज्ञासा को बदलने, लेकिन मैं डेटा प्रकार के साथ धोखा दे बंद करना पड़ा - दिनांकों में तार मजबूर द्वारा:

CREATE VIEW ExtendedExchangeRate(StartDate, EndDate, Rate) AS 
    SELECT DATE('0001-01-01') AS StartDate, 
      (SELECT MIN(Date) - 1 FROM ExchangeRate) AS EndDate, 
      (SELECT Rate FROM ExchangeRate WHERE Date = (SELECT MIN(Date) FROM ExchangeRate)) AS Rate 
    FROM Dual 
    UNION 
    SELECT X1.Date  AS StartDate, 
      X2.Date - 1 AS EndDate, 
      X1.Rate  AS Rate 
     FROM ExchangeRate AS X1 JOIN ExchangeRate AS X2 
      ON X1.Date < X2.Date 
    WHERE NOT EXISTS 
      (SELECT * 
       FROM ExchangeRate AS X3 
      WHERE X3.Date > X1.Date AND X3.Date < X2.Date 
      ) 
    UNION 
    SELECT (SELECT MAX(Date) FROM ExchangeRate) AS StartDate, 
      DATE('9999-12-31') AS EndDate, 
      (SELECT Rate FROM ExchangeRate WHERE Date = (SELECT MAX(Date) FROM ExchangeRate)) AS Rate 
    FROM Dual; 
+0

@ जोनाथन, मेरे पास एक ही मूल विचार था (और मुझे लगता है कि जब आप मेरा जवाब पोस्ट करते हैं तो आप उस पर काम कर रहे थे), हालांकि मैं विस्तारित एक्सचेंजरेट की गणना करने के लिए एक अलग (उत्तर संभवतः अधिक कुशल) दृष्टिकोण के साथ आया (जिसे मेरे कोड में रेंजएक्सचेंजरेट कहा जाता है) , और खुले बंद दिनांक सीमा को कार्यान्वित करना) वास्तव में केवल एक स्वयं-जुड़ने का उपयोग करके। तालिका में केवल एक तारीख रखना समझ में आता है क्योंकि - सही गणना मानते हुए - आप कभी भी एक असंगत डेटा संरचना के साथ समाप्त नहीं होंगे जो लेनदेन के साथ निम्नलिखित में शामिल हो सकता है। – Lucero

+0

@ लुसेरो: जैसा कि मैंने देखा, मैंने SQL में किसी भी ओलाप ऑपरेशंस (जैसे ओवर) का उपयोग नहीं किया। हालांकि मेरा उदाहरण समावेशी श्रेणियों का उपयोग करता है, मुझे संदेह है कि यह खुली बंद श्रेणियों का उपयोग करके बेहतर लिखा जाएगा। मैं खड़ा हूं 'विस्तारित एक्सचेंजरेट (एक्सएक्सआर) तालिका को वास्तविक तालिका में बनाना बेहतर होगा' (मौजूदा एक्सचेंजरेट तालिका के साथ एक्सएक्सआर तालिका का एक सरल प्रक्षेपण दृश्य होना); एक्सएक्सआर पर कुछ संशोधित संचालन के बीच संभावित असंतुलन और इसके लिए कई चुनिंदा संचालन मेरे लिए निर्णायक होंगे। एक नया मूल्य डालने के लिए वास्तविक संचालन एक ट्रिगर द्वारा संभाला जा सकता है। –

+1

@ लुसेरो: हम _are_ इस समझौते में हैं कि क्वेरी सरल है जब आपके पास विस्तारित विनिमय दर तालिका में स्पष्ट तिथि सीमा जानकारी उपलब्ध है। और डीबीएमएस जैसे नोट्स का समर्थन करता है, तो यह निश्चित रूप से अपना रास्ता करना संभव है। –

0

कई समाधान काम करेंगे। आपको वास्तव में वह व्यक्ति मिलना चाहिए जो आपके वर्कलोड के लिए सबसे अच्छा (सबसे तेज़) काम करता है: क्या आप आमतौर पर एक लेनदेन के लिए खोज करते हैं, उनमें से सभी, उनमें से सभी?

टाई ब्रेकर समाधान अपने स्कीमा दिया है:

SELECT  t.Date, 
      t.Amount, 
      r.Rate 
      --//add your multiplication/division here 

FROM  "Transactions" t 

INNER JOIN "ExchangeRates" r 
     ON r."ExchangeRateID" = (
         SELECT TOP 1 x."ExchangeRateID" 
         FROM  "ExchangeRates" x 
         WHERE  x."SourceCurrencyISO" = t."SourceCurrencyISO" --//these are currency-related filters for your tables 
           AND x."TargetCurrencyISO" = t."TargetCurrencyISO" --//,which you should also JOIN on 
           AND x."Date" <= t."Date" 
         ORDER BY x."Date" DESC) 

आप तेजी से होने के लिए इस प्रश्न के लिए सही सूचकांक की आवश्यकता है। आदर्श रूप से आपके पास JOIN"Date" पर नहीं होना चाहिए, लेकिन "ID"-जैसा फ़ील्ड (INTEGER) पर होना चाहिए। मुझे और अधिक स्कीमा जानकारी दें, मैं आपके लिए एक उदाहरण तैयार करूंगा।

+0

@ वैन - मेरे पास प्रत्येक तालिका पर एक संख्यात्मक प्राथमिक कुंजी फ़ील्ड है, उदा। लेनदेन आईडी और एक्सचेंजरेटिड। –

+0

क्वेरी को अद्यतन किया गया; इसे – van

+0

पर आज़माएं समाधान ऊपर केवल ए) नियम के लिए है। बी को ध्यान में नहीं देखा) प्रारंभ में। अब आपके साथ जोड़ने का अर्थ नहीं है क्योंकि आपके पास जवाब है जो आपको उपयुक्त बनाता है। – van

0

इसमें शामिल होने के बारे में कुछ भी नहीं है जो आपकी मूल पोस्ट में TOP 1 सहसंबंधित सबक्वायरी से अधिक सुरुचिपूर्ण होगा। हालांकि, जैसा कि आप कहते हैं, यह आवश्यकता को पूरा नहीं करता है बी

ये प्रश्न काम करते हैं (SQL सर्वर 2005 या बाद में आवश्यक)। the SqlFiddle for these देखें। के रूप में आप से पता चला है

SELECT 
    T.*, 
    ExchangeRate = E.Rate 
FROM 
    dbo.Transactions T 
    CROSS APPLY (
    SELECT TOP 1 Rate 
    FROM dbo.ExchangeRate E 
    WHERE E.RateDate <= T.TranDate 
    ORDER BY 
     CASE WHEN E.RateDate <= T.TranDate THEN 0 ELSE 1 END, 
     E.RateDate DESC 
) E; 

ध्यान दें कि क्रॉस एकल स्तंभ मान के साथ लागू कार्यात्मक SELECT खंड में सहसंबद्ध सबक्वेरी के बराबर है। मैं सिर्फ क्रॉस को लागू करना पसंद करता हूं क्योंकि यह अधिक लचीला है और आपको कई स्थानों में मान का पुन: उपयोग करने देता है, इसमें कई पंक्तियां हैं (कस्टम अनपिवोटिंग के लिए) और आपके पास एकाधिक कॉलम हैं।

SELECT 
    T.*, 
    ExchangeRate = Coalesce(E.Rate, E2.Rate) 
FROM 
    dbo.Transactions T 
    OUTER APPLY (
    SELECT TOP 1 Rate 
    FROM dbo.ExchangeRate E 
    WHERE E.RateDate <= T.TranDate 
    ORDER BY E.RateDate DESC 
) E 
    OUTER APPLY (
    SELECT TOP 1 Rate 
    FROM dbo.ExchangeRate E2 
    WHERE E.Rate IS NULL 
    ORDER BY E2.RateDate 
) E2; 

मैं नहीं जानता कि जो एक बेहतर प्रदर्शन कर सकते हैं, या यदि या तो पृष्ठ पर अन्य उत्तर की तुलना में बेहतर प्रदर्शन करेंगे। दिनांक कॉलम पर उचित इंडेक्स के साथ, उन्हें बहुत अच्छी तरह से ज़िंग करना चाहिए - निश्चित रूप से Row_Number() समाधान से बेहतर है।

संबंधित मुद्दे