我可以将此 C# 逻辑转换为 SQL SERVER 存储过程吗?

Can i convert this C# logic into SQL SERVER stored procedure?

大家好,

我有这个用 C# 编写的方法,它主要是在数据库中搜索寄存器,如果它是可重复的 (isRepeatable = true),它会递增并再次将自身插入到具有不同日期的列表中,但是具有相同的 Id 和 Name 属性。我也指定了重复类型(每日、每周、每月或每年),它将持续到达到 RepeatingEndDate 中指定的值或我在方法中指定的日期,因此它不会无限循环通过可重复的寄存器没有指定 RepeatingEndDate。

总而言之,如果我的数据库中有这样的寄存器:

Id: 1
Name: Foo
Date: 03/10/2014
IsRepeatable: true
RepetitionType: 3 (Monthly)
RepeatingEndDate 05/10/2014

这将是我的 C# 方法将从该单个寄存器输出的寄存器列表:

Id: 1
Name: Foo
Date: 03/10/2014
IsRepeatable: true
RepetitionType: 3 (Monthly)
RepeatingEndDate 05/10/2014


Id: 1
Name: Foo
Date: 04/10/2014
IsRepeatable: true
RepetitionType: 3 (Monthly)
RepeatingEndDate 05/10/2014


Id: 1
Name: Foo
Date: 05/10/2014
IsRepeatable: true
RepetitionType: 3 (Monthly)
RepeatingEndDate 05/10/2014

请注意,除日期之外的所有属性都是相同的,因为我正在创建具有所有相同属性的新对象,但考虑到我想要的重复并仅设置日期。我的目标是在不保存多个数据的情况下处理可重复的数据,在我的实际情况下,让应用程序处理重复。

但我发现这在某些时候确实 CPU 很激烈,我想将其全部转换为 SQL 服务器中的存储过程。

我的问题是:是否可以将所有这些 C# 逻辑转换为 SQL SERVER 存储过程,并在我的应用程序中将此过程作为寄存器列表使用?如果是这样,我的结果基本上会得到一个寄存器列表,有些可能具有相同的 ID、名称等,但日期不同,具体取决于它的重复。

编辑

纯代码在这里:https://codereview.stackexchange.com/questions/83777/refactoring-a-loop-through-repetitive-registers

我怀疑 SQL 服务器是否可以比 c# 更高效、更轻松地完成。

给出以下 class:

public class Record
{
    public int Id { get; set;} 
    public string Name {get; set;}

    public DateTime Date {get; set;}

    public bool IsRepeatable {get; set;}

    public int RepetitionType {get; set;}
    public DateTime? RepeatingEndDate { get; set; }
}

您可以使用 ExpandRepetitions 扩展方法扩展重复:

public static class RecordExtensions
{

    private static Func<DateTime, DateTime>[] PeriodIncrementers = new Func<DateTime, DateTime>[]
    {
        (date) => date, // RepetitionType = 0
        (date) => date.AddDays(1), // RepetitionType = 1 (daily)
        (date) => date.AddDays(7), // RepetitionType = 2 (weekly)
        (date) => date.AddMonths(1), // RepetitionType = 3 (monthy)
        (date) => date.AddMonths(3), // RepetitionType = 4 (quarterly)
        (date) => date.AddMonths(6), // RepetitionType = 5 (semiannually)
        (date) => date.AddYears(1), // RepetitionType = 6 (annually)
        (date) => date.AddYears(2), // RepetitionType = 7 (biannually)
    };

    private static Func<DateTime, DateTime>[] DefaultDateLimiters = new Func<DateTime, DateTime>[]
    {
        (date) => date, // RepetitionType = 0
        (date) => (new DateTime(date.Year, date.Month, 1)).AddMonths(1).AddDays(-1), // RepetitionType = 1 (daily). Limit: last day of month
        (date) => date.AddDays(7 * 10 ), // RepetitionType = 2 (weekly). Limit: 10 weeks
        (date) => date.AddYears(1), // RepetitionType = 3 (monthy). Limit: 1 year
        (date) => date.AddYears(2), // RepetitionType = 4 (quarterly). Limit:  2 year
        (date) => date.AddYears(4), // RepetitionType = 5 (semiannually). Limit: 4 years 
        (date) => date.AddYears(8), // RepetitionType = 6 (annually). Limit: 8 years
        (date) => date.AddYears(16), // RepetitionType = 7 (biannually). Limit: 16 years

    };

