【问题标题】:SQL Server Stored Procedure for Menu Performance Report用于菜单性能报告的 SQL Server 存储过程
【发布时间】:2022-01-20 21:00:00
【问题描述】:

我的 SQL 数据库中有四个表,即 MenuItemsCategoriesInvoicesInvoiceDetails。现在我想要显示某个日期的菜单性能报告,即总数量和总金额 特定日期的每个菜单项。它在 where 子句中显示不带日期的所需结果,但不包括具有空值的菜单项。

这是我的存储过程:

CREATE PROCEDURE spGetMenuPerformanceByDay 
    @Date date, 
    @Terminal int
AS
BEGIN
    SELECT
        M.Name, 
        ISNULL(SUM(D.Amount), 0) AS Amount,
        ISNULL(SUM(D.Qty), 0) AS Qty
    FROM
        MenuItems AS M 
    JOIN
        Categories AS C ON C.Id = M.CategoryId  
    LEFT JOIN 
        InvoiceDetails AS D ON M.Id = D.ItemId 
    LEFT JOIN 
        Invoices I ON I.Id = d.InvoiceId
    WHERE
        @Terminal IN (I.TerminalId, C.TerminalId) 
        AND CONVERT(date, I.Time) = @Date 
        OR NULL IN (Amount, Qty) 
    GROUP BY
        M.Name, M.Id, D.ItemId
    ORDER BY
        (Qty) DESC
END

该存储过程在 where 子句中添加 Date 时返回的结果:

Item Amount Qty
KOFTA ANDA 1950 3
HOT N SOUR SOUP 550 1
CHICKEN CHOWMEIN 250 1
CHICKEN KORMA 850 1

我想要的结果是,但在 where 子句中添加 Date 时没有得到它:

Item Amount Qty
KOFTA ANDA 1950 3
HOT N SOUR SOUP 550 1
CHICKEN CHOWMEIN 250 1
CHICKEN KORMA 850 1
CRISPY CHICKEN 0 0
MEXICAN BURGER 0 0

