在 Group By 查询中包含缺失的年份
Include missing years in Group By query
我是 Access 和 SQL 编程的新手。我正在尝试执行以下操作:
Sum(SO_SalesOrderPaymentHistoryLineT.Amount) AS [Sum Of PaymentPerYear]
并按年份分组,即使某些年份没有金额。我想将这些年也列出来作为一份带有图表的报告。我不确定这是否可行,但感谢您的每一点帮助。
到目前为止我的代码如下:
SELECT
Base_CustomerT.SalesRep,
SO_SalesOrderT.CustomerId,
Base_CustomerT.Customer,
SO_SalesOrderPaymentHistoryLineT.DatePaid,
Sum(SO_SalesOrderPaymentHistoryLineT.Amount) AS [Sum Of PaymentPerYear]
FROM
Base_CustomerT
INNER JOIN (
SO_SalesOrderPaymentHistoryLineT
INNER JOIN SO_SalesOrderT
ON SO_SalesOrderPaymentHistoryLineT.SalesOrderId = SO_SalesOrderT.SalesOrderId
) ON Base_CustomerT.CustomerId = SO_SalesOrderT.CustomerId
GROUP BY
Base_CustomerT.SalesRep,
SO_SalesOrderT.CustomerId,
Base_CustomerT.Customer,
SO_SalesOrderPaymentHistoryLineT.DatePaid,
SO_SalesOrderPaymentHistoryLineT.PaymentType,
Base_CustomerT.IsActive
HAVING
(((SO_SalesOrderPaymentHistoryLineT.PaymentType)=1)
AND ((Base_CustomerT.IsActive)=Yes))
ORDER BY
Base_CustomerT.SalesRep,
Base_CustomerT.Customer;
您需要另一个 table 并列出所有年份 -- 您可以即时创建它或在数据库中创建一个...从中加入。因此,如果您有一个名为 alltheyears 的 table,其中一个名为 y 的列刚刚列出了年份,那么您可以使用如下代码:
WITH minmax as
(
select min(year(SO_SalesOrderPaymentHistoryLineT.DatePaid) as minyear,
max(year(SO_SalesOrderPaymentHistoryLineT.DatePaid) as maxyear)
from SalesOrderPaymentHistoryLineT
), yearsused as
(
select y
from alltheyears, minmax
where alltheyears.y >= minyear and alltheyears.y <= maxyear
)
select *
from yearsused
join ( -- your query above goes here! -- ) T
ON year(T.SO_SalesOrderPaymentHistoryLineT.DatePaid) = yearsused.y
您需要一个能够提供年份数字的数据源。你不能凭空制造它们。假设你有一个 table Interesting_year
,只有一个列 year
,填充了 2000 到 2050 之间的每个不同的整数,你可以这样做:
SELECT
base.SalesRep,
base.CustomerId,
base.Customer,
base.year,
Sum(NZ(data.Amount)) AS [Sum Of PaymentPerYear]
FROM
(SELECT * FROM Base_CustomerT INNER JOIN Year) AS base
LEFT JOIN
(SELECT * FROM
SO_SalesOrderT
INNER JOIN SO_SalesOrderPaymentHistoryLineT
ON (SO_SalesOrderPaymentHistoryLineT.SalesOrderId = SO_SalesOrderT.SalesOrderId)
) AS data
ON ((base.CustomerId = data.CustomerId)
AND (base.year = Year(data.DatePaid))),
WHERE
(data.PaymentType = 1)
AND (base.IsActive = Yes)
AND (base.year BETWEEN
(SELECT Min(year(DatePaid) FROM SO_SalesOrderPaymentHistoryLineT)
AND (SELECT Max(year(DatePaid) FROM SO_SalesOrderPaymentHistoryLineT))
GROUP BY
base.SalesRep,
base.CustomerId,
base.Customer,
base.year,
ORDER BY
base.SalesRep,
base.Customer;
注意以下几点:
- 修改后的查询首先形成
BaseCustomerT
与 Interesting_year
的笛卡尔积,以便获得与每年相关联的基本客户数据(有时称为 CROSS JOIN
,但它是与没有连接谓词的 INNER JOIN
相同,这是 Access 所要求的)
- 为了获得没有付款的年份的结果行,您必须执行外部联接(在本例中为
LEFT JOIN
)。如果(基本客户,年份)组合没有关联订单,则连接结果的其余列将为 NULL
.
- 我从 Base_CustomerT 中选择 CustomerId,因为如果您从
SO_SalesOrderT
中选择有时会得到 NULL
,就像在开始查询中那样
- 我正在使用 Access
Nz()
函数将 NULL
付款金额转换为 0(来自与没有付款的年份对应的行)
- 我将您的
HAVING
子句转换为 WHERE
子句。在这种特殊情况下,这在语义上是等效的,并且会更有效,因为 在 组形成之前应用了 WHERE
过滤器,并且因为它允许从中省略某些列GROUP BY
子句。
- 按照 Hogan 的示例,我过滤掉了您的数据涵盖的总体范围之外的年份数据。或者,通过确保 table
Intersting_year
仅包含您想要结果的年份数字,您可以在没有该过滤条件及其子查询的情况下实现相同的效果。
更新:将查询修改为不同但逻辑上等效的 "something like this",我希望 Access 会更好。除了添加一堆括号外,主要区别在于将 LEFT JOIN
的左右操作数都变成了子查询。这与解决 Access "ambiguous outer join" 错误的共识建议一致。
谢谢约翰的帮助。我找到了适合我的解决方案。它看起来很不同,但我从中学到了很多东西。如果你感兴趣,这里是它现在的样子。
SELECT DISTINCTROW
Base_Customer_RevenueYearQ.SalesRep,
Base_Customer_RevenueYearQ.CustomerId,
Base_Customer_RevenueYearQ.Customer,
Base_Customer_RevenueYearQ.RevenueYear,
CustomerPaymentPerYearQ.[Sum Of PaymentPerYear]
FROM
Base_Customer_RevenueYearQ
LEFT JOIN CustomerPaymentPerYearQ
ON (Base_Customer_RevenueYearQ.RevenueYear = CustomerPaymentPerYearQ.[RevenueYear])
AND (Base_Customer_RevenueYearQ.CustomerId = CustomerPaymentPerYearQ.CustomerId)
GROUP BY
Base_Customer_RevenueYearQ.SalesRep,
Base_Customer_RevenueYearQ.CustomerId,
Base_Customer_RevenueYearQ.Customer,
Base_Customer_RevenueYearQ.RevenueYear,
CustomerPaymentPerYearQ.[Sum Of PaymentPerYear]
;
我是 Access 和 SQL 编程的新手。我正在尝试执行以下操作:
Sum(SO_SalesOrderPaymentHistoryLineT.Amount) AS [Sum Of PaymentPerYear]
并按年份分组,即使某些年份没有金额。我想将这些年也列出来作为一份带有图表的报告。我不确定这是否可行,但感谢您的每一点帮助。
到目前为止我的代码如下:
SELECT
Base_CustomerT.SalesRep,
SO_SalesOrderT.CustomerId,
Base_CustomerT.Customer,
SO_SalesOrderPaymentHistoryLineT.DatePaid,
Sum(SO_SalesOrderPaymentHistoryLineT.Amount) AS [Sum Of PaymentPerYear]
FROM
Base_CustomerT
INNER JOIN (
SO_SalesOrderPaymentHistoryLineT
INNER JOIN SO_SalesOrderT
ON SO_SalesOrderPaymentHistoryLineT.SalesOrderId = SO_SalesOrderT.SalesOrderId
) ON Base_CustomerT.CustomerId = SO_SalesOrderT.CustomerId
GROUP BY
Base_CustomerT.SalesRep,
SO_SalesOrderT.CustomerId,
Base_CustomerT.Customer,
SO_SalesOrderPaymentHistoryLineT.DatePaid,
SO_SalesOrderPaymentHistoryLineT.PaymentType,
Base_CustomerT.IsActive
HAVING
(((SO_SalesOrderPaymentHistoryLineT.PaymentType)=1)
AND ((Base_CustomerT.IsActive)=Yes))
ORDER BY
Base_CustomerT.SalesRep,
Base_CustomerT.Customer;
您需要另一个 table 并列出所有年份 -- 您可以即时创建它或在数据库中创建一个...从中加入。因此,如果您有一个名为 alltheyears 的 table,其中一个名为 y 的列刚刚列出了年份,那么您可以使用如下代码:
WITH minmax as
(
select min(year(SO_SalesOrderPaymentHistoryLineT.DatePaid) as minyear,
max(year(SO_SalesOrderPaymentHistoryLineT.DatePaid) as maxyear)
from SalesOrderPaymentHistoryLineT
), yearsused as
(
select y
from alltheyears, minmax
where alltheyears.y >= minyear and alltheyears.y <= maxyear
)
select *
from yearsused
join ( -- your query above goes here! -- ) T
ON year(T.SO_SalesOrderPaymentHistoryLineT.DatePaid) = yearsused.y
您需要一个能够提供年份数字的数据源。你不能凭空制造它们。假设你有一个 table Interesting_year
,只有一个列 year
,填充了 2000 到 2050 之间的每个不同的整数,你可以这样做:
SELECT
base.SalesRep,
base.CustomerId,
base.Customer,
base.year,
Sum(NZ(data.Amount)) AS [Sum Of PaymentPerYear]
FROM
(SELECT * FROM Base_CustomerT INNER JOIN Year) AS base
LEFT JOIN
(SELECT * FROM
SO_SalesOrderT
INNER JOIN SO_SalesOrderPaymentHistoryLineT
ON (SO_SalesOrderPaymentHistoryLineT.SalesOrderId = SO_SalesOrderT.SalesOrderId)
) AS data
ON ((base.CustomerId = data.CustomerId)
AND (base.year = Year(data.DatePaid))),
WHERE
(data.PaymentType = 1)
AND (base.IsActive = Yes)
AND (base.year BETWEEN
(SELECT Min(year(DatePaid) FROM SO_SalesOrderPaymentHistoryLineT)
AND (SELECT Max(year(DatePaid) FROM SO_SalesOrderPaymentHistoryLineT))
GROUP BY
base.SalesRep,
base.CustomerId,
base.Customer,
base.year,
ORDER BY
base.SalesRep,
base.Customer;
注意以下几点:
- 修改后的查询首先形成
BaseCustomerT
与Interesting_year
的笛卡尔积,以便获得与每年相关联的基本客户数据(有时称为CROSS JOIN
,但它是与没有连接谓词的INNER JOIN
相同,这是 Access 所要求的) - 为了获得没有付款的年份的结果行,您必须执行外部联接(在本例中为
LEFT JOIN
)。如果(基本客户,年份)组合没有关联订单,则连接结果的其余列将为NULL
. - 我从 Base_CustomerT 中选择 CustomerId,因为如果您从
SO_SalesOrderT
中选择有时会得到NULL
,就像在开始查询中那样 - 我正在使用 Access
Nz()
函数将NULL
付款金额转换为 0(来自与没有付款的年份对应的行) - 我将您的
HAVING
子句转换为WHERE
子句。在这种特殊情况下,这在语义上是等效的,并且会更有效,因为 在 组形成之前应用了WHERE
过滤器,并且因为它允许从中省略某些列GROUP BY
子句。 - 按照 Hogan 的示例,我过滤掉了您的数据涵盖的总体范围之外的年份数据。或者,通过确保 table
Intersting_year
仅包含您想要结果的年份数字,您可以在没有该过滤条件及其子查询的情况下实现相同的效果。
更新:将查询修改为不同但逻辑上等效的 "something like this",我希望 Access 会更好。除了添加一堆括号外,主要区别在于将 LEFT JOIN
的左右操作数都变成了子查询。这与解决 Access "ambiguous outer join" 错误的共识建议一致。
谢谢约翰的帮助。我找到了适合我的解决方案。它看起来很不同,但我从中学到了很多东西。如果你感兴趣,这里是它现在的样子。
SELECT DISTINCTROW
Base_Customer_RevenueYearQ.SalesRep,
Base_Customer_RevenueYearQ.CustomerId,
Base_Customer_RevenueYearQ.Customer,
Base_Customer_RevenueYearQ.RevenueYear,
CustomerPaymentPerYearQ.[Sum Of PaymentPerYear]
FROM
Base_Customer_RevenueYearQ
LEFT JOIN CustomerPaymentPerYearQ
ON (Base_Customer_RevenueYearQ.RevenueYear = CustomerPaymentPerYearQ.[RevenueYear])
AND (Base_Customer_RevenueYearQ.CustomerId = CustomerPaymentPerYearQ.CustomerId)
GROUP BY
Base_Customer_RevenueYearQ.SalesRep,
Base_Customer_RevenueYearQ.CustomerId,
Base_Customer_RevenueYearQ.Customer,
Base_Customer_RevenueYearQ.RevenueYear,
CustomerPaymentPerYearQ.[Sum Of PaymentPerYear]
;