    public static IEnumerable<Record> ExpandRepetitions(this IEnumerable<Record> records, DateTime? fromDate, DateTime? toDate)
    {
        var concatenation = Enumerable.Empty<Record>();
        foreach (var record in records)
        {
            concatenation = concatenation.Concat(ExpandRepetition(record, fromDate, toDate));
        }
        return concatenation;
    }

    private static IEnumerable<Record> ExpandRepetition(Record record, DateTime? fromDate, DateTime? toDate)
    {
        if ((fromDate == null || fromDate.Value <= record.Date) && (toDate == null || toDate.Value >= record.Date))
        {
            yield return record;
        }
        var previousRecord = record;
        DateTime endDate = record.RepeatingEndDate == null ? DefaultDateLimiters[record.RepetitionType](record.Date) : record.RepeatingEndDate.Value;
        if (toDate.HasValue && toDate.Value < endDate) endDate = toDate.Value;

        var incrementer = PeriodIncrementers[record.RepetitionType];
        if (record.IsRepeatable)
        {
            DateTime date = incrementer(previousRecord.Date);
            while (date <= endDate )
            {
                if (fromDate == null || fromDate.Value <= date)
                {
                    var newRecord = new Record
                    {
                        Date = date,
                        IsRepeatable = previousRecord.IsRepeatable,
                        Name = previousRecord.Name,
                        RepeatingEndDate = previousRecord.RepeatingEndDate,
                        RepetitionType = previousRecord.RepetitionType
                    };
                    previousRecord = newRecord;
                    yield return newRecord;
                }
                date = incrementer(date);
            }
        }
    }
}

这样使用:

var records = new Record[] {
    new Record 
    {
        Id = 1,
        Date = DateTime.Today,
        IsRepeatable = false,
        Name = "Unique",
        RepetitionType = 0
    },
    new Record 
    {
        Id = 2,
        Date = DateTime.Today,
        IsRepeatable = true,
        Name = "Daily",
        RepetitionType = 1
    },
    new Record
    {
        Id = 3,
        Date = DateTime.Today,
        IsRepeatable = true,
        Name = "Weekly",
        RepetitionType = 2,
        RepeatingEndDate = DateTime.Today.AddDays(7*2)
    }
};

var allRecords = records.ExpandRepetitions(DateTime.Today.AddDays(7), new DateTime(2015, 3, 25)).ToList();

简洁明了!

CTE 可以解决您的问题。这是一个可能对您有所帮助的示例。

DECLARE @T TABLE(
    Id INT,
    Name VARCHAR(20),
    [Date] DATE,
    IsRepeatable BIT,
    RepetitionType TINYINT, --1=daily,2=weekly,3=monthly
    RepeatingEndDate DATE
)



INSERT INTO @T
    SELECT 1,'Foo','03/10/2014',1,3,'05/10/2014'

;WITH Date_CTE (Id,Name,IsRepeatable,RepetitionType,RepeatingEndDate,[Date])
AS
(
    select Id,Name,IsRepeatable,RepetitionType,RepeatingEndDate,[Date] from @T
    UNION ALL
    SELECT Id,Name,IsRepeatable,RepetitionType,RepeatingEndDate,
        CASE
            WHEN RepetitionType=1 THEN DATEADD(DAY,1,[Date])
            WHEN RepetitionType=2 THEN DATEADD(WEEK,1,[Date])
            WHEN RepetitionType=3 THEN DATEADD(MONTH,1,[Date])
        END [Date]
    FROM Date_CTE
    WHERE
        CASE
            WHEN RepetitionType=1 THEN DATEADD(DAY,1,[Date])
            WHEN RepetitionType=2 THEN DATEADD(WEEK,1,[Date])
            WHEN RepetitionType=3 THEN DATEADD(MONTH,1,[Date])
        END <= RepeatingEndDate
    AND IsRepeatable=1
)
select *
from Date_CTE

