简体   繁体   English

Oracle-如何编写此SQL?

[英]Oracle - How to write this SQL?

In Oracle I have a table recording users' transactions like the one below. 在Oracle中,我有一张表记录用户的交易,如下表所示。 The user column isn't needed for the target query, just list here for reference. 目标查询不需要用户列,只需在此处列出以供参考。

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

For an given user, there will be a money cap, and I need to find out the minimal rows whose sum of money >= the given money cap, or all rows belonging to that user if the money cap is larger than the sum. 对于给定的用户,将有一个货币限额,并且我需要找出货币总和> =给定货币限额的最小行,或者如果货币限额大于总和,则找出属于该用户的所有行。 The rows that are returned must be sorted by transaction in ascending order. 返回的行必须按事务按升序排序。

For example, for user1, the given money cap is $30. 例如,对于user1,给定的资金上限为$ 30。 Then row1 and row2 must be returned. 然后必须返回row1和row2。 You cant return row4 as we must follow transaction order. 您无法返回row4,因为我们必须遵循交易顺序。 If given cap is $13, row1 and row2 must be returned since row1 isn't enough to cover $13. 如果给定的上限是$ 13,则必须返回row1和row2,因为row1不足以支付$ 13。 If given cap is $136 then row1/2/3/4 are returned since $10+$20+$5+$100 is smaller than $136. 如果给定的上限是$ 136,则返回第1/2/3/4行,因为$ 10 + $ 20 + $ 5 + $ 100小于$ 136。

With cursor we can use a stored procedure to solve this, but I cant find an elegant way to use some nested queries with sum achieve this. 使用游标,我们可以使用存储过程来解决此问题,但是我找不到一种优雅的方法来使用一些嵌套查询和sum来实现此目的。 Will really appreciate your help! 会非常感谢您的帮助!

You can use analytic functions to do this fairly easily: 您可以使用分析功能轻松完成此操作:

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

Using SUM in this way provides the total of the current row plus all previous rows, according to the ORDER BY clause (in this case, the row's transaction and all transactions with lower IDs) where the column specified by the PARTITION BY clause is the same. 按照ORDER BY子句(在这种情况下,该行的事务和所有具有较低ID的事务),以这种方式使用SUM可以提供当前行加上所有先前的行的总和,其中PARTITION BY子句指定的列相同。


Taking a second look at the question, I realized that this would not work, since it would only return values less that the value you are looking for, rather than including the value that hits that point. 再看一下问题,我意识到这是行不通的,因为它只会返回小于您要查找的值的值,而不是包含达到该点的值。 The following revision returns the current row if the previous row is less than the target total. 如果上一行小于目标总数,则以下修订版将返回当前行。

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

For a specific cap, same for all users: 对于特定的上限,所有用户均应相同:

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

As requested, here's an R solution. 根据要求,这是一个R解决方案。 I had to make a few assumptions to put this together, and here they are: 我不得不做出一些假设,将它们放在一起,在这里是:

  1. The money cap information is stored in a separate table with an appropriate key to join on to the transaction data 资金限额信息存储在单独的表中,带有适当的密钥以加入交易数据
  2. If a user's first transaction is greater than their money cap, then no rows are returned for that user 如果用户的第一笔交易大于其资金限额,则不会为该用户返回任何行

I commented the code below pretty heavily, but let me know if you have any questions. 我在下面的代码中发表了很多评论,但是如果您有任何疑问,请告诉我。 I first created some fake data which represents your data, then run the query you need at the very bottom. 我首先创建了一些代表您的数据的假数据,然后在最底部运行您需要的查询。

You can look to interfacing your database with R through the RODBC package. 您可以考虑通过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
...

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM