【问题标题】:Oracle - How to write this SQL?Oracle - 如何编写此 SQL?
【发布时间】:2011-08-29 21:21:53
【问题描述】:

在 Oracle 中,我有一张记录用户事务的表,如下所示。目标查询不需要用户列,请在此处列出以供参考。

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

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

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

使用游标我们可以使用存储过程来解决这个问题,但是我找不到一种优雅的方式来使用一些嵌套查询和 sum 来实现这一点。非常感谢您的帮助!

【问题讨论】:

  • 资金上限是否因用户而异?还是在整个查询中保持一致?你愿意使用 R 之类的东西吗?我可以看到这是 R 中的一个单行...
  • 是的,资金上限因用户而异。不幸的是,我必须使用 Oracle SQL。你介意发布 R 语言中的 1 线性吗?
  • 如果上限因用户而异,该信息(上限)存储在哪里?

标签: sql oracle


【解决方案1】:

您可以使用分析函数相当容易地做到这一点:

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

【讨论】:

    【解决方案2】:

    对于特定的上限,所有用户都一样:

    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
    

    【讨论】:

    • 这不会解决目前所说的问题。在第二个示例中,User1 的上限为 13 美元,这将只返回一个行 (transaction1 ($10)),它应该返回两个 (transaction1 ($10) 和 transaction2 ($20))。
    • @Allan:不,它会返回两行,因为10&lt;13
    • 注意ts.transaction &lt; t.transaction 条件是为了让子查询返回“先验总数”。
    • 啊,我错过了使用两个小于的效果。聪明。
    • 聪明的解决方案。稍加调整就可以解决我的问题。非常感谢大家!!
    【解决方案3】:

    根据要求,这是一个 R 解决方案。我必须做出一些假设才能将它们组合在一起,它们是:

    1. 资金上限信息存储在单独的表中,并带有适当的键以连接到交易数据
    2. 如果用户的第一笔交易大于其资金上限,则不会为该用户返回任何行

    我对下面的代码进行了大量评论,但如果您有任何问题,请告诉我。我首先创建了一些代表您的数据的假数据,然后在最底部运行您需要的查询。

    您可以通过 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
    ...
    

    【讨论】:

      猜你喜欢
      • 2018-08-14
      • 2019-05-18
      • 1970-01-01
      • 2015-08-14
      • 2013-02-01
      • 2012-02-29
      • 2011-09-03
      • 2019-01-18
      • 1970-01-01
      相关资源
      最近更新 更多