【问题标题】:How can I model the following data structure?如何建模以下数据结构?
【发布时间】:2014-03-15 05:40:39
【问题描述】:

抱歉,这个问题有点抽象,因此有点难以定义,所以我可能需要多次编辑这个问题来澄清:

我有一个需要解析的配置文件,其中每个相关行都包含以下格式之一:

FieldName = Value
FieldName(Index) = Value
FieldName(Index1, Index2) = Value
FieldName(Index1, Index2, ...IndexN) = Value

例如:

Field0 = 0
Field1(0, 0) = 0.01
Field1(0, 1) = 0.02
Field1(1, 0) = 0.03
Field1(1, 1) = 0.04
Field1(2, 0) = ADF0102BC5
Field1(2, 1) = ADF0102BC6
Field2(0, 0) = 0
Field2(0, 1) = 2
Field3(1) = 5
Field3(2) = 7
Field3(3) = 9
Field4(0, 0, 1) = 64.75
Field4(0, 1, 0) = 65.25
Field4(1, 0, 0) = 72.25

相关行很简单,可以使用正则表达式从文件中解析出来,我已经处理了这部分内容。我遇到的问题是如何对数据库中的数据进行建模,以便在新索引进入字段范围时,可以自动添加它,而无需将新列添加到表中。

FieldName 始终是最大长度为 50 的 Varchar

Value 始终是一个数值,以需要的多种字符串格式之一表示 单独解析并为此问题的目的在很大程度上是无关紧要的。

每个索引(如果字段有它们)都是一个整数值。每个都有其自身的含义,但作为一组值到字段名称的映射一起使用。

字段名的每个实例即Field1 将具有恒定数量的索引,即您永远不会拥有 Field1(0, 0) 和 Field1(0, 0, 0)。如果 Field1 在配置文件的一行中有 2 个索引,那么 Field1 的所有实例都会有 2 个索引。

我需要系统足够灵活,以解析文件并为每个字段附加尽可能多的索引。

我有两种想法 - 我是否将“方程式”的整个左侧视为标签,因此 Field1(0, 0) 成为“FieldName”,这使得通过索引查询非常困难,还是我对我的数据进行建模,使这些索引有效地成为字段值的坐标?

如果索引在所有文件中保持不变,我可以使用以下方法进行建模:

Table Fields(
    FieldId Integer Identity(1, 1) Primary Key,
    FieldName VarChar(50)
)

Table FieldValues(
    FieldId Integer Constraint FK_FV_FID Foreign Key References Fields(FieldId)
    Index1 Integer
    Index2 Integer
    Index3 Integer
    Index4 Integer
    Value  Varchar(50)
)

不幸的是,由于在解析文件之前索引数量未知,这使得建模这种关系更加复杂。

存储数据后,我需要能够简单地使用任一字段名进行查询,以获取所有对应索引引用及其值的列表,即

Field1
------
0, 0 = 0.01
0, 1 = 0.02
1, 0 = 0.03
1, 1 = 0.04
2, 0 = ADF0102BC5
2, 1 = ADF0102BC6

或者

Field1 Where Index1 = 0
-----------------------
0, 0 = 0.01
0, 1 = 0.02

或者

Field1 Where Index 2 = 1
------------------------
0, 1 = 0.02
1, 1 = 0.04
2, 1 = ADF0102BC6

或者

Field1 Where Index1 = 0 And Index2 = 1
--------------------------------------
0, 1 = 0.02

如果我有一个复杂的表结构,它会使简化查询更加令人头疼。

【问题讨论】:

  • 每个“字段名”是否都有固定数量的索引?即以下构造是否可能:Field4(0, 0) = 0 Field4(0, 0, 0) = 0
  • 是的,每个 'fieldname' 都有一定数量的索引,并且对于该 fieldname 的所有实例都是通用的。 Field1(0, 0) 实际上与 Field(0, 0, 0) 不同...尽管在配置文件的上下文中,单个字段名的索引数量是恒定的...不幸的是如何在解析配置文件之前,许多常量是未知的。你不会得到 Field4 (例如),一行有 2 个索引,另一行有 3 个索引。它在所有被解析的配置文件中保持不变。
  • 您能详细解释一下客户如何与数据交互吗?您可能期望哪些类型的查询?
  • “Field0 = 0”是否意味着某些字段也可能没有任何索引值,它只是常规表列?对于所有其他字段,我看到它们具有某种索引并且具有赋值。因此,例如,如果所有 Field3 只有一个索引,那么我可以说 Filed3 是一个单一数组类型的列。 Field1 和 Field2 是 2x2 Array,Field4 是 3x3 Array。我理解对了吗?
  • 您出于某种原因需要关系数据库吗?不好笑。您还没有说明您将运行哪些查询,并且将这些数据存储在 SQL 中可能不是特别可扩展 - 如果您正在处理数百万条父记录,例如,每条记录都有很多字段,那么您的数据可能会变得相当骨折。

