尝试插入时,错误、字符串或二进制数据将被截断

Posted

技术标签:

【中文标题】尝试插入时,错误、字符串或二进制数据将被截断【英文标题】: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?

【问题讨论】:

值得注意的是,现在有一个选项可以用包含实际列名的新错误替换这个过于笼统的错误。见***.com/a/63474873/852208 【参考方案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://***.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://***.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)")] 注释),所以很遗憾这对我来说不起作用。所以我继续创建方法来直接从数据库表中获取列宽信息。在这里查看答案:***.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 @N***_list as table 
n***            varchar(50)
,n***_revision  varchar(5)
,n***_iteration INT
,mpn_lifecycle  varchar(30)
,mfr            varchar(100)
,mpn            varchar(50)
,mpn_revision   varchar(5)
,mpn_iteration  INT
-- ...
) INSERT INTO @N***_LIST 
SELECT  left(n***           ,50)    as n***
        ,left(n***_revision ,10)    as n***_revision
        ,n***_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 步:

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

// 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.

【讨论】:

以上是关于尝试插入时,错误、字符串或二进制数据将被截断的主要内容,如果未能解决你的问题,请参考以下文章

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

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

尽管数据看似合适,但插入时“字符串或二进制数据将被截断”

用户将在 SQL Server 中截断字符串或​​二进制数据

SQL 字符串或二进制数据将被截断,列名

SQL Server (0x80131904):字符串或二进制数据将被截断 [重复]