2009-09-25 145 views
1

我正試圖在SQL(transact sql)中實現一個算法,並且鑑於我目前的能力而發現它很困難。我試圖將問題解決到問題。這個算法背後的基本思想是用戶正在計劃一個月的預算。他們對有多少錢以及何時來來往往有個好主意。這是本月中旬。問題是:根據目前的義務,在這個月的其餘時間,賬戶最糟糕的位置是什麼?下面如何在SQL中實現此算法?

例如在看的時候線讓我們說

Today = 15th 
Util = 17th 
B-day = 19th 
Cable = 22nd 
Wages = 25th 

17日該帳戶會比今天少$ 150 在19日的帳戶將比今天多100美元。 在22日的帳戶將比今天少25美元。 25日的賬戶將比今天多975美元。

所以在這個例子中,查詢將返回 - $ 150。

注:我只關心返回的負值。如果它是負面的意味着你有義務,不應該花這筆錢。如果它是積極的,那並不重要。您不能將錢花在您的帳戶中。

|         |            | 
|  ^  ^   | ^  ^       | 
|   |Rent(-500) |Phone(-50) | |Util(-150) |Cable(-125)    | 
----------------------------------------------------------------------------------- 
|  ^      |  ^     ^    | 
|  |Wages(+1000)    |  |B-day(+250)   |Wages(+1000) | 
|         |            | 
Past        Today           Future 

一個簡單的表格,我們可以利用這個問題:

create table MoneyFlow 
(
    fiscalEventID int not null, 
    value money, 
    transactionDate date 
) 

另一種方式來看待它。你如何在SQL中執行以下算法?

Algorithm 
    Input: Start date, End date 
    Output: Worst position the account is going to be in in the future. 

    WorstPosition = 0 //only want worst position if it is negative. 
    For each date D between start date and end date where a transaction takes place 
    Position_D = Sum deposits and withdrawls between start date and D 
     If Position_D < WorstPosition 
    WorstPosition = Position_D 

    return WorstPosition 



還要說明一點,我使用的數據庫的Sybase

讓我知道你是否需要澄清的任何細節。謝謝!

+2

榮譽。 – 2009-09-25 16:07:29

+0

順便說一句,這個問題聽起來像它可能是作業。如果是這樣,請標記爲。 – 2009-09-25 16:16:54

+0

我同意,這會做出好的作業問題。我用「預算」的例子來隱藏大部分討厭的商業細節。例如,在我正在處理的問題中,月末沒有傳入。未來空間在上次提款交易日期結束。 此外,我將不得不添加像'預算分類' – Jon 2009-09-25 17:47:54

回答

5

它看起來像我試圖創建一個運行總數,然後從運行總數中選擇最小的運行值。

以下是不漂亮,但它避免遊標。

先從下面來填充你的表:

CREATE TABLE #temp 
    (someDate datetime 
    ,amount decimal) 

INSERT INTO #temp (someDate, amount) 
SELECT '2009-01-01', 1000 UNION ALL 
SELECT '2009-01-02', -500 UNION ALL 
SELECT '2009-01-03', -50 UNION ALL 
SELECT '2009-01-04', -150 UNION ALL 
SELECT '2009-01-05', 250 UNION ALL 
SELECT '2009-01-06', -125 UNION ALL 
SELECT '2009-01-07', 1000 

這裏有一個簡單的查詢,以獲得最低運行總計:

SELECT 
    TOP 1 
    base.someDate 
    ,runningTotal = 
     (SELECT sum(derived.amount) 
     FROM #temp derived 
     WHERE derived.someDate <= base.someDate) 
FROM #temp base 
ORDER BY runningTotal ASC 
+0

對於少量數據可以原諒的,這可能是比光標更好,但是當行數增加我認爲這會導致問題。 – Brimstedt 2009-09-25 16:33:40

+0

@Brimstedt:我的第一個解決方案實際上和你的類似,但是你在我做之前發佈過;)但是,你的分析是完全正確的:不包括由數據庫引擎執行的優化,每行需要評估它之前的所有行。因此,性能相當於O(1 + 2 + 3 + 4 + ... n)= O(n(n + 1)/ 2)= O(n^2),所以我們會看到問題,如果數據集變得太大(n> 5000)。 – Juliet 2009-09-25 17:03:47

0

在被告知我沒有回答這個問題的風險中,爲什麼需要在SQL中完成?似乎業務邏輯可能更適合應用層,甚至有可能在其他地方重新使用。

+1

你想重新編碼算法爲每個應用程序層語言改變?我沒有... – 2009-09-25 16:11:31

+0

這是一個有趣的觀點Gratzy,但是將它作爲SQL中的存儲過程或UDF實際上會使得可重用的邏輯成爲可能。例如,如何在Crystal Report中重用Java類來進行此計算?那麼,你可以,但它會是一個醜陋的黑客。留在你的數據庫將允許儘可能廣泛的重用。 – 2009-09-25 16:15:50

+0

看來用SQL以外的語言編碼和維護會更容易。我不認爲把它留在db中可以實現儘可能廣泛的重用。如果那樣的話,所有的商業邏輯都不會駐留在那裏? – Gratzy 2009-09-25 16:20:49

3

我不確定Sybases T-SQL,但MS SQL的方言可以使用類似下面的技巧。

請注意,雖然它的工作原理,我不知道它是記錄的行爲。要真正確定你應該使用psasik建議的光標。

