连接字符

时间:2017-03-17 12:47:43

标签: sql-server xml

我试图连接一些与一些整数相对应的字符(例如前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之外的任何其他方法) - 优先解决方案

  • 删除XML中不允许的所有字符 - 我已经尝试了这一点,但似乎我只是点击了其他不允许的字符。我还查找了这些不允许的字符列表,但我找不到。

非常感谢任何帮助。

更新 - 上下文:

这是一个更大的CTE的一部分,我试图通过进行多次除法和模数运算从随机数生成随机字符集。

我将每个数字模数为256,得到结果,将其转换为相应的CHAR(),然后将数字除以256,依此类推,直到它的模数或除数为0。

最后我想连接所有这些字符。我已经掌握了一切,我只是遇到了这个错误,它不允许我连接CHAR()生成的字符串。

这可能听起来很奇怪,你可能会说它不是一个SQL任务,你可以用其他语言来做,但我想尝试在SQL中找到一个解决方案,无论性能有多低

2 个答案:

答案 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;小心x01x02):

enter image description here