【问题标题】:Loop and insert more than one comma separated List in SQL在 SQL 中循环并插入多个逗号分隔的 List
【发布时间】:2020-03-30 03:12:01
【问题描述】:

我希望遍历两个逗号分隔的值并执行插入

作为一个例子,让我们考虑两个变量

Declare   @Qid= 1,4,6,7,8   @Answers = 4,4,3,2,3

set @pos = 0
set @len = 0

WHILE CHARINDEX(',', @Answers, @pos+1)>0
BEGIN
    set @len = CHARINDEX(',', @Answers, @pos+1) - @pos
    set @value = SUBSTRING(@Answers, @pos, @len)

insert into table values(@fdid,@Qid,@fusid, @value)  -- i need Qid also

set @pos = CHARINDEX(',', @Answers, @pos+@len) +1
END

使用这个循环我可以提取@Answers 并可以执行插入。但我希望提取@Qid 并插入到循环中。

编辑 为了更清楚,它是一个反馈模块。我的结果表有 Qid 和 Answer 字段。答案是等级(1 到 5)。我们在变量@Qid 和@Answers 中得到的值是连续的。这意味着第一个答案将是第一个问题,依此类推。

编辑

根据 Shnugo 的回答

Declare @Qid varchar(100)= '1,4,6,7,8',   @Answers varchar(100)= '4,4,3,2,3'
DECLARE @tbl TABLE(ID INT IDENTITY, Questions VARCHAR(100),Answers VARCHAR(100));
INSERT INTO @tbl VALUES(@Qid,@Answers)


 INSERT INTO table(FeedbackId,QuestionId,FeedbackUserId,Answer)
 SELECT  1,
  A.qXml.value('(/x[sql:column("B.QuestionCount")])[1]','int') AS QuestionNumber,3
      ,A.aXml.value('(/x[sql:column("B.QuestionCount")])[1]','int') AS AnwerNumber
FROM @tbl t
CROSS APPLY(SELECT CAST('<x>' + REPLACE(@Qid,',','</x><x>') + '</x>' AS XML)
                  ,CAST('<x>' + REPLACE(@Answers,',','</x><x>') + '</x>' AS XML)) A(qXml,aXml)
CROSS APPLY(SELECT TOP(A.qXml.value('count(/x)','int')) ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) FROM master..spt_values) B(QuestionCount)

【问题讨论】:

  • 您的 SQL Server 版本是多少?
  • 或者使用string_split,所有支持的sql-server版本
  • 这些是您要插入的对吗:(1, 4), (4, 4), (6, 3), (7, 3), (8, 3)?
  • 我们如何确定问题的答案?
  • @Serg,开发人员忘记将片段位置包含在结果集中。文档明确指出,该集合可能不会按预期顺序返回...

标签: sql sql-server tsql sql-server-2014


【解决方案1】:

我更喜欢Zhorov's JSON answer(需要 v2016+)。

如果你使用低于 2016 的 SQL-Server,你可能会使用这个位置安全的基于 XML 的解决方案:

一个模型表,用两个不同的行来模拟您的问题。

DECLARE @tbl TABLE(ID INT IDENTITY, Questions VARCHAR(100),Answers VARCHAR(100));
INSERT INTO @tbl VALUES('1,4,6,7,8','4,4,3,2,3')
                      ,('1,2,3','4,5,6');

--查询

SELECT t.*
      ,A.qXml.value('(/x[sql:column("B.QuestionCount")])[1]','int') AS QuestionNumber
      ,A.aXml.value('(/x[sql:column("B.QuestionCount")])[1]','int') AS AnwerNumber
FROM @tbl t
CROSS APPLY(SELECT CAST('<x>' + REPLACE(t.Questions,',','</x><x>') + '</x>' AS XML)
                  ,CAST('<x>' + REPLACE(t.Answers,',','</x><x>') + '</x>' AS XML)) A(qXml,aXml)
CROSS APPLY(SELECT TOP(A.qXml.value('count(/x)','int')) ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) FROM master..spt_values) B(QuestionCount);

简而言之:

我们需要一个CROSS APPLY 和一些字符串方法来将1,2,3 之类的东西转换为&lt;x&gt;1&lt;/x&gt;&lt;x&gt;2&lt;/x&gt;&lt;x&gt;3&lt;/x&gt; 之类的xml。
现在我们可以使用 value() 和 XQuery count() 来查找问题的实际数量。
我们需要一个带有计算的TOP() 子句的CROSS APPLY 来获得一组从1 到n 的运行数字,其中n=countOfQuestions。我这样做是针对master..spt_values。这只是一个填充良好的标准表...我们不需要值,只需要创建计数器的任何集合...
最后,我们可以使用.value()sql:column() 连接,以便按位置获取问题和相应的答案。

更新:非表格数据

如果您没有将这些 CSV 参数作为表格获取,您可以使用:

Declare @Qid varchar(100)= '1,4,6,7,8',   @Answers varchar(100)= '4,4,3,2,3'

--INSERT INTO table(FeedbackId,QuestionId,FeedbackUserId,Answer)
SELECT  1
       ,A.qXml.value('(/x[sql:column("B.QuestionCount")])[1]','int') AS QuestionNumber
       ,3
       ,A.aXml.value('(/x[sql:column("B.QuestionCount")])[1]','int') AS AnwerNumber
