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 函数。
- 创建 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
- 创建模块并粘贴以下代码。
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
- 创建查询并粘贴以下 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);
- 我在代码中使用 Dictionary 来缓存之前的行号和 table,以使用 Dictionary
- [工具] - [参考资料] - 检查 [Microsoft Scripting 运行time]
- 在 运行 查询之前,通过在立即 window 中调用 RowNumber 函数来清除缓存。
RowNumber "", "", "", True
- 运行查询
您将看到与 SQL 服务器版本相同的结果。
您总是需要在 运行 查询之前清除缓存,以便为新查询使用新缓存。
SQL 服务器和 MS Access 之间的区别是
row_number
替换为 RowNumber
VBA 函数
SQL 服务器只需要 id
, partition
进行编号, 但 VBA 功能需要更多: Table
, article
、id
、rnum
因为 VBA 函数无法更改行的顺序。
order by
替换为 group by
以先获取子查询行然后连接,否则优化器将尝试先连接然后获取子查询行。
很抱歉,我对代码的解释不够,但如果您对代码有疑问,我会回答。
我有一个商品系统的访问数据库。在这个数据库中有两个 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 函数。
- 创建 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
- 创建模块并粘贴以下代码。
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
- 创建查询并粘贴以下 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);
- 我在代码中使用 Dictionary 来缓存之前的行号和 table,以使用 Dictionary
- [工具] - [参考资料] - 检查 [Microsoft Scripting 运行time]
- 在 运行 查询之前,通过在立即 window 中调用 RowNumber 函数来清除缓存。
RowNumber "", "", "", True
- 运行查询
您将看到与 SQL 服务器版本相同的结果。
您总是需要在 运行 查询之前清除缓存,以便为新查询使用新缓存。
SQL 服务器和 MS Access 之间的区别是
row_number
替换为RowNumber
VBA 函数SQL 服务器只需要
id
,partition
进行编号, 但 VBA 功能需要更多:Table
,article
、id
、rnum
因为 VBA 函数无法更改行的顺序。order by
替换为group by
以先获取子查询行然后连接,否则优化器将尝试先连接然后获取子查询行。
很抱歉,我对代码的解释不够,但如果您对代码有疑问,我会回答。