如何逐字符地比较字符串类型列而不循环?

时间:2015-01-13 02:02:35

标签: sql sql-server comparison

我有两列:一列包含实际答案,另一列是answer_key。

我想比较answer_key的答案并在第三栏中得分:

 ID   Answers    Answer_key    Score
  1   ABCD        ABCC          1110  
  2   ACD         DCA            010

当然,我可以检查长度,循环浏览每个角色以单独比较它们,并获得分数。

然而,还有其他选择吗?可能基于XML路径?

4 个答案:

答案 0 :(得分:0)

您可以尝试二进制值而不是字母。

A=0001  B=0010 C=0100 D=1000
ABCD = 0001001001001000  (0x1248)
ABCC = 0001001001000100  (0x1244)

Score = (Answers XOR Answer_key) XOR 11111111

XOR 11111111是可选的

答案 1 :(得分:0)

您要做的是将AnswersAnswers_Key中的每个字符拆分为不同的行,然后进行比较。这可以使用Recursive CTE来完成。使用FOR XML PATH函数完成连接。

CREATE TABLE temp(
    Answers     VARCHAR(10),
    Answer_Key  VARCHAR(10)
)
INSERT INTO temp VALUES ('ABCD', 'ABCC'), ('ACD', 'DCA');

;WITH temp_numbered AS(
    SELECT 
        ID = ROW_NUMBER() OVER(ORDER BY Answer_Key), 
        * 
    FROM temp
),
cte AS(
    SELECT
        ID,
        Answer_Key_Char =   SUBSTRING(Answer_Key, 1, 1),
        Answer_Key      =   STUFF(Answer_Key, 1, 1, ''),
        Answers_Char    =   SUBSTRING(Answers, 1, 1),
        Answers         =   STUFF(Answers, 1, 1, ''),
        RowID           =   1
    FROM temp_numbered t
    UNION ALL
    SELECT
        ID,
        Answer_Key_Char =   SUBSTRING(Answer_Key, 1, 1),
        Answers         =   STUFF(Answer_Key, 1, 1, ''),
        Answers_Char    =   SUBSTRING(Answers, 1, 1),
        Answers         =   STUFF(Answers, 1, 1, ''),
        RowID           =   RowID + 1
    FROM cte
    WHERE LEN(Answer_Key) > 0
)
SELECT 
    Answers,
    Answer_Key,
    Score = (SELECT 
                CASE WHEN Answer_Key_Char = Answers_Char THEN '1' ELSE '0' END
            FROM cte
            WHERE ID = t.ID
            ORDER BY ID, RowID
            FOR XML PATH(''))
FROM temp_numbered t

DROP TABLE temp

以下是使用Tally Table的另一种方式:

;WITH tally(N) AS(
    SELECT TOP 11000 ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) FROM sys.columns
)
,temp_numbered AS(
    SELECT 
        ID = ROW_NUMBER() OVER(ORDER BY Answer_Key), 
        * 
    FROM temp
)
,cte AS(
    SELECT
        ID,
        Answer_Key_Char =   SUBSTRING(Answer_Key, N, 1),
        Answers_Char    =   SUBSTRING(Answers, N, 1),
        RowID           =   N
    FROM temp_numbered tn
    CROSS JOIN Tally t
    WHERE t.N <= LEN(tn.Answer_Key)
)
SELECT 
    Answers,
    Answer_Key,
    Score = (SELECT 
                CASE WHEN Answer_Key_Char = Answers_Char THEN '1' ELSE '0' END
            FROM cte
            WHERE ID = t.ID
            ORDER BY ID, RowID
            FOR XML PATH(''))
FROM temp_numbered t

答案 2 :(得分:0)

我似乎最容易在整个集合中循环遍历每个字符:

-- get max Answer length
declare @len int,@max_len int
select @max_len = max(len(Answers)),
  @len = 1
from Answers

-- update scores
while @len <= @max_len
  begin
    update Answers
      set Score = isnull(Score,'') + '1'
      where substring(Answers,@len,1) = substring(Answer_Key,@len,1)
        and len(Answers) >= @len
    update Answers
      set Score = isnull(Score,'') + '0'
      where substring(Answers,@len,1) != substring(Answer_Key,@len,1)
        and len(Answers) >= @len
    set @len = @len + 1
  end

-- return Scores
select * from Answers

<强> SQL FIDDLE

答案 3 :(得分:0)

扩大@weswesthemenace的答案以克服cte限制。

DECLARE @Answers TABLE
(
    Id INT IDENTITY(1, 1) not null,
    Answers VARCHAR(MAX) not null,
    Answer_Key VARCHAR(MAX) not null
)

INSERT INTO @Answers (Answers, Answer_Key) VALUES ('ABCD', 'ABCC')
INSERT INTO @Answers (Answers, Answer_Key) VALUES ('ACD', 'DCA')
INSERT INTO @Answers (Answers, Answer_Key) VALUES ('ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ', 'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEGGHIJKLMNOPQRSTUVXXYZABCDEFGHIIKKMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXZZ');

WITH
  E01(N) AS (SELECT 1 UNION ALL SELECT 1),
  E02(N) AS (SELECT 1 FROM E01 a CROSS JOIN E01 b),
  E04(N) AS (SELECT 1 FROM E02 a CROSS JOIN E02 b),
  E08(N) AS (SELECT 1 FROM E04 a CROSS JOIN E04 b),
  E16(N) AS (SELECT 1 FROM E08 a CROSS JOIN E08 b),
  E32(N) AS (SELECT 1 FROM E16 a CROSS JOIN E16 b),
  cteTally(N) AS (SELECT row_number() OVER (ORDER BY N) FROM E32)

SELECT b.Answers, b.Answer_Key, 
    (
      SELECT CASE when SUBSTRING(a.Answer_Key, n.N, 1) = SUBSTRING(a.Answers, n.N, 1) then '1' else '0' end
        FROM @Answers a
          CROSS APPLY cteTally n
        WHERE b.Id = a.Id AND n.N <= DATALENGTH(b.Answers)
        ORDER BY ID, n.N
        FOR XML PATH('')
    ) Score
  FROM @Answers b

这可以通过数据库中的实用程序编号函数来简化。我叫做dbo.Number(开始,结束)

SELECT b.Answers, b.Answer_Key, 
    (
      SELECT CASE WHEN SUBSTRING(a.Answer_Key, n.N, 1) = SUBSTRING(a.Answers, n.N, 1) THEN '1' ELSE '0' END
        FROM @Answers a
          CROSS APPLY dbo.Number(1, DATALENGTH(b.Answers)) n
        WHERE b.Id = a.Id
        ORDER BY ID, n.N
        FOR XML PATH('')
    ) Score
  FROM @Answers b