SQL 从两个表中获取两个总和及其差异

SQL Getting two sums and their diff from two tables

我有一个商品系统的访问数据库。在这个数据库中有两个 tables:

table 购买

id  article  date        amount  price
1   341      2022-02-03  3       23
2   343      2022-02-04  5       18
3   343      2022-02-08  7       21
4   345      2022-02-17  3       12
5   348      2022-02-21  8       45
6   341      2022-03-02  5       27

table 销售额

id  article  date        amount  price
1   343      2022-02-23  3       28
2   341      2022-02-24  5       30
3   341      2022-03-03  2       35
4   345      2022-03-07  3       18

现在我想确定每篇文章产生了多少利润。这意味着已售商品的金额*价格之和减去购买商品的金额*价格之和。困难的部分是,如果我只卖出了 10 件商品但购买了 15 件商品,我将需要购买的 10 件商品的总和,而不是 15 件商品的总和。否则我的利润将是负数。

我知道如何使用“常规”编程语言通过遍历行来解决这个问题,但我想知道这是否可以通过单个 SQL 查询(例如内部查询或类似查询)来完成).

感谢任何帮助。

编辑: 以下是上述示例的预期结果:

id  article  amount  bought  sold  profit
1   343      3       54      84    30
2   341      7       177     220   43
3   345      3       36      54    18

试试这个:

SELECT 
    a.article, a.buy, b.sell, (a.sell-a.buy) profit 
FROM (
    SELECT 
        article, SUM(amount * price) buy
    FROM purchases
    GROUP BY article
) a
INNER JOIN (
    SELECT 
        article, SUM(amount * price) sell
    FROM sales
    GROUP BY article
) b ON a.article = b.article

我明白你的意思,我想编辑但被消息阻止 'Suggested edit queue is full'。

