tags:

views:

81

answers:

6

I've got this SQL QUERY in MS SQL 2005/2008 database which gets me Money Amount, Money Currency, Money Time and Currency Converter. I left join it with Table that has gathered information from polish national bank about currency converter per each day.

Here's the query:

SELECT  t1.[TransakcjeGotowkoweKwota],  
        t1.TypyWaluty,  
        t1.[TransakcjeGotowkoweData],  
        t2.[kurs_sredni]  
FROM    [BazaZarzadzanie].[dbo].[TransakcjeGotowkowe] t1

LEFT JOIN [BazaZarzadzanie].[dbo].[KursyWalutNBP] t2
ON t1.TypyWaluty = t2.[kod waluty]  AND t2.[data publikacji] = t1.[TransakcjeGotowkoweData]  
WHERE   [TypyWaluty] = 'EUR'

Here's the output:

TransakcjeGotowkoweKwota TypyWaluty TransakcjeGotowkoweData kurs_sredni
-14153.04000000         EUR         2009-01-05 00:00:00.000    4,1137    
-18.36000000            EUR         2009-07-01 00:00:00.000    4,4157    
4.61000000              EUR         2007-09-30 00:00:00.000    NULL
55.50000000             EUR         2007-09-30 00:00:00.000    NULL  

The problem is with NULL values for Kurs_sredni. It happens when [kurs_sredni] cannot be found in [KursyWalutNBP] for that particular day. What i would like to achieve is when it happens it should get the nearest possible date and get value for that day.

For example:
If value is NULL for [Kurs Sredni] for date 2007-09-30 it should get value from 2007-10-01 (if it has one of course).

How should i aproach this?

With regards,

MadBoy

+1  A: 

I would personally move to a User Defined Function for this type of thing, rather than a join. This way you can have full control over the evaluation process. You could do it in a manner like the following, based on your notes.

SELECT TOP 1 kurs_sredni
FROM YourTable
WHERE (Your Comparison here)
ORDER BY Date 

This way, you can do a >= comparison on date, and if it doesn't exist, you will get the next latest date value.

Mitchel Sellers
Do you mean i should create one query to get all the problematic NULL values and then in c# execute your query for every single entry in that table? Or you mean something else? If it's first it will be my last resort to get them to C# put them in variables and then run query per each variable. But that would be slower then native proper query ?
MadBoy
Just create a user defined function, in SQL Server that gets the value. An rather than using a join, your select would contain something like. Select ColumA, ColumnB, MyFunctionName(MyInputColumn) AS MyColumn
Mitchel Sellers
I've went your way and wrote user defined function. It takes 2 seconds to execute query with that function (i execute it only when it's missing the value). Sorry for asking but should i post that function and query in my first post (for reference) or maybe use Answer my own question option? Or I shouldn't do any :)
MadBoy
A: 

