【问题标题】:error, string or binary data would be truncated when trying to insert尝试插入时,错误、字符串或二进制数据将被截断
【发布时间】:2020-09-30 17:03:58
【问题描述】:

我正在运行带有以下行的 data.bat 文件:

Rem Tis batch file will populate tables

cd\program files\Microsoft SQL Server\MSSQL
osql -U sa -P Password -d MyBusiness -i c:\data.sql

data.sql文件的内容是:

   insert Customers
            (CustomerID, CompanyName, Phone)
             Values('101','Southwinds','19126602729')

还有 8 行类似的添加记录。

当我使用start > run > cmd > c:\data.bat 运行此程序时,我收到以下错误消息:

1>2>3>4>5>....<1 row affected>
Msg 8152, Level 16, State 4, Server SP1001, Line 1
string or binary data would be truncated.

<1 row affected>

<1 row affected>

<1 row affected>

<1 row affected>

<1 row affected>

<1 row affected>

另外,我显然是新手,但是Level #state # 是什么意思,以及如何查找例如上述错误消息:8152?

【问题讨论】:

标签: sql sql-server


【解决方案1】:

来自@gmmastros's answer

每当你看到消息时......

字符串或二进制数据将被截断

想想自己……这个字段不够大,无法容纳我的数据。

检查客户表的表结构。我想您会发现一个或多个字段的长度不足以容纳您尝试插入的数据。例如,如果 Phone 字段是 varchar(8) 字段,并且您尝试将 11 个字符放入其中,您将收到此错误。

【讨论】:

  • 另请注意,受影响的字段可能位于触发器中。希望下次发生这种情况时我能记住这一点......
  • 有什么方法可以在调试中查看哪个字段会被截断?
  • 这个错误是因为你的列不能保存超过你固定的长度的数据。例如; Firstname nvarchar(5) 如果插入超过5个字符会报错
  • SQL Server 的某些版本可以告诉您哪些数据会被截断。使用DBCC TRACEON(460); 启用此功能。 See Aaron Bertrand's answer
  • 我知道这是一个真正的 SQL 异常。但是,异常消息“字符串或二进制数据将被截断”有点误导?它实际上终止了执行而不是截断字符串。
【解决方案2】:

虽然数据长度比字段长度短,但我遇到了这个问题。 事实证明,问题在于有另一个日志表(用于审计跟踪),由主表上的触发器填充,其中列大小也必须更改。

【讨论】:

  • 谢谢。我的是因为 tableA 中的 sql 列是 varchar(100)。它还插入到另一个表中,其中的列是 varchar(50)。
  • 我的情况也发生了同样的问题。触发操作是罪魁祸首。
【解决方案3】:

在其中一个INSERT 语句中,您试图将过长的字符串插入到字符串(varcharnvarchar)列中。

如果仅仅看一下脚本并不能明显看出哪个INSERT 是违规者,您可以计算出现在错误消息之前&lt;1 row affected&gt; 行。获得的数字加一为您提供语句编号。在您的情况下,它似乎是产生错误的第二个 INSERT。

【讨论】:

  • 我也遇到了同样的问题,如何找到导致错误的列?
  • @AndréBastos:也许您可以将其作为问题提交(除非其他人已经这样做了,在这种情况下,某处可能有现成的答案)。
【解决方案4】:

只是想提供更多信息:我遇到了同样的问题,这是因为该字段不足以容纳传入的数据,这个线程帮助我解决了它(最佳答案澄清了这一切)。

但是知道可能导致它的可能原因是非常重要的。

在我的例子中,我正在创建一个带有如下字段的表:

Select '' as  Period, * From Transactions Into #NewTable

因此,“期间”字段的长度为零,导致插入操作失败。我将其更改为“XXXXXX”,即传入数据的长度,它现在可以正常工作(因为字段现在的长度为 6)。

我希望这对遇到同样问题的人有所帮助:)