所以我想解释一下你真正想要的。

  • 您希望买入量不超过卖出量。 (例如:第 341 条已卖出数量为 7,买入数量为 8,您希望将买入和卖出商品的数量限制为 7)

  • 您想先卖先买的商品(先进先出)。 (例如:第 341 条已被买入 2 次,金额不同(第 3 * 23 次,第 5 * 27 次)。您要卖出第 7 笔订单金额:3 * 23 + 4 * 27,而不是此订单:5 * 27 + 2 * 23

select * into purchases
from (
select 1 id, 341 article, '2022-02-03' date, 3 amount, 23 price union all
select 2, 343, '2022-02-04', 5, 18 union all
select 3, 343, '2022-02-08', 7, 21 union all
select 4, 345, '2022-02-17', 3, 12 union all
select 5, 348, '2022-02-21', 8, 45 union all
select 6, 341, '2022-03-02', 5, 27
) t;

select * into sales
from (
select 1 id, 343 article, '2022-02-23' date, 3 amount, 28 price union all
select 2, 341, '2022-02-24', 5, 30 union all
select 3, 341, '2022-03-03', 2, 35 union all
select 4, 345, '2022-03-07', 3, 18
) t;

select * into range
from (
select 1 rnum union all
select 2 rnum union all
select 3 rnum union all
select 4 rnum union all
select 5 rnum union all
select 6 rnum union all
select 7 rnum union all
select 8 rnum union all
select 9 rnum union all
select 10 rnum
) t;

select min(s.id) id, s.article, count(s.article) amount,
       sum(p.price) bought, sum(s.price) sold,
       sum(s.price) - sum(p.price) profit
from
(
select row_number() over (partition by p.article order by p.id) seq,
       p.article, p.price
from   purchases p
       inner join range r
       on p.amount >= r.rnum
) p
inner join
(
select row_number() over (partition by s.article order by s.id) seq,
       s.id, s.article, s.price
from   sales s
       inner join range r
       on s.amount >= r.rnum
) s
on p.article = s.article and p.seq = s.seq
group by s.article
order by min(s.id);

结果:

id  article amount  bought  sold    profit
1   343 3   54  84  30
2   341 7   177 220 43
4   345 3   36  54  18

https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=96decd2f92a8f77964a929013e825774

这是SQL服务器解决方案。如您所知,MS Access 中没有 row_number 函数。因此,您需要创建新的 table 以生成具有自动编号类型的 seq 列,以在 MS Access 的情况下模仿 row_number。

如果这解决了您的问题并且您希望将其应用于 MS Access,请告诉我。


现在是 MS Access 的时候了。

我说你需要创建新的 table 但我错了。你不需要。

我创建了 RowNumber VBA 函数来模仿 row_number SQL 函数。

  1. 创建 Class 模块并将其命名为 CRowNumber 并粘贴以下代码。
Option Compare Database
Option Explicit

Private PartitionOld As String
Private RowNum As Integer
Private RowNums As New Dictionary

Public Table As String

Public Function RowNumber(Order As String, Partition As String) As Integer
    ' Order is used to prevent duplicated execution
    If RowNums.Exists(Order) Then
        ' Already executed before, so use cached value
        RowNum = RowNums(Order)
    Else
        ' If Partition changed, reset to 1, otherwise increase 1
        If (Partition <> PartitionOld) Then
            RowNum = 1
        Else
            RowNum = RowNum + 1
        End If
        
        'Debug.Print Table & "," & Order & ", " & Partition & "=" & PartitionOld & ":" & RowNum
    
        ' Cache for future use
        RowNums.Add Order, RowNum
    End If
    
    PartitionOld = Partition
    
    RowNumber = RowNum
End Function
  1. 创建模块并粘贴以下代码。
Option Compare Database
Option Explicit

' To use Dictionary:
' [Tools] - [References] - Check [Microsoft Scripting Runtime]
' To clear cache:
' RowNumber "", "", "", True
Public Function RowNumber(Table As String, Order As String, Partition As String, Optional Reset As Boolean) As Integer
    Dim rn As CRowNumber
    Static RowNumbers As New Dictionary
    
    
    If Reset Then
        Set RowNumbers = New Dictionary
        RowNumber = 0
        Exit Function
    End If
    
    If RowNumbers.Exists(Table) Then
        Set rn = RowNumbers.Item(Table)
    Else
        Set rn = New CRowNumber
        rn.Table = Table
        RowNumbers.Add Table, rn
    End If
        
    RowNumber = rn.RowNumber(Order, Partition)
End Function
  1. 创建查询并粘贴以下 SQL。
select min(s.id) as id, s.article, count(s.article) as amount,
       sum(p.price) as bought, sum(s.price) as sold,
       sum(s.price) - sum(p.price) as profit
from
(
select RowNumber('p', p.article & '.' & p.id & '.' & r.rnum, p.article) as seq,
       p.article, p.price
from   purchases as p
       inner join range as r
       on p.amount >= r.rnum
group by p.article, p.id, r.rnum, p.price
) as p
inner join
(
select RowNumber('s', s.article & '.'  & s.id & '.' & r.rnum, s.article) as seq,
       s.id, s.article, s.price
from   sales as s
       inner join range as r
       on s.amount >= r.rnum
group by s.article, s.id, r.rnum, s.price
) as s
on p.article = s.article and p.seq = s.seq
group by s.article
order by min(s.id);
  1. 我在代码中使用 Dictionary 来缓存之前的行号和 table,以使用 Dictionary
  • [工具] - [参考资料] - 检查 [Microsoft Scripting 运行time]
  1. 在 运行 查询之前,通过在立即 window 中调用 RowNumber 函数来清除缓存。
RowNumber "", "", "", True
  1. 运行查询

您将看到与 SQL 服务器版本相同的结果。

您总是需要在 运行 查询之前清除缓存,以便为新查询使用新缓存。

SQL 服务器和 MS Access 之间的区别是

  1. row_number 替换为 RowNumber VBA 函数

  2. SQL 服务器只需要 id, partition 进行编号, 但 VBA 功能需要更多: Table, articleidrnum 因为 VBA 函数无法更改行的顺序。

  3. order by 替换为 group by 以先获取子查询行然后连接,否则优化器将尝试先连接然后获取子查询行。

很抱歉,我对代码的解释不够,但如果您对代码有疑问,我会回答。