尝试以下操作,至少将性能与备选方案进行比较。它是一个内联 TVF,可为单个 "Register" 输出所需的行。对于多个 "Registers",只需 CROSS APPLY :-)。下面提供了两者的示例。

设置:

SET ANSI_NULLS ON;
SET QUOTED_IDENTIFIER ON;
SET NOCOUNT ON;
GO

IF (OBJECT_ID(N'dbo.GenerateRowsForDates') IS NOT NULL)
BEGIN
    DROP FUNCTION dbo.GenerateRowsForDates;
END;

GO
CREATE FUNCTION dbo.GenerateRowsForDates
(
  @Id INT,
  @Name NVARCHAR(50),
  @Date DATE,
  @IsRepeatable BIT,
  @RepetitionType TINYINT,
  @RepeatingEndDate DATE
)
RETURNS TABLE
WITH SCHEMABINDING
AS RETURN

  WITH num1(num) AS
  (
    SELECT tmp.col FROM (
                  VALUES (1), (1), (1), (1), (1), (1), (1), (1), (1), (1)
                        ) tmp(col)
  ), num2(TheNumber) AS
  (
    SELECT 0
    UNION ALL
    SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
    FROM   num1 n1
    CROSS JOIN num1 n2
    --CROSS JOIN num1 n3 -- uncomment if you need more than 100 repetitions
  ), dates(TheDate) AS
  (
    SELECT TOP (CASE WHEN @IsRepeatable = 0 THEN 1
                     ELSE CASE @RepetitionType
                               WHEN 1 THEN DATEDIFF(DAY, @Date, @RepeatingEndDate)
                               WHEN 2 THEN DATEDIFF(WEEK, @Date, @RepeatingEndDate)
                               WHEN 3 THEN DATEDIFF(MONTH, @Date, @RepeatingEndDate)
                          END + 1
                END)
           CASE @RepetitionType
              WHEN 1 THEN DATEADD(DAY, num2.TheNumber, @Date)
              WHEN 2 THEN DATEADD(WEEK, num2.TheNumber, @Date)
              WHEN 3 THEN DATEADD(MONTH, num2.TheNumber, @Date)
           END
    FROM   num2
  )
  SELECT 
         @Id AS [Id],
         @Name AS [Name],
         dates.TheDate AS [Date],
         @IsRepeatable AS [IsRepeatable],
         @RepetitionType AS [RepetitionType],
         @RepeatingEndDate AS [RepeatingEndDate]
  FROM dates;
GO

单寄存器测试:

SELECT * FROM dbo.GenerateRowsForDates(1, N'Foo', '2014-03-10', 1, 3, '2014-05-10');
-- 3 rows

SELECT * FROM dbo.GenerateRowsForDates(1, N'Foo', '2014-03-10', 0, 3, '2014-05-10');
-- 1 row (due to @IsRepeatable being set to 0)

多寄存器测试:

DECLARE @Registers TABLE
(
  Id INT,
  Name NVARCHAR(50),
  [Date] DATE,
  IsRepeatable BIT,
  RepetitionType TINYINT,
  RepeatingEndDate DATE
);

INSERT INTO @Registers VALUES (1, N'Foo', '2014-03-10', 1, 3, '2014-05-10');
INSERT INTO @Registers VALUES (2, N'Who', '2014-03-10', 1, 1, '2014-05-10');
INSERT INTO @Registers VALUES (3, N'You', '2014-03-10', 1, 2, '2014-05-10');

SELECT dates.*
FROM   @Registers reg
CROSS APPLY dbo.GenerateRowsForDates(reg.[Id], reg.[Name], reg.[Date],
                                     reg.[IsRepeatable], reg.[RepetitionType],
                                     reg.[RepeatingEndDate]) dates
ORDER BY dates.[Id] ASC, dates.[Date] ASC;