I'm having a bit of trouble understanding your query, but this is a general solution to what I think you're trying to solve. If you have potentially many children per parent then you might want to limit by the date to start with (for example, include criteria in the LEFT OUTER JOIN and subquery such that it limits to only children within 3 days of @my_date (as an example). At least then an index on the date might get some use, whereas the below code won't be able to use it at all.

SELECT
    P.parent_id,
    C.stuff
FROM
    Parent P
LEFT OUTER JOIN Child C ON
    C.parent_id = P.parent_id
WHERE
    NOT EXISTS
    (
        SELECT
            *
        FROM
            Child C2
        WHERE
            C2.parent_id = P.parent_id AND
            ABS(DATEDIFF(ss, C2.my_date, @my_date)) < ABS(DATEDIFF(ss, C.my_date, @my_date))
    )
Tom H.
I don't think it's the right one. What i want is to get [kurs_sredni] for diffrent date if its NULL.
MadBoy
Yes... if the dates match then C.my_date will be equal to @my_date and you won't find any ABS values for C2.my_date - @my_date which are less than that. If it doesn't have an exact match then it will return the closest one. Don't think of it as a two-step process. SQL deals with sets, not step-by-step processing.
Tom H.
+1  A: 

I can not try with MS SQL, but something like that should work for you.

It should return the value with the smallest date-difference (same date if possible).

SELECT * FROM (
    SELECT  t1.[TransakcjeGotowkoweKwota],  
            t1.TypyWaluty,
            t2.[kurs_sredni],
            ROW_NUMBER() OVER( PARTITION BY t1.[TransakcjeGotowkoweData] ORDER BY ABS(cast(t1.[TransakcjeGotowkoweData] - t2.[data publikacji] AS FLOAT)) ) rank
    FROM    [BazaZarzadzanie].[dbo].[TransakcjeGotowkowe] t1
    LEFT JOIN [BazaZarzadzanie].[dbo].[KursyWalutNBP] t2
    ON t1.TypyWaluty = t2.[kod waluty]
    WHERE   [TypyWaluty] = 'EUR'
) x
WHERE rank = 1
Peter Lang
This query returned 1 row (when it should 1818) :-) TransakcjeGotowkoweKwota TypyWaluty kurs_sredni rank0.01000000 EUR 3,7900 1
MadBoy
If I understand it right, you should be able to replace the `LEFT JOIN` by a `JOIN` in my answer. Please try that too.
Peter Lang
You're close but it's showing 200+ and i need 1800. Here's output from simple query i run (that doesn't show null but it shows that there can be more then one transaction-6054.09000000 EUR 2006-03-13 00:00:00.000 3,8952 -5462.40000000 EUR 2006-03-14 00:00:00.000 3,9390 -5287.85000000 EUR 2006-03-14 00:00:00.000 3,9390 -6104.16000000 EUR 2006-03-14 00:00:00.000 3,9390 Where yours assumes it only one:-6054.09000000 EUR 3,8952 1 2006-03-13 00:00:00.000-6012.32000000 EUR 3,9390 1 2006-03-14 00:00:00.000-6122.49457600 EUR 3,8788 1 2006-03-15 00:00:00.000
MadBoy
Also problem with this it takes some amount of time to process it. But i guess i could sacrifice it since i won't be working with full table but only some transactions per client.
MadBoy
Try adding `t1.[TransakcjeGotowkoweKwota]` to the `PARTITION BY`, so that it becomes `PARTITION BY t1.[TransakcjeGotowkoweKwota], t1.[TransakcjeGotowkoweData] ORDER BY...`
Peter Lang
It was close as it says 1689 rows now. Howerver it took 46 seconds to execute query. It's definetly too long. I need to find other solution. Probably Mitchels solution to create user defined function would be better idea?
MadBoy
I have marked your answer as useful but went Mitchel's way. It seems to do what i need in short time (2 seconds).
MadBoy
Glad your problem is solved :)
Peter Lang
A: 

Something like this should do it:

SELECT  t1.[TransakcjeGotowkoweKwota],  
        t1.TypyWaluty,  
        t1.[TransakcjeGotowkoweData],  
        t2.[kurs_sredni]  
FROM    [BazaZarzadzanie].[dbo].[TransakcjeGotowkowe] t1
LEFT JOIN (
    select ta.TypyWaluty, ta.[TransakcjeGotowkoweData], min(ABS(cast(ta.[TransakcjeGotowkoweData] - tb.[data publikacji] as float))) as ClosestDate
    FROM    [BazaZarzadzanie].[dbo].[TransakcjeGotowkowe] ta
    inner join [BazaZarzadzanie].[dbo].[KursyWalutNBP] tb ON ta.TypyWaluty = tb.[kod waluty] 
    group by ta.TypyWaluty, ta.[TransakcjeGotowkoweData]
) t2c ON t1.TypyWaluty = t2c.TypyWaluty
    AND t1.[TransakcjeGotowkoweData] = t2c.[TransakcjeGotowkoweData]
LEFT JOIN [BazaZarzadzanie].[dbo].[KursyWalutNBP] t2 ON t2c.TypyWaluty = t1.TypyWaluty
    AND t1.[TransakcjeGotowkoweData] = t2.[TransakcjeGotowkoweData]
    AND t2c.ClosestDate = ABS(cast(t1.[TransakcjeGotowkoweData] - t2.[data publikacji] as float))
WHERE   t1.[TypyWaluty] = 'EUR'
RedFilter
@OrbMan: Correct me if I'm wrong, but this would return two rows if "today" was missing, but both "yesterday" and "tomorrow" were available?
Peter Lang
Your query returns more then one value per day. And returned 48000+ rows :-) While mine only returns 1800.-6054.09000000 EUR 2006-03-13 00:00:00.000 3,2652 -6054.09000000 EUR 2006-03-13 00:00:00.000 2,3931 -6054.09000000 EUR 2006-03-13 00:00:00.000 0,4207 -6054.09000000 EUR 2006-03-13 00:00:00.000 2,8104
MadBoy
Yes, the query is flawed in that it will return dupes. Not sure why it is returning 48,000+, though. It needs to have a top 1 added...no time now unfortunately! It is a little hard for me without some source data to work from...
RedFilter
[dbo].[KursyWalutNBP] has 59k rows, and [dbo].[TransakcjeGotowkowe] has 40k rows. Probably mix of both gives 48k ;-) Don't know a way to share data other then pasting what it has.
MadBoy
Edited query, try again!
RedFilter
Still getting over 40k rows :-)
MadBoy
I think I'll need some sample data in order to do any better, the non-English words are confounding my brain. Some DDL to create schema and 5 or 10 rows showing the common use cases should do it.
RedFilter
A: 

