[英]Can i convert this C# logic into SQL SERVER stored procedure?
Helo everyone, 大家好
I have this method written in C#, and it basically search for a register on the database, and if it's repeatable (isRepeatable = true), it will increment and insert itself again in a list with a different date, but with the same Id and Name properties. 我用C#编写了这个方法,它基本上是在数据库上搜索一个寄存器,如果它是可重复的(isRepeatable = true),它将递增并再次将自己插入具有不同日期,但具有相同ID和的列表中。名称属性。 I do specify too the repeating type (daily, weekly, monthly or yearly), and it will go until it reaches the value specified in the RepeatingEndDate or the date that i specify on the method, so it won't loop infinitely through repeatable registers that don't have a RepeatingEndDate specified. 我也确实指定了重复类型(每天,每周,每月或每年),并且会一直重复直到达到RepeatingEndDate中指定的值或我在方法中指定的日期,所以它不会无限循环通过可重复寄存器没有指定RepeatingEndDate的代码。
In summary, if i have a register like this in my Database: 总之,如果我的数据库中有这样的寄存器:
Id: 1
Name: Foo
Date: 03/10/2014
IsRepeatable: true
RepetitionType: 3 (Monthly)
RepeatingEndDate 05/10/2014
This will be the list of registers that my C#'s method will output from that single register: 这将是我的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
Note that all the properties except the Date are the same, because i'm creating new Objects with all the same properties, but taking in consideration the repetition that i want and setting only the date. 请注意,除日期以外的所有属性都相同,因为我正在创建具有所有相同属性的新对象,但要考虑到我想要的重复并仅设置日期。 My goal here is to process repeatable data without saving multiple data and, in my actual case, letting the application handle the repetition. 我的目标是在不保存多个数据的情况下处理可重复的数据,在我的实际情况下,让应用程序处理重复。
But i found this is really CPU intense at some point, and i had the idea of converting this all to Stored Procedure in SQL SERVER. 但是我发现在某些时候这确实是CPU密集型的,并且我有将其全部转换为SQL SERVER中的存储过程的想法。
My question is: Is it possible to convert all this C# logic into SQL SERVER stored procedure and just consume this proc in my application as a List of Registers? 我的问题是:是否有可能将所有这些C#逻辑转换为SQL SERVER存储过程,并仅在应用程序中将此proc用作寄存器列表? If so, my result will basically be getting a list of registers, some may have the same Id, Name, Etc, but different dates, according to it's repetition. 如果是这样,我的结果基本上是得到一个寄存器列表,根据其重复,有些寄存器可能具有相同的ID,名称,等,但日期不同。
EDIT 编辑
Plain code is here: https://codereview.stackexchange.com/questions/83777/refactoring-a-loop-through-repetitive-registers 普通代码在这里: https : //codereview.stackexchange.com/questions/83777/refactoring-a-loop-through-repetitive-registers
I doubt SQL Server could do more efficiently and easily than c#. 我怀疑SQL Server是否可以比c#更高效,更轻松地执行。
Given the following 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; }
}
You can expand repetitions using ExpandRepetitions extension method: 您可以使用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);
}
}
}
}
Use like this: 像这样使用:
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();
Clean and easy! 干净又容易!
CTE would solve your problem. CTE将解决您的问题。 Here is an example that may help you. 这是一个可以帮助您的示例。
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
Try the following, at least to compare performance against the alternatives. 请尝试以下操作,至少将性能与其他选择进行比较。 It is an inline TVF that outputs the desired rows for a single "Register". 它是嵌入式TVF,可为单个“寄存器”输出所需的行。 For multiple "Registers", just CROSS APPLY
it :-). 对于多个“寄存器”,只需CROSS APPLY
:-)。 Examples for both are provided below. 下面提供了两个示例。
The Setup: 设置:
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
Single Register tests: 单寄存器测试:
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)
Multiple Register test: 多寄存器测试:
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;
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.