【问题讨论】:

  • Null in (Amount,Qty) 永远不会是真的。测试列的唯一方法是 null 是 is null
  • 我不想测试,而是添加具有空值的行。问题在于在 where 子句中添加日期,排除具有空值的行。
  • 程序代码是高度特定于供应商的 - 所以请添加一个标签来指定您是否使用mysqlpostgresqlsql-serveroracledb2 - 或完全不同的东西。
  • 看起来你缺少OR 的括号,而且NULL IN ... 永远不会起作用,因为NULL 不等于包括它自己在内的任何东西。最重要的是,CONVERT(date 可能会导致性能问题。试试AND ((I.Time >= CAST(CAST(@Date AS date) AS datetime) AND I.Time < CAST(DATEADD(day, 1, CAST(@Date AS date) AS datetime))) OR Amount IS NULL OR Qty IS NULL)

标签: sql sql-server performance stored-procedures


【解决方案1】:

如果您不在 WHERE 子句中输入发票条件怎么办?

样本数据

create table Categories (
 Id int primary key, 
 Name varchar(30) not null,
 TerminalId int not null
);

create table MenuItems (
 Id int identity(21,1) primary key, 
 Name varchar(30) not null,
 CategoryId int not null, 
 foreign key (CategoryId) references Categories(Id)
);

create table Invoices (
 Id int identity(31,1) primary key,
 TerminalId int not null, 
 ItemId int not null, 
 Time datetime, 
 foreign key (ItemId) references MenuItems(Id)
);

create table InvoiceDetails (
 InvoiceDetailId int identity(41,1) primary key, 
 InvoiceId int,
 Amount decimal(10,2), 
 Qty int, 
 foreign key (InvoiceId) references Invoices(Id)
);

insert into Categories (Id, Name, TerminalId) values
(1,'KOFTA', 1), 
(2,'SOUP', 1), 
(3,'CHICKEN', 1), 
(4,'BURGER', 1);

insert into MenuItems (CategoryId, Name) values
(1,'KOFTA ANDA'), 
(2,'HOT N SOUR SOUP'), 
(3,'CHICKEN CHOWMEIN'), 
(3,'CHICKEN KORMA'), 
(3,'CRISPY CHICKEN'), 
(4,'MEXICAN BURGER');

insert into Invoices (ItemId, TerminalId, Time)
select itm.Id, cat.TerminalId, GetDate() as Time
from MenuItems itm
join Categories cat on cat.Id = itm.CategoryId
where itm.Name in (       
'KOFTA ANDA', 
'HOT N SOUR SOUP', 
'CHICKEN CHOWMEIN', 
'CHICKEN KORMA'
);

insert into InvoiceDetails (InvoiceId, Amount, Qty) values
(31, 1950, 3),
(32, 550, 1),
(33, 250, 1),
(34, 850, 1);

查询

DECLARE @TerminalId INT = 1;
DECLARE @Date DATE = GetDate();

SELECT
 V.[Date], 
 C.Name AS Category, 
 M.Name AS MenuItemName, 
 ISNULL(SUM(D.Amount), 0) AS Amount,
 ISNULL(SUM(D.Qty), 0) AS Qty
FROM Categories AS C 
CROSS JOIN (SELECT @Date AS [Date], @TerminalId AS TerminalId) V
JOIN MenuItems AS M 
ON M.CategoryId = C.Id
LEFT JOIN Invoices I
  ON I.ItemId = M.Id
 AND I.TerminalId = V.TerminalId
 AND CAST(I.Time AS DATE) = V.[Date]
LEFT JOIN InvoiceDetails AS D 
  ON D.InvoiceId = I.Id
WHERE C.TerminalId = V.TerminalId
GROUP BY V.[Date], C.Id, M.Id, C.Name, M.Name
ORDER BY SUM(D.Qty) DESC
Date Category MenuItemName Amount Qty
2021-12-18 KOFTA KOFTA ANDA 1950.00 3
2021-12-18 SOUP HOT N SOUR SOUP 550.00 1
2021-12-18 CHICKEN CHICKEN CHOWMEIN 250.00 1
2021-12-18 CHICKEN CHICKEN KORMA 850.00 1
2021-12-18 CHICKEN CRISPY CHICKEN 0.00 0
2021-12-18 BURGER MEXICAN BURGER 0.00 0

dbfiddle here

上的演示

【讨论】:

  • 问题是仅包含在发票表中的日期。我需要特定日期的菜单项报告
  • 但是你不想排除不匹配的?那不是很好吗?
  • 但它会显示整体报告,而不是特定日期。如果我需要一个月、一周或一天的菜单报告性能怎么办
  • 那么可能是这样的。
  • @DawoodJamil 如果您需要更多日期或范围,那么您可以加入日历表。 Here's an example of how to create one.
【解决方案2】:

这是我对你的目标的破解。注意变化。我发现 Category 表中对 TerminalId 的引用非常可疑 - 以至于我怀疑这是模型缺陷。沿着这些思路,我注意到 TerminalId 应该有一个外键来指向终端的缺失表。所以我忽略了。

这样,对 Category 的引用现在就无关紧要了。所以这也被删除了。我还更改了程序名称,因为我发现对“day”的引用具有误导性。由于零售(尤其是食品服务)销售额在一周中的每一天都在不断变化,因此很有可能会以“天”为基础评估“菜单性能”。所以我们不要误导任何人认为这就是这个过程的作用。

为了简单明了,我删除了 ISNULL 用法。如果需要,可以将其添加回来,但结果集的使用者通常会更好地处理这些事情。我将 ORDER BY 子句作为存根留给您重新评估(您需要)。

那么这是如何工作的呢?只需直接在 CTE 中计算总和,然后从菜单项外连接到 CTE 总和,以获得所有菜单项以及指定日期的相关性能信息。

CREATE PROCEDURE dbo.GetMenuPerformanceByDate
    @Date date, 
    @Terminal int
AS
BEGIN 
    with sales as (
       select det.ItemId, SUM(det.Amount) as amt, SUM(det.Qty) as qty 
         from dbo.Invoices as inv
        inner join dbo.InvoiceDetails as det 
           on inv.Id = det.InvoiceId
       where cast(inv.Time as date) = @Date 
         and inv.TerminalId = @Terminal 
       group by det.ItemId 
    )
    select menu.name, sales.amt, sales.qty 
      from dbo.MenuItems as menu
      left join sales 
        on menu.Id = sles.ItemId 
    order by ...
    ;
END;

最后一点。这个过滤器:

cast(inv.Time as date) = @Date 

通常不是过滤日期时间列的好方法。使用包容性的下界和独占的上界要好得多,例如:

inv.Time >= @date and inv.Time < dateadd(day, 1, @date)

this reason

我的最后一点 - MenuItems 存在潜在缺陷。大概“名字”是独一无二的。多行具有相同名称的可能性极小,但“不太可能”不是限制。如果您根据名称生成行并且名称结果不是唯一的,则您的结果难以解释。

【讨论】:

  • 非常感谢。这正是我一直在寻找的......其次只是补充一下,终端指的是一个分支。类别表的 terminalId 为 FK,以防不同的分支有不同类别的食物。
  • 我添加了类别,因为我想根据日期和终端进行报告。
  • 在您的解决方案中添加类别返回时,它工作正常。因为在我的情况下,不同终端的类别不同,并且没有添加类别,它混合了所有终端的菜单项。再次非常感谢。我感谢你的努力。它解决了我的问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-03-12
  • 2015-04-18
  • 1970-01-01
  • 1970-01-01
  • 2016-04-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多