【讨论】:

    【解决方案5】:

    您的某些数据无法放入您的数据库列(小)。找出问题所在并不容易。如果您使用 C# 和 Linq2Sql,您可以列出将被截断的字段:

    首先创建辅助类:

    public class SqlTruncationExceptionWithDetails : ArgumentOutOfRangeException
    {
        public SqlTruncationExceptionWithDetails(System.Data.SqlClient.SqlException inner, DataContext context)
            : base(inner.Message + " " + GetSqlTruncationExceptionWithDetailsString(context))
        {
        }
    
        /// <summary>
        /// PArt of code from following link
        /// http://stackoverflow.com/questions/3666954/string-or-binary-data-would-be-truncated-linq-exception-cant-find-which-fiel
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        static string GetSqlTruncationExceptionWithDetailsString(DataContext context)
        {
            StringBuilder sb = new StringBuilder();
    
            foreach (object update in context.GetChangeSet().Updates)
            {
                FindLongStrings(update, sb);
            }
    
            foreach (object insert in context.GetChangeSet().Inserts)
            {
                FindLongStrings(insert, sb);
            }
            return sb.ToString();
        }
    
        public static void FindLongStrings(object testObject, StringBuilder sb)
        {
            foreach (var propInfo in testObject.GetType().GetProperties())
            {
                foreach (System.Data.Linq.Mapping.ColumnAttribute attribute in propInfo.GetCustomAttributes(typeof(System.Data.Linq.Mapping.ColumnAttribute), true))
                {
                    if (attribute.DbType.ToLower().Contains("varchar"))
                    {
                        string dbType = attribute.DbType.ToLower();
                        int numberStartIndex = dbType.IndexOf("varchar(") + 8;
                        int numberEndIndex = dbType.IndexOf(")", numberStartIndex);
                        string lengthString = dbType.Substring(numberStartIndex, (numberEndIndex - numberStartIndex));
                        int maxLength = 0;
                        int.TryParse(lengthString, out maxLength);
    
                        string currentValue = (string)propInfo.GetValue(testObject, null);
    
                        if (!string.IsNullOrEmpty(currentValue) && maxLength != 0 && currentValue.Length > maxLength)
                        {
                            //string is too long
                            sb.AppendLine(testObject.GetType().Name + "." + propInfo.Name + " " + currentValue + " Max: " + maxLength);
                        }
    
                    }
                }
            }
        }
    }
    

    然后为 SubmitChanges 准备包装器:

    public static class DataContextExtensions
    {
        public static void SubmitChangesWithDetailException(this DataContext dataContext)
        {
            //http://stackoverflow.com/questions/3666954/string-or-binary-data-would-be-truncated-linq-exception-cant-find-which-fiel
            try
            {
                //this can failed on data truncation
                dataContext.SubmitChanges();
            }       
            catch (SqlException sqlException) //when (sqlException.Message == "String or binary data would be truncated.")
            {
    
                if (sqlException.Message == "String or binary data would be truncated.") //only for EN windows - if you are running different window language, invoke the sqlException.getMessage on thread with EN culture
                    throw new SqlTruncationExceptionWithDetails(sqlException, dataContext);
                else
                    throw;
            }
        }
    }
    

    准备全局异常处理程序和日志截断详细信息:

    protected void Application_Error(object sender, EventArgs e)
    {
        Exception ex = Server.GetLastError();
        string message = ex.Message;
        //TODO - log to file
    }
    

    最后使用代码:

    Datamodel.SubmitChangesWithDetailException();
    

    【讨论】:

    • 您的解决方案需要在实体模型中定义字段宽度信息(例如:[Column("SomefieldName", TypeName = "varchar(10)")] 属性中的[Column("SomefieldName", TypeName = "varchar(10)")] 注释),所以很遗憾这对我来说不起作用。所以我继续创建方法来直接从数据库表中获取列宽信息。在这里查看答案:stackoverflow.com/a/71329931/8644294
    【解决方案6】:

    您可能会收到此错误的另一种情况如下:

    我遇到了同样的错误,原因是在从 UNION 接收数据的 INSERT 语句中,列的顺序与原始表不同。如果将#table3中的顺序改为a、b、c,就可以修复错误了。

    select a, b, c into #table1
    from #table0
    
    insert into #table1
        select a, b, c from #table2
        union
        select a, c, b from #table3
    

    【讨论】:

      【解决方案7】:

      在 sql server 上,您可以像这样使用 SET ANSI_WARNINGS OFF:

              using (SqlConnection conn = new SqlConnection("Data Source=XRAYGOAT\\SQLEXPRESS;Initial Catalog='Healthy Care';Integrated Security=True"))
              {
                  conn.Open();
      
                  using (var trans = conn.BeginTransaction())
                  {
                      try
                      {
                          using cmd = new SqlCommand("", conn, trans))
                          { 
      
                          cmd.CommandText = "SET ANSI_WARNINGS OFF";
                          cmd.ExecuteNonQuery();
      
                          cmd.CommandText = "YOUR INSERT HERE";
                          cmd.ExecuteNonQuery();
      
                          cmd.Parameters.Clear();
      
                          cmd.CommandText = "SET ANSI_WARNINGS ON";
                          cmd.ExecuteNonQuery();
      
                          trans.Commit();
                          }
                      }
                      catch (Exception)
                      {
      
                          trans.Rollback();
                      }
      
                  }
      
                  conn.Close();
      
              }
      

      【讨论】:

      • 使用SET ANSI_WARNINGS OFF 正是我需要的诊断工具。不过我不打算在生产中使用。
      【解决方案8】:

      我遇到了同样的问题。我的专栏的长度太短了。

      您可以做的是增加长度或缩短要放入数据库的文本。

      【讨论】:

        【解决方案9】:

        在 Web 应用程序表面上也出现了这个问题。 最终发现同样的错误信息来自于具体表中的SQL更新语句。

        最后发现相关历史表中的列定义在某些特定情况下并没有映射nvarchar类型的原始表列长度。

        【讨论】:

          【解决方案10】:

          我遇到了同样的问题,即使在增加了表中有问题的列的大小之后也是如此。

          tl;dr:相应的Table Types中匹配列的长度也可能需要增加。

          在我的例子中,错误来自 Microsoft Dynamics CRM 中的数据导出服务,该服务允许将 CRM 数据同步到 SQL Server DB 或 Azure SQL DB。

          经过长时间的调查,我得出结论,数据导出服务一定是使用Table-Valued Parameters

          您可以使用表值参数将多行数据发送到 Transact-SQL 语句或例程(例如存储过程或函数),而无需创建临时表或许多参数。

          正如您在上面的文档中看到的,表类型用于创建数据摄取过程:

          CREATE TYPE LocationTableType AS TABLE (...);
          CREATE PROCEDURE dbo.usp_InsertProductionLocation
            @TVP LocationTableType READONLY
          

          不幸的是,无法更改表类型,因此必须完全删除并重新创建它。由于我的表有超过 300 个字段(?),我创建了一个查询,以方便根据表的列定义创建相应的表类型(只需将 [table_name] 替换为您的表名):

          SELECT 'CREATE TYPE [table_name]Type AS TABLE (' + STRING_AGG(CAST(field AS VARCHAR(max)), ',' + CHAR(10)) + ');' AS create_type
          FROM (
            SELECT TOP 5000 COLUMN_NAME + ' ' + DATA_TYPE
                + IIF(CHARACTER_MAXIMUM_LENGTH IS NULL, '', CONCAT('(', IIF(CHARACTER_MAXIMUM_LENGTH = -1, 'max', CONCAT(CHARACTER_MAXIMUM_LENGTH,'')), ')'))
                + IIF(DATA_TYPE = 'decimal', CONCAT('(', NUMERIC_PRECISION, ',', NUMERIC_SCALE, ')'), '')
                AS field
            FROM INFORMATION_SCHEMA.COLUMNS
            WHERE TABLE_NAME = '[table_name]'
            ORDER BY ORDINAL_POSITION) AS T;
          

          更新表格类型后,数据导出服务再次开始正常运行! :)

          【讨论】:

            【解决方案11】:

            当我尝试执行我的存储过程时,我遇到了同样的问题,因为我需要添加一些数据的列的大小比我要添加的数据短。

            您可以增加列数据类型的大小或减少数据的长度。

            【讨论】:

              【解决方案12】:

              可能发生此错误的另一种情况是 SQL Server 管理工作室。如果您的表格中有“text”或“ntext”字段, 无论您要更新哪种类型的字段(例如位或整数)。 似乎 Studio 不会加载整个“ntext”字段,并且还会更新所有字段而不是修改后的字段。 要解决此问题,请从 Management Studio 中的查询中排除“text”或“ntext”字段

              【讨论】:

              • 请考虑通过添加逗号、句点和修正语法错误来重新表述您的答案。
              • 这个答案对我有帮助 - 我的 nvarchar 字段足够大,但我有一个 ntext 字段。 Management Studio / SMSS 中似乎有一些错误。
              【解决方案13】:

              2016/2017 年更新将向您显示错误的值和列。

              新的跟踪标志会将旧错误替换为新的 2628 错误,并将打印出列和违规值。 Traceflag 460 在 2016 年和 2017 年的最新累积更新中可用:

              https://support.microsoft.com/en-sg/help/4468101/optional-replacement-for-string-or-binary-data-would-be-truncated

              只需确保在安装 CU 后启用跟踪标志,无论是全局/永久在服务器上:

              ...或使用 DBCC TRACEON:

              https://docs.microsoft.com/en-us/sql/t-sql/database-console-commands/dbcc-traceon-trace-flags-transact-sql?view=sql-server-ver15

              【讨论】:

                【解决方案14】:

                Kevin Pope's 接受的答案下的评论是我需要的。

                就我而言,问题是我在表上定义了将更新/插入事务插入审计表的触发器,但审计表的数据类型不匹配,其中原始列中的 VARCHAR(MAX)表在审计表中存储为 VARCHAR(1),因此当我在原始表列中插入大于 VARCHAR(1) 的任何内容时,我的触发器失败,我会收到此错误消息。

                【讨论】:

                  【解决方案15】:

                  我使用了不同的策略,在某些地方分配了 8K 的字段。这里只使用了大约 50/100。

                  declare @NVPN_list as table 
                  nvpn            varchar(50)
                  ,nvpn_revision  varchar(5)
                  ,nvpn_iteration INT
                  ,mpn_lifecycle  varchar(30)
                  ,mfr            varchar(100)
                  ,mpn            varchar(50)
                  ,mpn_revision   varchar(5)
                  ,mpn_iteration  INT
                  -- ...
                  ) INSERT INTO @NVPN_LIST 
                  SELECT  left(nvpn           ,50)    as nvpn
                          ,left(nvpn_revision ,10)    as nvpn_revision
                          ,nvpn_iteration
                          ,left(mpn_lifecycle ,30)
                          ,left(mfr           ,100)
                          ,left(mpn           ,50)
                          ,left(mpn_revision  ,5)
                          ,mpn_iteration
                          ,left(mfr_order_num ,50)
                  FROM [DASHBOARD].[dbo].[mpnAttributes] (NOLOCK) mpna
                  

                  我想要速度,因为我总共有 100 万条记录,并加载了 28K 条记录。

                  【讨论】:

                    【解决方案16】:

                    此错误可能是由于字段大小小于您输入的数据。

                    例如如果您的数据类型为 nvarchar(7) 并且您的值为 'aaaaddddf' 则错误显示为:

                    字符串或二进制数据将被截断

                    【讨论】:

                      【解决方案17】:

                      在这方面你根本无法击败 SQL Server。

                      您可以像这样插入到新表中:

                      select foo, bar
                      into tmp_new_table_to_dispose_later
                      from my_table
                      

                      将表定义与要插入数据的真实表进行比较

                      有时有用有时没用。

                      如果您尝试从该临时表插入最终/实际表,它可能会正常工作(例如,由于数据转换的工作方式与 SSMS 不同)。

                      另一种选择是将数据插入块中,而不是立即插入您使用top 1000 插入的所有内容,然后重复该过程,直到找到有错误的块。至少您可以更好地了解不适合表格的内容。

                      【讨论】:

                        【解决方案18】:

                        如果有人在 C# 应用程序中遇到此错误,我创建了以下方法:

                        1. 获取我们尝试进行此插入/更新的表的所有列的列宽。 (我直接从数据库中获取此信息。)
                        2. 将列宽与我们尝试插入/更新的值的宽度进行比较。

                        第 1 步:

                        直接从数据库中获取一个表所有列的列宽

                        // I took HUGE help from this Microsoft docs website: - AshishK
                        // https://docs.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlconnection.getschema?view=netframework-4.7.2#System_Data_SqlClient_SqlConnection_GetSchema_System_String_System_String___
                        private static Dictionary<string, int> GetColumnSizesOfTableFromDatabase(string tableName, string connectionString)
                        {
                            var columnSizes = new Dictionary<string, int>();
                                    
                            using (var connection = new SqlConnection(connectionString))
                            {
                                // Connect to the database then retrieve the schema information.  
                                connection.Open();
                        
                                // You can specify the Catalog, Schema, Table Name, Column Name to get the specified column(s).
                                // You can use four restrictions for Column, so you should create a 4 members array.
                                String[] columnRestrictions = new String[4];
                        
                                // For the array, 0-member represents Catalog; 1-member represents Schema;
                                // 2-member represents Table Name; 3-member represents Column Name.
                                // Now we specify the Table_Name and Column_Name of the columns what we want to get schema information.
                                columnRestrictions[2] = tableName;
                        
                                DataTable allColumnsSchemaTable = connection.GetSchema("Columns", columnRestrictions);
                        
                                foreach (DataRow row in allColumnsSchemaTable.Rows)
                                {
                                    var columnName = row.Field<string>("COLUMN_NAME");
                                    var dataType = row.Field<string>("DATA_TYPE");
                                    var characterMaxLength = row.Field<int?>("CHARACTER_MAXIMUM_LENGTH");
                        
                                    // I'm only capturing columns whose Datatype is "varchar" or "char", i.e. their CHARACTER_MAXIMUM_LENGTH won't be null.
                                    if(characterMaxLength != null)
                                    {
                                        columnSizes.Add(columnName, characterMaxLength.Value);
                                    }
                                }
                        
                                connection.Close();
                            }
                        
                            return columnSizes;
                        }
                        

                        第 2 步:

                        将列宽与我们尝试插入/更新的值的宽度进行比较:

                        public static Dictionary<string, string> FindLongBinaryOrStringFields<T>(T entity, string connectionString)
                        {
                            var tableName = typeof(T).Name;
                            Dictionary<string, string> longFields = new Dictionary<string, string>();
                            var objectProperties = GetProperties(entity);
                            //var fieldNames = objectProperties.Select(p => p.Name).ToList();
                        
                            var actualDatabaseColumnSizes = GetColumnSizesOfTableFromDatabase(tableName, connectionString);
                                    
                            foreach (var dbColumn in actualDatabaseColumnSizes)
                            {
                                var maxLengthOfThisColumn = dbColumn.Value;
                                var currentValueOfThisField = objectProperties.Where(f => f.Name == dbColumn.Key).First()?.GetValue(entity, null)?.ToString();
                        
                                if (!string.IsNullOrEmpty(currentValueOfThisField) && currentValueOfThisField.Length > maxLengthOfThisColumn)
                                {
                                    longFields.Add(dbColumn.Key, $"'{dbColumn.Key}' column cannot take the value of '{currentValueOfThisField}' because the max length it can take is {maxLengthOfThisColumn}.");
                                }
                            }
                        
                            return longFields;
                        }
                        
                        public static List<PropertyInfo> GetProperties<T>(T entity)
                        {
                            //The DeclaredOnly flag makes sure you only get properties of the object, not from the classes it derives from.
                            var properties = entity.GetType()
                                                    .GetProperties(System.Reflection.BindingFlags.Public
                                                    | System.Reflection.BindingFlags.Instance
                                                    | System.Reflection.BindingFlags.DeclaredOnly)
                                                    .ToList();
                        
                            return properties;
                        }
                        

                        用法:

                        假设我们正在尝试插入在我们的应用中建模的 SomeTable 类的 someTableEntity,如下所示:

                        public class SomeTable
                        {
                            [Key]
                            public long TicketID { get; set; }
                            public string SourceData { get; set; }
                        }
                        

                        它在我们的SomeDbContext 里面,就像这样:

                        public class SomeDbContext : DbContext
                        {
                            public DbSet<SomeTable> SomeTables { get; set; }
                        }
                        

                        Db 中的此表具有 SourceData 字段为 varchar(16),如下所示:

                        现在我们将尝试在此字段中插入超过 16 个字符的值并捕获此信息:

                        public void SaveSomeTableEntity()
                        {
                            var connectionString = "server=SERVER_NAME;database=DB_NAME;User ID=SOME_ID;Password=SOME_PASSWORD;Connection Timeout=200";
                                
                            using (var context = new SomeDbContext(connectionString))
                            {
                                var someTableEntity = new SomeTable()
                                {
                                    SourceData = "Blah-Blah-Blah-Blah-Blah-Blah"
                                };
                                
                                context.SomeTables.Add(someTableEntity);
                                
                                try
                                {
                                    context.SaveChanges();
                                }
                                catch (Exception ex)
                                {
                                    if (ex.GetBaseException().Message == "String or binary data would be truncated.\r\nThe statement has been terminated.")
                                    {
                                        var badFieldsReport = "";
                                        List<string> badFields = new List<string>();
                                        
                                        // YOU GOT YOUR FIELDS RIGHT HERE:
                                        var longFields = FindLongBinaryOrStringFields(someTableEntity, connectionString);
                        
                                        foreach (var longField in longFields)
                                        {
                                            badFields.Add(longField.Key);
                                            badFieldsReport += longField.Value + "\n";
                                        }
                                    }
                                    else
                                        throw;
                                }
                            }
                        }
                        

                        badFieldsReport 将具有此值:

                        “SourceData”列不能取值 'Blah-Blah-Blah-Blah-Blah-Blah' 因为它可以采取的最大长度是 16.

                        【讨论】:

                          猜你喜欢
                          • 1970-01-01
                          • 2017-01-15
                          • 1970-01-01
                          • 2015-04-29
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 2017-09-10
                          相关资源
                          最近更新 更多