我试图连接一些与一些整数相对应的字符(例如前15个ASCII字符):
;with cte as (
select 1 nr
union all
select nr + 1
from cte
where nr <= 15)
select (
select char(nr)
from cte
for xml path (''), type).value('.', 'nvarchar(max)')
option (maxrecursion 0)
但我收到的错误是:
Msg 6841,Level 16,State 1,Line 1
FOR XML无法序列化 节点的数据&#39; NoName&#39;因为它包含一个字符(0x0001) XML中不允许使用。要使用FOR XML检索此数据,请将其转换 到二进制,varbinary或图像数据类型并使用BINARY BASE64 指令。
例如,即使我尝试将CTE的种子从1修改为10,我仍然会收到错误,但是对于不同的角色,0x000B
。
我有两种可能的解决方案:
FOR XML
之外的任何其他方法) - 优先解决方案 或
非常感谢任何帮助。
更新 - 上下文:
这是一个更大的CTE的一部分,我试图通过进行多次除法和模数运算从随机数生成随机字符集。
我将每个数字模数为256,得到结果,将其转换为相应的CHAR()
,然后将数字除以256,依此类推,直到它的模数或除数为0。
最后我想连接所有这些字符。我已经掌握了一切,我只是遇到了这个错误,它不允许我连接CHAR()
生成的字符串。
这可能听起来很奇怪,你可能会说它不是一个SQL任务,你可以用其他语言来做,但我想尝试在SQL中找到一个解决方案,无论性能有多低
答案 0 :(得分:1)
XML PATH
只是用于分组串联的技术之一。 Aaron Bertrand在Grouped Concatenation in SQL Server中解释并比较了所有这些内容。对此的内置支持将以STRING_AGG
的形式出现在下一版本的SQL Server中。
Bertrand的文章解释说XML PATH
只能用于XML安全字符。如果没有首先对数据进行XML编码,则0x1
(SOH)和0xB
(垂直制表符)等不可打印字符将无效。通常,这不是问题,因为实际数据不包含不可打印的字符 - SOH和VT在文本框中会是什么样子?
也许,解决问题的最简单方法是使用UNICODE()
而不是CHAR()
来生成Unicode字符并从32开始而不是0或1。
目前,聚合字符串的最快和最安全的方法是使用SQLCLR自定义聚合。如果你不使用像直接连接字符串这样的粗俗技术,它也会消耗最少的内存。显示in this project的各种GROUP_CONCAT
实现都足够小,你可以在你自己的项目中复制和使用。它们也适用于任何Unicode字符,即使是不可打印的字符。
BTW,SQL Server vNext使STRING_AGG
聚合字符串。我们只需等待一两年。
非订购版本GROUP_CONCAT
只有99行。它只是收集字典中的所有字符串并在结尾处写出来:
using System;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.IO;
using System.Collections.Generic;
using System.Text;
namespace GroupConcat
{
[Serializable]
[SqlUserDefinedAggregate(Format.UserDefined,
MaxByteSize = -1,
IsInvariantToNulls = true,
IsInvariantToDuplicates = false,
IsInvariantToOrder = true,
IsNullIfEmpty = true)]
public struct GROUP_CONCAT : IBinarySerialize
{
private Dictionary<string, int> values;
public void Init()
{
this.values = new Dictionary<string, int>();
}
public void Accumulate([SqlFacet(MaxSize = 4000)] SqlString VALUE)
{
if (!VALUE.IsNull)
{
string key = VALUE.Value;
if (this.values.ContainsKey(key))
{
this.values[key] += 1;
}
else
{
this.values.Add(key, 1);
}
}
}
public void Merge(GROUP_CONCAT Group)
{
foreach (KeyValuePair<string, int> item in Group.values)
{
string key = item.Key;
if (this.values.ContainsKey(key))
{
this.values[key] += Group.values[key];
}
else
{
this.values.Add(key, Group.values[key]);
}
}
}
[return: SqlFacet(MaxSize = -1)]
public SqlString Terminate()
{
if (this.values != null && this.values.Count > 0)
{
StringBuilder returnStringBuilder = new StringBuilder();
foreach (KeyValuePair<string, int> item in this.values)
{
for (int value = 0; value < item.Value; value++)
{
returnStringBuilder.Append(item.Key);
returnStringBuilder.Append(",");
}
}
return returnStringBuilder.Remove(returnStringBuilder.Length - 1, 1).ToString();
}
return null;
}
public void Read(BinaryReader r)
{
int itemCount = r.ReadInt32();
this.values = new Dictionary<string, int>(itemCount);
for (int i = 0; i <= itemCount - 1; i++)
{
this.values.Add(r.ReadString(), r.ReadInt32());
}
}
public void Write(BinaryWriter w)
{
w.Write(this.values.Count);
foreach (KeyValuePair<string, int> s in this.values)
{
w.Write(s.Key);
w.Write(s.Value);
}
}
}
}
答案 1 :(得分:1)
另一种方法(也适用于非printables ):
您要在彼此之后添加一个字符。您根本不需要任何组连接。你的递归(而不是迭代)CTE本身就是一个隐藏的RBAR ,并会为你做这件事。
以下示例使用一个int列表(考虑您需要使用随机数执行此操作的用例)作为输入:
DECLARE @SomeInts TABLE(ID INT IDENTITY,intVal INT);
INSERT INTO @SomeInts VALUES(36),(33),(39),(32),(35),(37),(1),(2),(65);
WITH cte AS
(
SELECT ID,intVal AS nr,CAST(CHAR(intVal) AS VARCHAR(MAX)) AS targetString FROM @SomeInts WHERE ID=1
UNION ALL
SELECT si.ID,intVal + 1,targetString + CHAR(intVal)
FROM @SomeInts AS si
INNER JOIN cte ON si.ID=cte.ID+1
)
SELECT targetString, CAST(targetString AS varbinary(max))
FROM cte
option (maxrecursion 0);
结果(打印和增长的十六进制列表 - &gt;小心x01
和x02
):