FROM (SELECT CAST('<x>' + REPLACE(@Qid,',','</x><x>') + '</x>' AS XML)
            ,CAST('<x>' + REPLACE(@Answers,',','</x><x>') + '</x>' AS XML)) A(qXml,aXml)
CROSS APPLY(SELECT TOP(A.qXml.value('count(/x)','int')) ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) FROM master..spt_values) B(QuestionCount);

【讨论】:

  • @SachuMine,遗憾的是 SQL Server 的开发人员并没有真正实现很多字符串功能。 CSV 数据(在您的情况下是逗号分隔的数字列表)是他们最终甚至不想支持的东西。但这并不是很复杂......该语句将返回一个列表,您可以将其直接插入任何表而无需任何循环。最简单的是INTO dbo.NewTargetTable 就在FROM 之前。您可以继续使用新创建的表...
  • 感谢您的回答。但对我来说理解起来似乎有点复杂。我的问题实际上是在循环(或任何其他方式)内执行插入,例如 ----insert into table values(fdid,Qid,fusid, Ans) --- 这里 fdid 和 fusid 是一些固定值和 Qid anf Ans从逗号分隔值中按顺序提取
  • @SachuMine 可能是,你只是想复杂......你可以很容易地使用INSERT INTO YourTable([list of target columns here]) SELECT [list of columns here] FROM... 并使用我的SELECT 如上所述。没有循环,没有VALUES()...
  • 真的很抱歉,我还在闲逛。假设我的表有 5 个字段。创建表选项卡(id int identity,FeedbackId int,QuestionId int,FeedbackUserId int,Answer int)——我希望在其中创建一个我们编写插入语句的过程。作为参数,我们得到 FeedbackId(比如 22)和 FeedbackUserId(比如 34)的值。 QuestionId = 1,4,6,7,8 和 Answer = 4,4,3,2,3 的值以逗号分隔。你能帮我插入查询吗?
  • 您能否检查我在问题中的编辑并评论其是否正确。我们是否需要始终创建临时表
【解决方案2】:

如果您使用 SQL Server 2016 或更高版本,您可以尝试使用下一个基于 JSON 的方法,按问题和答案在输入字符串中的位置映射问题和答案。您需要将输入字符串转换为有效的 JSON 数组,然后使用 OPENJSON() 和默认模式来解析这些数组。结果是一个表,其中包含keyvaluetype 列,key 列保存指定数组中元素的索引。

请注意,STRING_SPLIT() 函数不保证行的顺序,输出行可能是任意顺序。

声明:

DECLARE @Qid nvarchar(max) = N'1,4,6,7,8'
DECLARE @Answers nvarchar(max) = N'4,4,3,2,3'

-- Build your INSERT statement as you expect
-- INSERT INTO Table ...
SELECT j1.[value] AS Qid, j2.[value] AS Answers  
FROM OPENJSON(CONCAT(N'[', @Qid, N']')) j1
JOIN OPENJSON(CONCAT(N'[', @Answers, N']')) j2 ON j1.[key] = j2.[key]

SELECT 语句的结果:

Qid Answers
1   4
4   4
6   3
7   2
8   3

【讨论】:

  • 如果我们使用SQL Server 2014版本,您是否也可以指定答案
  • @SachuMine 对于 2016 年之前的版本,您需要使用基于 XML 的方法,就像在 Shnugo 的回答中一样。谢谢。
  • @SachuMine 也就是说,为什么说明版本如此重要。这个很棒的答案不知何故失效了……但是,我只是放置了一个基于 XML 的位置安全答案,该答案适用于 SQL-Server 2014……
  • 抱歉没有指定版本
【解决方案3】:

您没有描述问题及其答案的关系。我觉得它是一对一的关系,为此,我已经给出了答案。

declare @Qid varchar(200)= '1,4,6,7,8' , @Answers varchar(200) = '4,4,3,2,3'

;with cte
as(
    select id, data qid from dbo.Split (@qid, ',') 
),
cte1 as
(
select id, data ansid from dbo.Split (@answers, ',') 
)


--insert into tablename 
select
qid, ansid from cte join cte1 on cte.id = cte1.id

结果将是:

qid ansid
1    4
4    4
6    3
7    2
8    3

查看更高版本的 sqlserver 的其他选项:Split function equivalent in T-SQL?

【讨论】:

  • 您调用的 dbo.Split() 函数不是标准函数...这个答案无济于事...至少只是关于可能方法的一般想法...
  • @Shnugo,我不知道,但我通常在 2016 版本中使用了很长时间。谢谢。对于 sachuMine,您可以使用 STRING_SPLIT() 代替 split 函数。
  • 现在,不是STRING_SPLIT()!请通读 cmets 和其他答案。 STRING_SPLIT() 不保证按预期顺序返回。这绝对是按位置绑定集合的错误选择...
猜你喜欢
  • 2011-09-15
  • 1970-01-01
  • 1970-01-01
  • 2015-07-30
  • 2021-05-29
  • 2011-08-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多