簡體   English   中英

SQL 服務器循環內部存儲過程

[英]SQL Server looping through inside stored procedure

我有一個與產品相關的 C# windows 應用程序。 在我的應用程序中,我們獲取產品添加到庫存的日期。 根據產品的AddDate ,我們得到產品的年齡(以月為單位)。

假設產品的使用期限為 25 個月。

int Age = 25;

for(int i = Age; i >=0; i --)
{
    var result = GetProductData(DateTime.Now.AddMonth(0-i));
} 

GetProductData()方法調用存儲過程,因此如果產品的使用期限為 25 個月,則存儲過程將被調用 25 次。

在存儲過程中,我們從DateTime中提取月份和年份部分,並將這些位存儲到 2 個單獨的變量中。 這是目前的情況

CREATE PROCEDURE usp_GetProductData
     @AppId INT,
    @Date DATETIME
AS
BEGIN
    DECLARE @Month INT
    DECLARE @Year INT
    DECLARE @ProductInstall INT

    SELECT @Month = SELECT DATEPART(m, @Date)
    SELECT @Year = SELECT YEAR(@Date)

    SELECT @ProductInstall = (SUM(P.[AutoInstalls]) + SUM(P.[ITInstalls]))
    FROM dbo.[ProductInstalls] P 
    INNER JOIN [User] U ON U.[UserId] = P.[UserId] 
    WHERE LicenseRequired = 1 
      AND DATEPART(m, P.[InstallDate]) = @month 
      AND DATEPART(year, P.[InstallDate]) = @Year

    SELECT 
        AVG(A.[TotalRequests] - A.[TotalInstalls]) * 100 AS [ProductAverage], 
        @ProductInstall, @Month/@Year 
    FROM 
        dbo.[ApplicationInstalls]
    /*There are few more joins and some business logic after this */
    WHERE 
        DATEPART(m, A.[InstallDate]) = @Month 
        AND DATEPART(year, A.[InstallDate]) = @Year
END

現在,我不想像產品/應用程序的年齡一樣多次調用存儲過程,而是想在一個請求中執行它,因為我已經知道應用程序/產品添加到庫存中的日期

DECLARE @ProductAddDate
DECLARE @ProductAge
SELECT @ProductAddDate = [DateAdded] FROM dbo.[Application] WHERE [AppId] = @AppId
SELECT @ProductAge = DATEDIFF(DAY, @ProductAddDate, GETDATE())/30

現在隨着我擁有的產品年齡,我想每個月循環通過以下邏輯。

 SELECT 
     @ProductInstall = (SUM(P.[AutoInstalls]) + SUM(P.[ITInstalls]))
 FROM 
     dbo.[ProductInstalls] P 
 INNER JOIN
     [User] U ON U.[UserId] = P.[UserId] 
 WHERE 
     LicenseRequired = 1 
     AND DATEPART(m, P.[InstallDate]) = @month 
     AND DATEPART(year, P.[InstallDate]) = @Year

 SELECT 
     AVG(A.[TotalRequests] - A.[TotalInstalls]) * 100 AS [ProductAverage], @ProductInstall, 
     @Month/@Year 
 FROM 
     dbo.[ApplicationInstalls]
     /*There are few more joins and some business logic after this */
 WHERE 
     DATEPART(m, A.[InstallDate]) = @Month 
     AND DATEPART(year, A.[InstallDate]) = @Year

不確定您的核心邏輯是否真的代表“應用程序”數據或產品數據,但我會將 proc 名稱保留為“getProductData”。 這是它的作用:

  • 首先,在“monthYears”CTE 中,我獲取應用程序的 product-add-date 並創建從添加日期到當前日期的月份和年份 bin。 這是我唯一的“循環”。 否則,更健康的方法是使用 SQL 服務器中可用的集合論操作,這樣效率更高。
  • 然后,在“productInstalls”CTE 中,我采用您的邏輯,省略月份和年份過濾器,而是按月份和年份分組以一次性獲取它們。
  • 我對“applicationInstalls”CTE 中的應用程序安裝邏輯執行相同操作。
  • 最后,我在核心查詢中按年和月將它們全部連接在一起。

這是代碼:

create procedure getProductData
    @AppId int 
as 

declare @ProductAddDate date = (
    select  dateAdded 
    from    [application] 
    where   appId = @AppId
);

with

    monthYears as (

        select      mo = datepart(m, @productAddDate),
                    yr = datepart(year, @productAddDate),
                    i = 0

        union all
        select      mo = datepart(m, dateAdd(m, i+1, @productAddDate)),
                    yr = datepart(year, dateAdd(m, i+1, @productAddDate)),
                    i = i+1
        from        monthYears
        where       datepart(m, dateAdd(m, i+1, @productAddDate)) <= datepart(m, getdate())
        and         datepart(year, dateAdd(m, i+1, @productAddDate)) <= datepart(year, getdate())
                
    ),

    productInstalls as (

        select      mo = datepart(m, p.installDate), 
                    yr = datepart(year, p.installDate), 
                    ProductInstall = sum(p.autoinstalls) + sum(p.itinstalls)
        from        productInstalls p 
        join        [user] u on u.userId = p.userId 
        where       licenseRequired = 1 
        group by    datepart(m, p.installDate),
                    datepart(year, p.installDate)

    ),

    applicationInstalls as (

        select      mo = datepart(m, a.installDate), 
                    yr = datepart(year, a.installDate), 
                    ProductAverage = avg(a.totalRequests - a.totalInstalls) * 100
        from        applicationInstalls a
        group by    datepart(m, p.installDate),
                    datepart(year, p.InstallDate)

    )

    select      my.yr,
                my.mo,
                ProductAverage = isnull(a.ProductAverage, 0),
                ProductInstall = isnull(p.ProductInstall, 0)
    from        monthYears my
    left join   applicationInstalls a on my.yr = a.yr and my.mo = a.mo
    left join   productInstalls p on my.yr = p.yr and my.mo = p.mo;

當你在 C# 中得到這個時,它將作為一個集合。 最有可能的是 output 作為 DataTable 或者您的業務層可能是 output 它作為 IEnumerable 或某種類型的列表。 因此,如有必要,您的循環將發生在 C# 代碼中,而不是 SQL 代碼中。

就像是:

int appId = 0;  // or whatever

foreach(var productDatum in GetProductData(appId)) {
    // do something with productDatum
} 

我本身沒有“答案”,但我確實有一個可能值得深入研究的想法。 在 SQL 服務器中,有 Window 函數的概念。 幾年前,Itsak Ben-Gan 寫了一本書,但是你可以通過谷歌搜索找到它——這里有一個網站

這里的缺點是你必須開始以不同的方式思考才能以這種方式編程。 在適用的情況下,專業人士沒有遞歸。 window 在單次掃描中向下流過表,在它向下時收集聚合。 幾年前,我用它來確定大型應用程序中的調度沖突,將返回結果的時間從 10 - 20 秒減少到約 0.5 秒。 您可能不需要這種性能提升,但如果您能弄清楚如何應用到您的問題,您就可以避免大量的表(或索引)掃描。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM