سؤال

I have a large dataset with invoices that I need to perform some currency conversions on. There are a lot of joins required to get the data, but the basic idea is that One table has the individual invoice charges in USD, while another has the total charges in Local Currency, but no where are the individual charges on the invoice provided in local currency, so I need to compute the effective exchange rate by dividing Local Currency Total by USD Total and then multiply the charges by this rate. So far, I have successfully completed this for a single record as follows:

DECLARE @USD_Total float, @LOC_Total float, @FX_Rate float

SET @USD_Total = (Select SUM (InvoiceTable.USD_ChargeAmount)
FROM 
{Some Joins}
WHERE InvoiceID in ('1234567')
Group By InvoiceTable.InvoiceID)

SET @LOC_Total = (Select LocalInvoiceTable.ChargeTotal
FROM
{Some Joins}
WHERE InvoiceID in ('1234567')
Group By LocalInvoiceTable.InvoiceID

SET @FX_Rate = @LOC_Total / @USD_Total

SELECT
InvoiceTable.InvoiceID
    SUM(CASE InvoiceTable.ChargeCode when 'TYPE A' THEN InvoiceTable.USD_ChargeAmount ELSE 0 END)*@FX_Rate As Type_A,
    SUM(CASE InvoiceTable.ChargeCode when 'TYPE B' THEN InvoiceTable.USD_ChargeAmount ELSE 0 END)*@FX_Rate As Type_B,
    SUM(CASE InvoiceTable.ChargeCode when 'TYPE C' THEN InvoiceTable.USD_ChargeAmount ELSE 0 END)*@FX_Rate As Type_C
FROM
{Some Joins}
WHERE InvoiceID in ('1234567')
Group By LocalInvoiceTable.InvoiceID

So this works fine, but I need to replicate this for thousands of invoice ID's. How can I accomplish this without the WHERE clause? I'm a novice at this, so any help is greatly appreciated. I am using Microsoft SQL Server 2008.

هل كانت مفيدة؟

المحلول 3

Don't think in terms of individual rows. You have sets of rows so think in terms of sets of each question. Using CTEs you can encapsulate each piece (result set for each part of the question) and combine the answers in either other CTEs or the final query.

The following adaptation of your query shows 3 CTEs:

  1. First we get USD values for each InvoiceID
  2. Second we get localized values for each InvoiceID
  3. Third we calculate the effective exchange rate based on the previous two aggregations, JOINing on their respective InvoiceIDs

Finally, in the main query we use the outcome of the effective Exchange Rate calculation

;WITH USD AS (
    SELECT InvoiceID, SUM(InvoiceTable.USD_ChargeAmount) AS [Total]
    FROM 
    {Some Joins}
    GROUP BY InvoiceTable.InvoiceID
),
LOC AS (
    SELECT InvoiceID, SUM(LocalInvoiceTable.ChargeTotal) AS [Total]
    FROM
    {Some Joins}
    GROUP BY LocalInvoiceTable.InvoiceID
),
FX AS (
    SELECT USD.InvoiceID,
           CONVERT(MONEY, LOC.Total) / CONVERT(MONEY, USD.Total) AS [Rate]
    FROM USD
    INNER JOIN LOC
            ON LOC.InvoiceID = USD.InvoiceID
),
SELECT InvoiceTable.InvoiceID,
       SUM(CASE InvoiceTable.ChargeCode
              when 'TYPE A' THEN InvoiceTable.USD_ChargeAmount
              ELSE 0 END) * FX.[Rate] AS [Type_A],
       SUM(CASE InvoiceTable.ChargeCode
              when 'TYPE B' THEN InvoiceTable.USD_ChargeAmount
              ELSE 0 END) * FX.[Rate] AS [Type_B],
       SUM(CASE InvoiceTable.ChargeCode
              when 'TYPE C' THEN InvoiceTable.USD_ChargeAmount
              ELSE 0 END) * FX.[Rate] AS [Type_C]
FROM InvoiceTable
{Some Joins}
INNER JOIN FX
        ON FX.InvoiceID = InvoiceTable.InvoiceID

EDIT:
A more ideal approach would be to store the exchange rate used at the time that the InvoiceTable and LocalInvoiceTable rows are being inserted. You could use a table similar to:

InvoiceInfo
(
  InvoiceID INT NOT NULL, -- PK, FK to InvoiceTable
  ExchangeRate SMALLMONEY NOT NULL,
  CurrencyCode CHAR(3) NOT NULL, -- USD, EUR, etc.
  InvoiceTotalUSD MONEY, -- optional
  InvoiceTotalLocal MONEY -- optional
)

I would recommend using the ISO 4217 currency codes. The two InvoiceTotal* fields are only for if you routinely need to aggregate by InvoiceID outside of needing to calculate the "effective exchange rate".

Storing the info at the time of Invoice creation reduces your query to just:

SELECT InvoiceTable.InvoiceID,
       SUM(CASE InvoiceTable.ChargeCode
              when 'TYPE A' THEN InvoiceTable.USD_ChargeAmount
              ELSE 0 END) * II.[ExchangeRate] AS [Type_A],
       SUM(CASE InvoiceTable.ChargeCode
              when 'TYPE B' THEN InvoiceTable.USD_ChargeAmount
              ELSE 0 END) * II.[ExchangeRate] AS [Type_B],
       SUM(CASE InvoiceTable.ChargeCode
              when 'TYPE C' THEN InvoiceTable.USD_ChargeAmount
              ELSE 0 END) * II.[ExchangeRate] AS [Type_C]
FROM InvoiceTable
{Some Joins}
INNER JOIN InvoiceInfo II
        ON II.InvoiceID = InvoiceTable.InvoiceID

نصائح أخرى

You must not approach it as a procedural program. SQL is a language for set manipulation. You need to think in terms like calculated columns and joins. First you need to calculate USD totals by invoice, then join it to LocalInvoiceTable and divide by ChargeTotal to get the FX rate. Finally join back to InvoiceTable and multiply each individual charge by the FX rate.

with UsdTotal as
(
  select InvoiceID, sum(USD_ChargeAmount) as USD_ChargeTotal
  from InvoiceTable
  group by InvoiceID
)
, FxRate as
(
  select lit.InvoiceID, lit.ChargeTotal / ut.USD_ChargeTotal as Rate
  from LocalInvoiceTable lit
  inner join UsdTotal ut
    on ut.InvoiceID = lit.InvoiceID
)
select
  it.InvoiceId,
  it.USD_ChargeAmount as UsdValue,
  it.USD_ChargeAmount * fx.Rate as LocalCcyValue
from InvoiceTable it
inner join FxRate fx on fx.InvoiceID = it.InvoiceID

You can try it in SQLFiddle. Please note that FX rates change every day. If the individual charges have been realized on different days you don't get the effective FX rate just and average. Hence the calculated amounts in local currency will be different from the real ones.

Yes you can. No you shouldn't

It just a bad programming practice. You should separate and isolate different parts of your program for ease of future maintenance (think of the next programmer!)

SQL is a data persistence language, it is not a great procedural language

By all means use SQL to select complex data representations for reports etc. But what you have here is business rules (logic) embedded into the data request. Where else are these business rules repeated?

Business rules should exist in one, and only one place, and be referred to from there (see DRY principle) . If you scatter your business rules throughout the system it will quickly become unmaintainable. History has shown this time and time again

Use your service client language of choice (Java, c#, etc. Any decent OO language will do) to do this task. Do the select across all the invoice numbers at once for each data set. Wrap the whole thing in an explicit transaction so you can be sure of stable results if required

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top