标签: sql data-modeling


【解决方案1】:

这是我对这种情况的思考过程, 将有两种主要的不同类型的查询。结果不被 IndexPostion 和/或 IndexValue 切片的一种。和第二个结果被他们分割的地方。

没有任何单一的桌子设计可以给我这样的结果而无需任何权衡。权衡可能是存储、性能或查询复杂性。

以下解决方案是“放开存储”,但在访问此架构时会考虑性能和查询的简单性。

对于第一种类型的查询,只会使用表“SO_FieldIndexValue”。

但是对于第二种类型的查询,我们需要将其与其他两个查询相结合,我们需要通过 IndexPosition/IndexPositionValue 过滤结果。

    IF OBJECT_ID('SO_FieldIndexPositionValue') IS NOT NULL 
        DROP TABLE SO_FieldIndexPositionValue
    IF OBJECT_ID('SO_FieldIndexValue') IS NOT NULL 
        DROP TABLE SO_FieldIndexValue
    IF OBJECT_ID('SO_IndexPositionValue') IS NOT NULL 
        DROP TABLE SO_IndexPositionValue

    CREATE TABLE SO_FieldIndexValue
        (
          FIV_ID        BIGINT NOT NULL IDENTITY
            CONSTRAINT XPK_SO_FieldIndexValue PRIMARY KEY NONCLUSTERED
          ,FieldName    NVARCHAR(50)NOT NULL
          ,FieldIndex   NVARCHAR(10) NOT NULL
          ,FieldValue   NVARCHAR(500) NULL
        )
    CREATE UNIQUE CLUSTERED INDEX CIDX_SO_FieldIndexValue
    ON SO_FieldIndexValue(FIV_ID ASC,FieldName ASC,FieldIndex ASC)
    CREATE NONCLUSTERED INDEX NCIDX_SO_FieldIndexValue
    ON SO_FieldIndexValue (FIV_ID,FieldName) 
    INCLUDE (FieldIndex,FieldValue)

    CREATE TABLE SO_IndexPositionValue
        (
            IPV_ID              BIGINT  NOT NULL IDENTITY
                CONSTRAINT XPK_SO_IndexPositionValue PRIMARY KEY NONCLUSTERED
            ,IndexName          SYSNAME NOT NULL
            ,IndexPosition      INT     NOT NULL
            ,IndexPositionValue BIGINT  NOT NULL
        )
    CREATE UNIQUE CLUSTERED INDEX CIDX_SO_IndexPositionValue 
    ON SO_IndexPositionValue(IPV_ID ASC,IndexPosition ASC, IndexPositionValue ASC)

    CREATE TABLE SO_FieldIndexPositionValue
        (
          FIPV_ID       BIGINT NOT NULL IDENTITY
                CONSTRAINT XPK_SO_FieldIndexPositionValue PRIMARY KEY NONCLUSTERED
          ,FIV_ID           BIGINT NOT NULL REFERENCES SO_FieldIndexValue (FIV_ID)
          ,IPV_ID       BIGINT NOT NULL REFERENCES SO_IndexPositionValue (IPV_ID)
        )
    CREATE CLUSTERED INDEX CIDX_SO_FieldIndexPositionValue 
    ON SO_FieldIndexPositionValue(FIPV_ID ASC,FIV_ID ASC,IPV_ID ASC)

我提供了一个简单的 SQL API 来演示如何使用单个 API 轻松处理插入到此架构中。

