2009-09-25 143 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