here is my thinking process on this situation, There will be major two different kinds of queries. One where results are not sliced by IndexPostion and/Or IndexValue. and Second Where Results are sliced by them.
And no single table design can give me that result w/o any trade off. Trade Off might be storage, performance, or query complexity.
Below solution is "let go Storage" but takes care of performance and query simplicity while accessing this schema.
For the First type of queries only table "SO_FieldIndexValue" will be used.
But for the second type of queries we need to join it with other two where we need the result filtered by 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)
I have provided a simple SQL API to just demonstrate the how insert into this schema can be handle easily using single API.
There is plenty of opportunity to play with this API and make customization as needed. for example add validation if input is in proper format.
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
Now Sample Input Data
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'
Sample Query1
SELECT FieldName,FieldIndex,FieldValue
FROM dbo.SO_FieldIndexValue
WHERE FieldName = 'Field1'
Sample Result1
Sample Query2
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'
Sample Result2