Best aproach to this was Mitchel's one. I've created

CREATE FUNCTION KursWaluty
(
  @typWaluty nvarchar(15),
  @dataWaluty DATETIME
)
RETURNS varchar(30)
AS BEGIN
RETURN ( SELECT TOP 1
                kurs_sredni
         FROM   [BazaZarzadzanie].[dbo].[KursyWalutNBP]
         WHERE  [kod waluty] = @typWaluty
                AND [data publikacji] >= @dataWaluty
         ORDER BY [data publikacji]
       )

end

And used this query to get it:

SELECT  t1.[TransakcjeGotowkoweKwota],
    TypyWaluty,
    [TransakcjeGotowkoweData],
    CASE WHEN [kurs_sredni] IS NULL
         THEN BazaZarzadzanie.dbo.KursWaluty(TypyWaluty, [TransakcjeGotowkoweData])
         ELSE [kurs_sredni]
    END AS 'Currency'

FROM    [BazaZarzadzanie].[dbo].[TransakcjeGotowkowe] t1
    LEFT JOIN [BazaZarzadzanie].[dbo].[KlienciPortfeleKonta] t2
    ON t1.[KlienciPortfeleKontaID] = t2.[KlienciPortfeleKontaID]
    LEFT JOIN [BazaZarzadzanie].[dbo].[KursyWalutNBP]
    ON TypyWaluty = [kod waluty]
       AND [data publikacji] = [TransakcjeGotowkoweData]
 WHERE   [TypyWaluty] = 'EUR' -- AND [kurs_sredni] IS NULL

This works and seems to be preety fast (2 seconds). I have used AND [kurs_sredni] IS NULL to verify that the null values got the right values now.

MadBoy
+1  A: 

A helper table of dates is useful for this; joining this to KursyWalutNBP you can easily work out the right exchange rate for each date. I'm presenting this as a view to make the final query simpler:

CREATE VIEW CurrencyNearRates (kod_waluty, data, kurs_sredni)
AS 
SELECT currencydates.kod_waluty, currencydates.data, ratenow.kurs_sredni FROM (SELECT currencies.kod_waluty, Helper_Dates.data FROM currencies CROSS JOIN Helper_Dates) currencydates
LEFT OUTER JOIN KursyWalutNBP ratenow
ON 
currencydates.kod_waluty = ratenow.kod_waluty
AND currencydates.data <= ratenow.data_publikacji
AND ratenow.data_publikacji = 
(
  SELECT MIN(futurerates.data_publikacji)
  FROM KursyWalutNBP futurerates
  WHERE ratenow.kod_waluty = futurerates.kod_waluty
  AND currencydates.data <= futurerates.data_publikacji
) 

That gives you data like this:

SELECT * FROM CurrencyNearRates ORDER BY kod_waluty, data;
|kod_waluty     |data        |kurs_sredni|
|----------------------------------------|
|EUR            |2009-01-04  |4.1137     |
|EUR            |2009-01-05  |4.1137     |
|EUR            |2009-01-06  |4.4157     |
|EUR            |2009-01-07  |4.4157     |
 ----------------------------------------

Then you just do a simple join between the transactions in TransakcjeGotowkowe and the rates in CurrencyNearRates:

SELECT
 t1.[TransakcjeGotowkoweKwota],  
 t1.[TypyWaluty],  
 t1.[TransakcjeGotowkoweData],  
 CurrencyNearRates.[kurs_sredni]
FROM
dbo.[TransakcjeGotowkowe] t1
LEFT OUTER JOIN CurrencyNearRates
 ON t1.[TypyWaluty] = CurrencyNearRates.[kod_waluty]
 AND t1.[TransakcjeGotowkoweData] = CurrencyNearRates.[data]
WHERE t1.[TypyWaluty] = 'EUR'  
ORDER BY t1.[TransakcjeGotowkoweData]

And that gives you output like this:

|TransakcjeGotowkoweKwota   |TypyWaluty   |TransakcjeGotowkoweData   |kurs_sredni   |
|-----------------------------------------------------------------------------------|
|-18.36                     |EUR          |2009-07-01                |4.4157        |
|-14153.04                  |EUR          |2009-01-05                |4.1137        |
|4.61                       |EUR          |2007-09-30                |4.5678        |
|55.5                       |EUR          |2007-09-30                |4.5678        |
 -----------------------------------------------------------------------------------
vincebowdren
Helo vincebowdren, THanks for your example. I will try it out later on and see if it's faster then having user function like i have now
MadBoy