根据动态比较查找匹配的实体

时间:2018-03-25 02:05:02

标签: sql-server asp.net-web-api entity-framework-core

我不确定这个主题是否真的有意义,但我不确定如何说出来。以下是设置:我有一个Item,其中有ItemLogicField,其中每个Item。例如,每个ItemLogic都有25个Item个实体。逻辑确定Field X has a value greater than A是否与表单中的给定输入匹配。例如,对于25个字段中的每个字段,Field Y has a value equal to Btrue等等。

在当前版本的应用中,所有相关实体都会被查询并循环,返回第一个匹配项,其中所有ItemLogic都是CREATE TABLE [dbo].[Items]( [Id] [int] IDENTITY(1,1) NOT NULL, [Name] [nvarchar](255) NOT NULL ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] CREATE TABLE [dbo].[ItemLogic]( [Id] [int] IDENTITY(1,1) NOT NULL, [ItemId] [int] NOT NULL, [FieldId] [int] NOT NULL, [Value] [nvarchar](max) NULL, [Comparison] [int] NOT NULL ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] CREATE TABLE [dbo].[Fields]( [Id] [int] IDENTITY(1,1) NOT NULL, [Value] [nvarchar](max) NOT NULL, [Type] [int] NOT NULL, ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] INSERT INTO [dbo].[Fields] (Value, Type) VALUES ('abc', 0), ('def', 0), ('123', 1) INSERT INTO [dbo].[Items] (Name) VALUES ('Item 1'), ('Item 2'), ('Item 3') INSERT INTO [dbo].[ItemLogic] (ItemId, FieldId, Value, Comparison) VALUES (1, 1, 'xyz', 1), (1, 2, 'qrs', 1), (1, 3, '200', 0), (2, 1, 'abc', 1), (2, 2, 'xyz', 1), (2, 3, '123', 2), (3, 1, 'abc', 1), (3, 2, 'def', 1), (3, 2, '100', 0) 。它有点贵,但代码简单,而且从来没有那么多项目要看。到现在为止。

现在,该应用需要过滤3000项以查找匹配项。上一个查询至少有两个连接,在我们的SQL实例上大约需要45秒。这太长了。

存储过程似乎很自然,但这里有一个问题:数据对于每组Items都是动态的,它以字符串值的形式出现,并且通常需要转换为不同的类型(DateTime或最常见的是)执行实际的比较,忽略一些逻辑而不是比较。这对存储过程来说是一个额外的开销,至少它是如何打击我的。

或者,我可以对数据进行分块,但这对于试图匹配集合中最后一个项目的可怜人来说并没有多少节省。

可以采取哪些方法来加速比赛?

架构和一些示例数据:

Comparison

对于Type字段,它是枚举匹配:0 =大于,1 =等于,2 =忽略。对于Item 3字段,它是枚举匹配:0 =字符串,1 = int。

上述匹配的预期结果应该是返回import Tkinter

4 个答案:

答案 0 :(得分:1)

永远不会快。然而,这是我能想象到的最简单,最紧凑的解决方案:

SELECT * 
FROM 
    Items
WHERE 
    Id NOT IN (
        SELECT IL.ItemId
        FROM
            Fields F
            INNER JOIN ItemLogic IL
                ON F.Id = IL.FieldId
        WHERE
            NOT (
                IL.Comparison = 2 -- Ignore
                OR
                F.Type = 0 AND ( -- string types
                    IL.Comparison = 0 AND F.Value > IL.Value
                    OR 
                    IL.Comparison = 1 AND F.Value = IL.Value
                )
                OR
                F.Type = 1 AND ( -- integer types
                    IL.Comparison = 0 AND TRY_CAST(F.Value AS int) > TRY_CAST(IL.Value AS int)
                    OR 
                    IL.Comparison = 1 AND TRY_CAST(F.Value AS int) = TRY_CAST(IL.Value AS int)
                )
            )
        )

答案 1 :(得分:0)

您可以使用案例语句根据您的逻辑执行字段的实际匹配。然后简单地计算匹配数并将其与要匹配的字段数进行比较。以下示例演示:

WITH CTE_TypedFieldValues
AS
(   -- First unpivot fields by data type and convert to typed value
    SELECT   [Id]
            ,[DataType]
            ,[0] AS [ValueString]
            ,CONVERT(INT, [1]) AS [ValueInt] 
    FROM
    (
        SELECT   [Id]
                ,[Value]
                ,[Type] AS [DataType]
                ,[Type]
        FROM dbo.Fields
    ) AS [FieldsSource]
    PIVOT
    (
        MAX([Value])
        FOR [Type] IN ([0], [1])
    ) AS [FieldTyped]
), 
CTE_Compare
AS
(
    SELECT       [IL].[ItemId]
                ,[IL].[FieldId]
                ,[FV].[DataType]
                ,[IL].[Value] AS [LogicValue]
                ,[IL].[Comparison]
                ,[FV].[ValueString]
                ,[FV].[ValueInt]
                ,(
                    CASE 
                        WHEN [FV].[DataType]  = 0 THEN -- If data types are strings then use [ValueString] column for comparison
                            CASE 
                                WHEN [IL].[Comparison] = 0 THEN     -- Perform greater than comparison, if condition is met then flag as matched.
                                    CASE WHEN [FV].[ValueString] > [IL].[Value] THEN 1 ELSE 0 END
                                WHEN [IL].[Comparison] = 1 THEN     -- Perform greater than comparison, if condition is met then flag as matched.
                                    CASE WHEN  [FV].[ValueString] = [IL].[Value] THEN 1 ELSE 0 END
                            END
                        WHEN [FV].[DataType]  =  1 THEN -- If data types are integers then use [ValueInt] column for comparison
                            CASE 
                                WHEN [IL].[Comparison] = 0 THEN 
                                    CASE WHEN [FV].[ValueInt]  > CAST([IL].[Value] AS INT) THEN 1 ELSE 0 END
                                WHEN [IL].[Comparison] = 1 THEN
                                    CASE WHEN [FV].[ValueInt] = CAST([IL].[Value] AS INT) THEN 1 ELSE 0 END
                            END
                    END
                ) [Match]

    FROM        [dbo].[ItemLogic] [IL]
    INNER JOIN  CTE_TypedFieldValues [FV] ON [IL].FieldId = [FV].[Id]
    WHERE       Comparison < 2 -- Filter out fields marked as ignored.
)
SELECT      ItemId
            ,COUNT([FieldId]) AS [ExpectedMatches]
            ,SUM([Match]) AS [ActualMatches]
FROM        CTE_Compare
GROUP BY    ItemId
HAVING      COUNT([FieldId]) = SUM([Match]) -- Only return ItemIDs where the number matched fields is equal to the number of expected matches.

答案 2 :(得分:0)

我建议使用不同的方法,即单独的逻辑:查询数据将在SQL中完成,所有转换和检查都将在应用程序中完成。逻辑是:

  1. 获取需要检查的所有Items

  2. 循环throguh all Items(找到匹配时,打破循环)。在每次迭代时,查询相关ItemLogicField。转换和检查应该比在SQL中更有效(同样,select查询将立即执行,因为您将查询结果一次限制为一个项目。)

  3. 执行多个查询可能看起来很难,但如果你在一个连接中进行(在.NET中有连接池,所以你甚至不用担心),它应该更快。

答案 3 :(得分:0)

作为替代解决方案,您可以检索所有记录并将规则应用于内存。您也可以尝试在内存中存储相关记录。