Oracle - 如何编写这个SQL?

时间:2011-08-28 20:45:20

标签: sql oracle

在Oracle中,我有一个记录用户交易的表格,如下所示。目标查询不需要用户列,只需在此处列出以供参考。

user1, transaction1, $10            <-row1
user1, transaction2, $20            <-row2
user1, transaction3, $5             <-row3
user1, transaction4, $100           <-row4
user2, ... ...
user3, ... ...

对于给定的用户,会有一个上限金额,我需要找出其金额&gt; =给定金额上限的最小行,或者如果金额上限大于该用户的所有行总和。返回的行必须按事务按升序排序。

例如,对于user1,给定的上限为30美元。然后必须返回row1和row2。你不能返回row4因为我们必须遵循交易顺序。如果给定的上限为13美元,则必须返回row1和row2,因为row1不足以覆盖13美元。如果给定的上限是136美元,则返回第1 / 2/3/4行,因为10美元+ 20美元+ 5美元+ 100美元小于136美元。

使用游标我们可以使用存储过程来解决这个问题,但是我无法找到一种优雅的方法来使用一些嵌套查询来实现这一点。真的很感谢你的帮助!

3 个答案:

答案 0 :(得分:3)

您可以使用分析函数轻松完成此操作:

SELECT user_id, transaction_id, transaction_value
FROM   (SELECT user_id,
               transaction_id,
               transaction_value,
               SUM(transaction_value) 
                  OVER (PARTITION BY user_id 
                        ORDER BY transaction_id) AS running_total
        FROM   transactions)
WHERE  running_total <= :transaction_cap

以这种方式使用SUM根据ORDER BY子句(在这种情况下,行的事务和具有较低ID的所有事务)提供当前行和所有先前行的总和。 PARTITION BY子句指定的列是相同的。


再看一下这个问题,我意识到这不起作用,因为它只会返回低于你要寻找的值的值,而不是包含达到该点的值。如果前一行小于目标总数,则以下修订返回当前行。

SELECT user_id, transaction_id, transaction_value
FROM   (SELECT user_id,
               transaction_id,
               transaction_value,
               running_total,
               LAG(running_total) 
                   OVER (PARTITION BY user_id 
                         ORDER BY transaction_id) AS prior_total
        FROM   (SELECT user_id,
                       transaction_id,
                       transaction_value,
                       SUM(transaction_value) 
                          OVER (PARTITION BY user_id 
                                ORDER BY transaction_id) AS running_total
                FROM   transactions))
WHERE  prior_total < :transaction_cap or prior_total  is null

答案 1 :(得分:1)

对于特定上限,对所有用户都相同:

SELECT user, transaction, amount
FROM MyTable t
WHERE ( SELECT SUM(ts.amount)
        FROM MyTable ts
        WHERE ts.user = t.user
          AND ts.transaction < t.transaction
      ) < @cap 
ORDER BY user, transaction

答案 2 :(得分:1)

根据要求,这是一个R解决方案。我不得不做出一些假设来把它们放在一起,这里它们是:

  1. 资金上限信息存储在一个单独的表中,该表中有适当的密钥以加入交易数据
  2. 如果用户的第一笔交易大于其资金上限,则不会为该用户返回任何行
  3. 我对以下代码进行了大量评论,但如果您有任何疑问,请与我们联系。我首先创建了一些表示数据的虚假数据,然后在最底层运行您需要的查询。

    您可以通过RODBC包查看数据库与R的接口。

    #load needed package
    require(plyr)
    #Sed seet for reproducibility
    set.seed(123)
    
    #Make some fake data
    dat <- data.frame(user = rep(letters[1:4], each = 4)
                      , transaction = rep(1:4, 4)
                      , value = sample(5:50, 16,TRUE) 
                      )
    #Separate "data.frame" or table with the money cap info
    moneyCaps <- data.frame(user = letters[1:4], moneyCap = sample(50:100, 4, TRUE))
    
    #Ensure that items are ordered by user and transcation #. 
    dat <- dat[order(dat$user, dat$transaction) ,]
    
    #Merge the transaction data with the moneyCap data. This is equivalant to an inner join
    dat <- merge(dat, moneyCaps)
    
    #After the merge, the data looks like this:
    
    
    user transaction value moneyCap
    1     a           1    18       62
    2     a           2    41       62
    3     a           3    23       62
    4     a           4    45       62
    5     b           1    48       52
    6     b           2     7       52
    ....
    
    #Use the plyr function ddply to split at the user level and return values which are <=
    #to the moneyCap for that individual. Note that if the first transaction for a user
    #is greater than the moneyCap, then nothing is returned. Not sure if that's a possibility
    #with your data
    
    ddply(dat, "user", function(x) subset(x, cumsum(value) <= moneyCap))
    
    #And the results look like:
    
      user transaction value moneyCap
    1    a           1    18       62
    2    a           2    41       62
    3    b           1    48       52
    ...