有很多机会可以使用此 API 并根据需要进行自定义。例如,如果输入格式正确,则添加验证。

    IF object_id('pr_FiledValueInsert','p') IS NOT NULL
        DROP PROCEDURE pr_FiledValueInsert
    GO
    CREATE PROCEDURE pr_FiledValueInsert
    (
        @FieldIndexValue    NVARCHAR(MAX)
        ,@FieldValue        NVARCHAR(MAX)=NULL
    )
    AS
    BEGIN
    SET NOCOUNT ON
    BEGIN TRY
    BEGIN TRAN
            DECLARE @OriginalFiledIndex NVARCHAR(MAX)=@FieldIndexValue
            DECLARE @FieldName              sysname=''
                    ,@FIV_ID                BIGINT
                    ,@FieldIndex            sysname
                    ,@IndexName             sysname
                    ,@IndexPosition         BIGINT
                    ,@IndexPositionValue    BIGINT
                    ,@IPV_ID                BIGINT
                    ,@FIPV_ID               BIGINT
                    ,@CharIndex1            BIGINT
                    ,@CharIndex2            BIGINT
                    ,@StrLen                BIGINT
                    ,@StartPos              BIGINT
                    ,@EndPos                BIGINT

            SET @CharIndex1 = CHARINDEX('(',@OriginalFiledIndex)
            SET @StrLen     = LEN(@OriginalFiledIndex)
            SET @CharIndex2 = CHARINDEX(')',@OriginalFiledIndex)
            SET @FieldName  = RTRIM(LTRIM(SUBSTRING(@OriginalFiledIndex,1,@CharIndex1-1)))
            SET @FieldIndex = RTRIM(LTRIM(SUBSTRING(@OriginalFiledIndex,@CharIndex1+1,@StrLen-@CharIndex1-1)))


            --Insert FieldIndexValue and Get @FIV_ID
            SELECT @FIV_ID = FIV_ID 
            FROM SO_FieldIndexValue 
            WHERE FieldName=@FieldName
            AND FieldIndex=@FieldIndex
            IF @FIV_ID IS NULL
            BEGIN
                INSERT INTO SO_FieldIndexValue ( FieldName,FieldIndex,FieldValue )
                SELECT @FieldName,@FieldIndex,@FieldValue
                SELECT @FIV_ID = SCOPE_IDENTITY()
            END
            ELSE
            BEGIN
                RAISERROR('Filed and Index Combination already Exists',16,1)
            END


            --Find the First IndexPosition and IndexPositionValue and Get @IPV_ID
            SELECT @StartPos=CHARINDEX('(',@OriginalFiledIndex,1)+1
            SELECT @EndPos = CASE   WHEN CHARINDEX(',',@OriginalFiledIndex,@StartPos)<>0
                                    THEN  CHARINDEX(',',@OriginalFiledIndex,@StartPos)- @StartPos
                                    ELSE CHARINDEX(')',@OriginalFiledIndex,@StartPos) - @StartPos
                                END
            SELECT @IndexPosition = 1
            SELECT @IndexPositionValue = SUBSTRING(@OriginalFiledIndex,@StartPos,@EndPos)
            SELECT @IndexName = 'Index'+CAST(@IndexPosition AS Sysname)

            --Insert IndexPositionvalue
            SELECT @IPV_ID = IPV_ID
            FROM SO_IndexPositionValue
            WHERE IndexPosition=@IndexPosition
            AND IndexPositionValue = @IndexPositionValue
            IF @IPV_ID IS NULL
            BEGIN
                INSERT SO_IndexPositionValue
                        ( IndexName ,
                          IndexPosition ,
                          IndexPositionValue
                        )
                SELECT @IndexName,@IndexPosition,@IndexPositionValue
                SET @IPV_ID = SCOPE_IDENTITY()          
            END

            --Insert the First FieldIndexPositionValue
            IF NOT EXISTS(
                            SELECT TOP(1) 1 
                            FROM SO_FieldIndexPositionValue
                            WHERE FIV_ID = @FIV_ID
                            AND IPV_ID = @IPV_ID
                        )
            BEGIN
                INSERT SO_FieldIndexPositionValue( FIV_ID, IPV_ID )
                SELECT @FIV_ID,@IPV_ID
            END

            --If More than One Index exist, process remining indexpositions
            WHILE @StrLen>@StartPos+@EndPos
            BEGIN           
                SET @StartPos = @StartPos+@EndPos+1
                SET @EndPos = CASE WHEN CHARINDEX(',',@OriginalFiledIndex,@StartPos)<>0
                                    THEN  CHARINDEX(',',@OriginalFiledIndex,@StartPos)- @StartPos
                                    ELSE CHARINDEX(')',@OriginalFiledIndex,@StartPos) - @StartPos
                                END

                SELECT @IndexPosition = @IndexPosition+1
                SELECT @IndexPositionValue = SUBSTRING(@OriginalFiledIndex,@StartPos,@EndPos)
                SELECT @IndexName = 'Index'+CAST(@IndexPosition AS Sysname)

                --Insert IndexPositionvalue
                SET @IPV_ID = NULL
                SELECT @IPV_ID = IPV_ID
                FROM SO_IndexPositionValue
                WHERE IndexPosition=@IndexPosition
                AND IndexPositionValue = @IndexPositionValue
                IF @IPV_ID IS NULL
                BEGIN
                    INSERT SO_IndexPositionValue
                            ( IndexName ,
                              IndexPosition ,
                              IndexPositionValue
                            )
                    SELECT @IndexName,@IndexPosition,@IndexPositionValue
                    SET @IPV_ID = SCOPE_IDENTITY()
                END

                --Insert FieldIndexPositionValue
                IF NOT EXISTS(
                                SELECT TOP(1) 1 
                                FROM SO_FieldIndexPositionValue
                                WHERE FIV_ID = @FIV_ID
                                AND IPV_ID = @IPV_ID
                            )
                BEGIN
                    INSERT SO_FieldIndexPositionValue( FIV_ID, IPV_ID )
                    SELECT @FIV_ID,@IPV_ID
                END
            END
    COMMIT TRAN
    END TRY
    BEGIN CATCH
        ROLLBACK TRAN
        SELECT ERROR_MESSAGE()
    END CATCH
    SET NOCOUNT OFF
    END
    GO

