如何在SQL中实现此算法?

时间:2009-09-25 15:58:03

标签: sql sybase

我正在尝试在SQL中实现一个算法(transact 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

如果您需要澄清任何细节,请与我们联系。谢谢!

7 个答案:

答案 0 :(得分: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

答案 1 :(得分: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

答案 2 :(得分: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')

答案 3 :(得分:0)

冒着被告知我没有回答这个问题的风险,为什么有必要在SQL中完成?似乎业务逻辑可能更适合应用程序层,甚至可能在其他地方重用。

答案 4 :(得分: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

这应该会告诉你平衡最低点的那一天。您可能需要调整此开始日期和起始余额。同样,如果您只想返回一天,则需要将输出限制在第一行。

答案 5 :(得分: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

要将输出限制为负值,请添加预设值 (但是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

答案 6 :(得分: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