SET NOCOUNT ON 

CREATE TABLE MoneyFlow 
(
    fiscalEventID INT NOT NULL, 
    value MONEY, 
    transactionDate DATETIME 
) 
go 

INSERT INTO MoneyFlow VALUES(1, 1000, '2009-08-25') 
INSERT INTO MoneyFlow VALUES(1, -500, '2009-08-30') 
INSERT INTO MoneyFlow VALUES(1, -50, '2009-09-01') 

-- Today 

INSERT INTO MoneyFlow VALUES(1, -150, '2009-09-17') -- -150 
INSERT INTO MoneyFlow VALUES(1, +250, '2009-09-19') -- +100 
INSERT INTO MoneyFlow VALUES(1, -125, '2009-09-22') -- -25 
INSERT INTO MoneyFlow VALUES(1, 1000, '2009-09-25') -- +975 
--INSERT INTO MoneyFlow VALUES(1, -2000, '2009-09-25') -- -1025 



GO 

DECLARE @curr MONEY 
, @min MONEY 

SELECT @curr = 0 
, @min = 0 

SELECT @curr = @curr + value 
, @min = CASE 
      WHEN @curr < @min THEN @curr 
      ELSE @min 
     END 
FROM MoneyFlow f (NOLOCK) 
WHERE f.transactionDate > '2009-09-15' 

SELECT @min 

GO 
DROP TABLE MoneyFlow 
+0

+1:不起眼的黑客可以通過O(n)的解決方案:) – Juliet 2009-09-25 17:05:32

1

這是MS TSQL但我想這將是類似SYBASE

SELECT MIN(lmv.value) 
FROM @moneyFlow mv 
JOIN (
    SELECT SUM(mv.value) as [VALUE], lmv.fiscalEventID 
    FROM @moneyFlow mv 
    JOIN @moneyFlow lmv ON mv.transactionDate <= lmv.transactionDate 
    WHERE mv.transactionDate >= @Start AND mv.transactionDate <= @End 
     AND lmv.transactionDate >= @Start AND lmv.transactionDate <= @End 
    GROUP BY lmv.fiscalEventID 
) lmv ON mv.fiscalEventID = lmv.fiscalEventID 
WHERE lmv.value < 0 

DECLARE @Start DATETIME 
SET @Start = '1/2/09' 
DECLARE @End DATETIME 
SET @End = '1/6/09' 
DECLARE @moneyFlow TABLE (
    fiscalEventID int not null, 
    value money, 
    transactionDate DATETIME 
) 

INSERT @moneyFlow VALUES (1, 1000, '1/1/09') 
INSERT @moneyFlow VALUES (2, -500, '1/2/09') 
INSERT @moneyFlow VALUES (3, -50, '1/3/09') 
INSERT @moneyFlow VALUES (4, -150, '1/4/09') 
INSERT @moneyFlow VALUES (5, 250, '1/5/09') 
INSERT @moneyFlow VALUES (6, -125, '1/6/09') 
INSERT @moneyFlow VALUES (7, 1000, '1/7/09') 
0

我不熟悉與Sybase,所以我不知道你是否能做到這一點或沒有,但我會嘗試像下面這樣:

select a.transactionDate as balanceDate 
    , (select sum(value) 
      from MoneyFlow b 
     where b.transactionDate <= a.transactionDate 
     ) as balance 
    from MoneyFlow a 
order by 2 

這應該告訴你的日子,其中平衡底部。您可能需要調整它的起始日期和起始餘額。同樣,如果您只希望有一天返回,則需要將輸出限制在第一行。

+0

我認爲這是一個隱藏的rbar? http://www.sqlservercentral.com/articles/T-SQL/61539/ – Brimstedt 2009-09-25 16:31:49

+0

是的,我認爲你可能是對的,但你可以建議一個不是SQL的解決方案嗎? – 2009-09-25 17:02:36

+0

我認爲最可靠的方法將是一個遊標(雖然這很痛苦)。我也發佈了可能有用的黑客攻擊;-) – Brimstedt 2009-09-25 17:20:01

0

這個查詢提供了運行總計:

Select M1.TransactionDate, Sum(M2.Money) 
From MoneyFlow M1 
    Join MoneyFlow M2 
      On M2.TransactionDate <= M1.TransactionDate 
Group By M1.TransactionDate 

您希望最小的這些,所以這個SQL應該做..

Select Min(RunBalance) From 
(Select M1.TransactionDate, Sum(M2.Money) RunBalance 
    From MoneyFlow M1 
    Join MoneyFlow M2 
      On M2.TransactionDate <= M1.TransactionDate 
    Group By M1.TransactionDate) Z 

要限制輸出中爲負值添加predivate。
(但SQL應該只產生一個行,所以如果沒有底片,這將導致空輸出...)在詢問一個相當複雜的問題清晰和徹底

Select Min(RunBalance) From 
(Select M1.TransactionDate, Sum(M2.Money) RunBalance 
    From MoneyFlow M1 
    Join MoneyFlow M2 
      On M2.TransactionDate <= M1.TransactionDate 
    Group By M1.TransactionDate) Z 
Where RunBalance > 0 
0
select min(Value) 
from (
    select sum(value) as Value 
    from MoneyFlow 
    group by transactionDate 
    where transactionDate between @startdate and @enddate 
) a 
where min(Value) < 0