现在输入数据示例

    EXECUTE pr_FiledValueInsert 'FIELD1(0,1,0)',101
    EXECUTE pr_FiledValueInsert 'FIELD1(0,1,2)','ABCDEF'
    EXECUTE pr_FiledValueInsert 'FIELD1(1,0,1)','hello1'

    EXECUTE pr_FiledValueInsert 'FIELD2(1,0,0)',102
    EXECUTE pr_FiledValueInsert 'FIELD2(1,1,0)','hey2'
    EXECUTE pr_FiledValueInsert 'FIELD2(1,0,1)','hello2'

示例查询1

    SELECT FieldName,FieldIndex,FieldValue 
    FROM dbo.SO_FieldIndexValue
    WHERE FieldName = 'Field1'

样本结果1

示例查询2

    SELECT FieldName,FieldIndex AS CompeleteIndex,IndexPosition,IndexPositionValue,FieldValue
    FROM SO_FieldIndexPositionValue fipv
    JOIN dbo.SO_IndexPositionValue ipv
        ON ipv.IPV_ID=fipv.IPV_ID
    JOIN dbo.SO_FieldIndexValue fiv
        ON fiv.FIV_ID=fipv.FIV_ID
    WHERE
    (IndexPosition=2 AND IndexPositionValue=1)
    AND FieldName = 'Field1'

样本结果2

【讨论】:

  • 天哪 - 年度最佳答案奖颁给了 Anup Shah!非常感谢您完成所有这些工作! +100 :)
【解决方案2】:

不确定这是唯一的答案 - 但这是一个想法:

field
-------
field_id
name

index
---------
index_id
field_id
position
value

field_value
------------
field_id
index_id
value

【讨论】:

  • Randy 我已经玩过这个了,但我无法理解这样一个事实,即每个字段的索引被视为一个组来获取值,而不是单独地。
  • 是的 - 您需要按顺序查询给定字段的所有索引
  • 这基本上是 SQL Server 本身对类似事物建模的方式,例如元数据中的存储过程和用户​​定义函数定义。
  • 本,我添加了一个 index_id 来帮助您解决问题...根据需要在索引表中重复 index_id,然后您可以将其用作 field_value 表中的参考。
【解决方案3】:

我的 SQL 经验教会了我一件事 - 如果您不知道它们有多少,那么它们属于行而不是列。

我建议两张表结构如下:

行 ID、字段名称、值

索引

Row_Id、Index_Position、Index_Value

要通过索引查找参数值,您可以对索引表进行多次连接,例如

select r.Row_Id, r.Value from Row r
join Index i1 on r.Row_Id = i1.Row_Id
join Index i2 on r.Row_Id = i2.Row_Id
join Index i3 on r.Row_Id = i3.Row_Id
where
i1.Index_Position = 1 and i1.Index_Value = '3' and
i2.Index_Position = 2 and i2.Index_Value = '7' and
i3.Index_Position = 3 and i3.Index_Value = '42' and

编辑:这基本上归结为符合first normal form。在一列中包含多条信息(例如,允许您的 FieldName 列包含“FieldName(0,1)”)违反了这一点 - 这将导致以后头痛(如您所述 - 如何解析?如何比较具有不同数字的行条目数?如何查询?)。

EDIT 2:您的问题中列出的配置文件前三行的示例数据。基本上,配置文件中的每一行都映射到 Row 表中的一个条目。每个索引参数都映射到索引表中的一个条目(带有指向它来自哪一行的链接):

行 ID、字段名称、值

1, "字段0", "0"

2,“字段1”,“0.01”

3,“字段1”,“0.02”

索引

Row_Id、Index_Position、Index_Value

2、1、0

2、2、0

3、1、0

3、2、1

【讨论】:

  • 格雷厄姆,你能给我一个简短的例子来说明这些表格中的数据吗?这样我就可以直观地看到我在看什么?
  • 哦,好吧,我现在明白了。我被困在将字段名称视为主键并试图映射围绕它的所有内容。这就是为什么我很难理解它!
猜你喜欢
  • 1970-01-01
  • 2019-02-25
  • 2017-03-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多