SQL Server String or binary data would be truncated - sql-server

I am involved in a data migration project. I am getting the following error when I try to insert data from one table into another table (SQL Server 2005):
Msg 8152, Level 16, State 13, Line 1
String or binary data would be truncated.
The source data columns match the data type and are within the length definitions of the destination table columns so I am at a loss as to what could be causing this error.

You will need to post the table definitions for the source and destination tables for us to figure out where the issue is but the bottom line is that one of your columns in the source table is bigger than your destination columns. It could be that you are changing formats in a way you were not aware of. The database model you are moving from is important in figuring that out as well.

As others have already said, one of your columns datatypes in the source table is larger than your destination columns.
A simple solution is to turn off the warning and allow truncation to take place. So, if you're receiving this error but you are sure it is acceptable for data in your old database/table to be truncated (cut to size) you can simply do the following;
SET ANSI_WARNINGS OFF;
-- Your insert TSQL here.
SET ANSI_WARNINGS ON;
As above, always remember to turn warnings back on again afterwards.

The issue is quite simple: one or more of the columns in the source query contains data that exceeds the length of its destination column. A simple solution would be to take your source query and execute Max(Len( source col )) on each column. I.e.,
Select Max(Len(TextCol1))
, Max(Len(TextCol2))
, Max(Len(TextCol3))
, ...
From ...
Then compare those lengths to the data type lengths in your destination table. At least one, exceeds its destination column length.
If you are absolutely positive that this should not be the case and do not care if it is not the case, then another solution is to forcibly cast the source query columns to their destination length (which will truncate any data that is too long):
Select Cast(TextCol1 As varchar(...))
, Cast(TextCol2 As varchar(...))
, Cast(TextCol3 As varchar(...))
, ...
From ...

SQL Server 2019 will finally return more meaningful error message.
Binary or string data would be truncated => error message enhancments
if you have that error (in production), it's not obvious to see which column or row this error comes from, and how to locate it exactly.
To enable new behavior you need to use DBCC TRACEON(460). New error text from sys.messages:
SELECT * FROM sys.messages WHERE message_id = 2628
2628 – String or binary data would be truncated in table ‘%.*ls’, column ‘%.*ls’. Truncated value: ‘%.*ls’.
String or Binary data would be truncated: replacing the infamous error 8152
This new message is also backported to SQL Server 2017 CU12 (and in an upcoming SQL Server 2016 SP2 CU), but not by default. You need to enable trace flag 460 to replace message ID 8152 with 2628, either at the session or server level.
Note that for now, even in SQL Server 2019 CTP 2.0 the same trace flag 460 needs to be enabled. In a future SQL Server 2019 release, message 2628 will replace message 8152 by default.
SQL Server 2017 CU12 also supports this feature.
Improvement: Optional replacement for "String or binary data would be truncated" message with extended information in SQL Server 2017
This SQL Server 2017 update introduces an optional message that contains the following additional context information.
Msg 2628, Level 16, State 6, Procedure ProcedureName, Line Linenumber
String or binary data would be truncated in table '%.*ls', column '%.*ls'.
Truncated value: '%.*ls'.
The new message ID is 2628. This message replaces message 8152 in any error output if trace flag 460 is enabled.
db<>fiddle demo
ALTER DATABASE SCOPED CONFIGURATION
VERBOSE_TRUNCATION_WARNINGS = { ON | OFF }
APPLIES TO: SQL Server (Starting with SQL Server 2019 (15.x)) and Azure SQL Database
Allows you to enable or disable the new String or binary data would be
truncated error message. SQL Server 2019 (15.x) introduces a new, more
specific error message (2628) for this scenario:
String or binary data would be truncated in table '%.*ls', column'%.*ls'. Truncated value: '%.*ls'.
When set to ON under database compatibility level 150, truncation
errors raise the new error message 2628 to provide more context and
simplify the troubleshooting process.
When set to OFF under database compatibility level 150, truncation
errors raise the previous error message 8152.
For database compatibility level 140 or lower, error message 2628
remains an opt-in error message that requires trace flag 460 to be
enabled, and this database scoped configuration has no effect.

One other potential reason for this is if you have a default value setup for a column that exceeds the length of the column. It appears someone fat fingered a column that had a length of 5 but the default value exceeded the length of 5. This drove me nuts as I was trying to understand why it wasn't working on any insert, even if all i was inserting was a single column with an integer of 1. Because the default value on the table schema had that violating default value it messed it all up - which I guess brings us to the lesson learned - avoid having tables with default value's in the schema. :)

Here is a slightly different answer. Your column names & lengths may all match, but perhaps you are specifying the columns in the wrong order in your SELECT statement. Say tableX and tableY have columns with the same name, but in different order

I am going to add one other possible cause of this error just because no one has mentioned it and it might help some future person (since the OP has found his answer). If the table you are inserting into has triggers, it could be the trigger is generating the error. I have seen this happen when table field definitions were changed, but audit tables were not.

If you’re on SQL Server 2016-2017:
to fix it, turn on trace flag 460
DBCC TRACEON(460, 1);
GO
and make sure you turn it off after:
DBCC TRACEOFF(460, 1);
GO
source

For the others, also check your stored procedure. In my case in my stored procedure CustomSearch I accidentally declared not enough length for my column, so when I entered a big data I received that error even though I have a big length on my database. I just changed the length of my column in my custom search the error goes away. This is just for the reminder. Thanks.

This can be a challenging error. Here are some notes taken from https://connect.microsoft.com/SQLServer/feedback/details/339410/ look for AmirCharania's comment.
I've adjusted the answer given by AmirCharania for data selected into an actual table, instead of a temp one. First select your dataset into a development table then run the following:
WITH CTE_Dev
AS (
SELECT C.column_id
,ColumnName = C.NAME
,C.max_length
,C.user_type_id
,C.precision
,C.scale
,DataTypeName = T.NAME
FROM sys.columns C
INNER JOIN sys.types T ON T.user_type_id = C.user_type_id
WHERE OBJECT_ID = OBJECT_ID('YOUR TARGET TABLE NAME HERE, WITH SCHEMA')
)
,CTE_Temp
AS (
SELECT C.column_id
,ColumnName = C.NAME
,C.max_length
,C.user_type_id
,C.precision
,C.scale
,DataTypeName = T.NAME
FROM sys.columns C
INNER JOIN sys.types T ON T.user_type_id = C.user_type_id
WHERE OBJECT_ID = OBJECT_ID('YOUR TEMP TABLE NAME HERE, WITH SCHEMA')
)
SELECT *
FROM CTE_Dev D
FULL OUTER JOIN CTE_Temp T ON D.ColumnName = T.ColumnName
WHERE ISNULL(D.max_length, 0) < ISNULL(T.max_length, 999)

Yes,I am also face these kind of problem.
REMARKS VARCHAR(500)
to
REMARKS VARCHAR(1000)
Here, I've change REMARKS filed length from 500 to 1000

Yep - "a pint into a half-pint pot will not go". I've not had much luck (for whatever reason) with the various SPs that folks have suggested, BUT as long as the two tables are in the same DB (or you can get them into the same DB), you can use INFORMATION_SCHEMA.COLUMNS to locate the errant field(s), thusly:
select c1.table_name,c1.COLUMN_NAME,c1.DATA_TYPE,c1.CHARACTER_MAXIMUM_LENGTH,c2.table_name,c2.COLUMN_NAME, c2.DATA_TYPE,c2.CHARACTER_MAXIMUM_LENGTH
from [INFORMATION_SCHEMA].[COLUMNS] c1
left join [INFORMATION_SCHEMA].[COLUMNS] c2 on
c1.COLUMN_NAME=c2.COLUMN_NAME
where c1.TABLE_NAME='MyTable1'
and c2.TABLE_NAME='MyTable2'
--and c1.DATA_TYPE<>c2.DATA_TYPE
--and c1.CHARACTER_MAXIMUM_LENGTH <> c2.CHARACTER_MAXIMUM_LENGTH
order by c1.COLUMN_NAME
This will let you scroll up and down, comparing field lengths as you go. The commented sections let you see (once uncommented, obviously) if there are data type mismatches, or specifically show those that differ in field length - cos I'm too lazy to scroll - just be aware that the whole thing is predicated on the source column names matching those of the target.

I came across this problem today, and in my search for an answer to this minimal informative error message i also found this link:
https://connect.microsoft.com/SQLServer/feedback/details/339410/please-fix-the-string-or-binary-data-would-be-truncated-message-to-give-the-column-name
So it seems microsoft has no plans to expand on error message anytime soon.
So i turned to other means.
I copied the errors to excel:
(1 row(s) affected)
(1 row(s) affected)
(1 row(s) affected)
Msg 8152, Level 16, State 14, Line 13
String or binary data would be truncated.
The statement has been terminated.
(1 row(s) affected)
counted the number of rows in excel, got to close to the records counter that caused the problem... adjusted my export code to print out the SQL close to it... then ran the 5 - 10 sql inserts around the problem sql and managed to pinpoint the problem one, see the string that was too long, increase size of that column and then big import file ran no problem.
Bit of a hack and a workaround, but when you left with very little choice you do what you can.

I was using empty string '' on on table creation and then receiving error 'Msg 8152, String or binary data would be truncated' on subsequent update. This was happening due to the update value containing 6 characters and being larger than the column definition anticipated. I used "SPACE" to get around this only because I knew I would be updating in bulk following the initial data creation i.e. the column was not going to remain empty for long.
SO BIG CAVEAT HERE: This is not a particularly slick solution but is useful in the case where you are pulling together a data set e.g. for one-off intelligence requests where you are creating a table for data mining, applying some bulk processing/interpretation and storing before and after results for later comparison/mining. This is a frequent occurrence in my line of work.
You can initially populate using the SPACE keyword i.e.
select
Table1.[column1]
,Table1.[column2]
,SPACE(10) as column_name
into table_you_are_creating
from Table1
where ...
Subsequent updates to "column_name" of 10 characters or less (substitute as applicable) will then be allowed without causing truncate error. Again, I would only use this in scenarios similar to that described in my caveat.

I've built a stored procedure that analyses a source table or query with several characteristics per column among which the minimum length (min_len) and maximum length (max_len).
CREATE PROCEDURE [dbo].[sp_analysetable] (
#tableName varchar(8000),
#deep bit = 0
) AS
/*
sp_analysetable 'company'
sp_analysetable 'select * from company where name is not null'
*/
DECLARE #intErrorCode INT, #errorMSG VARCHAR(500), #tmpQ NVARCHAR(2000), #column_name VARCHAR(50), #isQuery bit
SET #intErrorCode=0
IF OBJECT_ID('tempdb..##tmpTableToAnalyse') IS NOT NULL BEGIN
DROP TABLE ##tmpTableToAnalyse
END
IF OBJECT_ID('tempdb..##tmpColumns') IS NOT NULL BEGIN
DROP TABLE ##tmpColumns
END
if CHARINDEX('from', #tableName)>0
set #isQuery=1
IF #intErrorCode=0 BEGIN
if #isQuery=1 begin
--set #tableName = 'USE '+#db+';'+replace(#tableName, 'from', 'into ##tmpTableToAnalyse from')
--replace only first occurance. Now multiple froms may exists, but first from will be replaced with into .. from
set #tableName=Stuff(#tableName, CharIndex('from', #tableName), Len('from'), 'into ##tmpTableToAnalyse from')
exec(#tableName)
IF OBJECT_ID('tempdb..##tmpTableToAnalyse') IS NULL BEGIN
set #intErrorCode=1
SET #errorMSG='Error generating temporary table from query.'
end
else begin
set #tableName='##tmpTableToAnalyse'
end
end
end
IF #intErrorCode=0 BEGIN
SET #tmpQ='USE '+DB_NAME()+';'+CHAR(13)+CHAR(10)+'
select
c.column_name as [column],
cast(sp.value as varchar(1000)) as description,
tc_fk.constraint_type,
kcu_pk.table_name as fk_table,
kcu_pk.column_name as fk_column,
c.ordinal_position as pos,
c.column_default as [default],
c.is_nullable as [null],
c.data_type,
c.character_maximum_length as length,
c.numeric_precision as [precision],
c.numeric_precision_radix as radix,
cast(null as bit) as [is_unique],
cast(null as int) as min_len,
cast(null as int) as max_len,
cast(null as int) as nulls,
cast(null as int) as blanks,
cast(null as int) as numerics,
cast(null as int) as distincts,
cast(null as varchar(500)) as distinct_values,
cast(null as varchar(50)) as remarks
into ##tmpColumns'
if #isQuery=1 begin
SET #tmpQ=#tmpQ+' from tempdb.information_schema.columns c, (select null as value) sp'
end
else begin
SET #tmpQ=#tmpQ+'
from information_schema.columns c
left join sysobjects so on so.name=c.table_name and so.xtype=''U''
left join syscolumns sc on sc.name=c.column_name and sc.id =so.id
left join sys.extended_properties sp on sp.minor_id = sc.colid AND sp.major_id = sc.id and sp.name=''MS_Description''
left join information_schema.key_column_usage kcu_fk on kcu_fk.table_name = c.table_name and c.column_name = kcu_fk.column_name
left join information_schema.table_constraints tc_fk on kcu_fk.table_name = tc_fk.table_name and kcu_fk.constraint_name = tc_fk.constraint_name
left join information_schema.referential_constraints rc on rc.constraint_name = kcu_fk.constraint_name
left join information_schema.table_constraints tc_pk on rc.unique_constraint_name = tc_pk.constraint_name
left join information_schema.key_column_usage kcu_pk on tc_pk.constraint_name = kcu_pk.constraint_name
'
end
SET #tmpQ=#tmpQ+' where c.table_name = '''+#tableName+''''
exec(#tmpQ)
end
IF #intErrorCode=0 AND #deep = 1 BEGIN
DECLARE
#count_rows int,
#count_distinct int,
#count_nulls int,
#count_blanks int,
#count_numerics int,
#min_len int,
#max_len int,
#distinct_values varchar(500)
DECLARE curTmp CURSOR LOCAL FAST_FORWARD FOR
select [column] from ##tmpColumns;
OPEN curTmp
FETCH NEXT FROM curTmp INTO #column_name
WHILE ##FETCH_STATUS = 0 and #intErrorCode=0 BEGIN
set #tmpQ = 'USE '+DB_NAME()+'; SELECT'+
' #count_rows=count(0), '+char(13)+char(10)+
' #count_distinct=count(distinct ['+#column_name+']),'+char(13)+char(10)+
' #count_nulls=sum(case when ['+#column_name+'] is null then 1 else 0 end),'+char(13)+char(10)+
' #count_blanks=sum(case when ltrim(['+#column_name+'])='''' then 1 else 0 end),'+char(13)+char(10)+
' #count_numerics=sum(isnumeric(['+#column_name+'])),'+char(13)+char(10)+
' #min_len=min(len(['+#column_name+'])),'+char(13)+char(10)+
' #max_len=max(len(['+#column_name+']))'+char(13)+char(10)+
' from ['+#tableName+']'
exec sp_executesql #tmpQ,
N'#count_rows int OUTPUT,
#count_distinct int OUTPUT,
#count_nulls int OUTPUT,
#count_blanks int OUTPUT,
#count_numerics int OUTPUT,
#min_len int OUTPUT,
#max_len int OUTPUT',
#count_rows OUTPUT,
#count_distinct OUTPUT,
#count_nulls OUTPUT,
#count_blanks OUTPUT,
#count_numerics OUTPUT,
#min_len OUTPUT,
#max_len OUTPUT
IF (#count_distinct>10) BEGIN
SET #distinct_values='Many ('+cast(#count_distinct as varchar)+')'
END ELSE BEGIN
set #distinct_values=null
set #tmpQ = N'USE '+DB_NAME()+';'+
' select #distinct_values=COALESCE(#distinct_values+'',''+cast(['+#column_name+'] as varchar), cast(['+#column_name+'] as varchar))'+char(13)+char(10)+
' from ('+char(13)+char(10)+
' select distinct ['+#column_name+'] from ['+#tableName+'] where ['+#column_name+'] is not null) a'+char(13)+char(10)
exec sp_executesql #tmpQ,
N'#distinct_values varchar(500) OUTPUT',
#distinct_values OUTPUT
END
UPDATE ##tmpColumns SET
is_unique =case when #count_rows=#count_distinct then 1 else 0 end,
distincts =#count_distinct,
nulls =#count_nulls,
blanks =#count_blanks,
numerics =#count_numerics,
min_len =#min_len,
max_len =#max_len,
distinct_values=#distinct_values,
remarks =
case when #count_rows=#count_nulls then 'all null,' else '' end+
case when #count_rows=#count_distinct then 'unique,' else '' end+
case when #count_distinct=0 then 'empty,' else '' end+
case when #min_len=#max_len then 'same length,' else '' end+
case when #count_rows=#count_numerics then 'all numeric,' else '' end
WHERE [column]=#column_name
FETCH NEXT FROM curTmp INTO #column_name
END
CLOSE curTmp DEALLOCATE curTmp
END
IF #intErrorCode=0 BEGIN
select * from ##tmpColumns order by pos
end
IF #intErrorCode=0 BEGIN --Clean up temporary tables
IF OBJECT_ID('tempdb..##tmpTableToAnalyse') IS NOT NULL BEGIN
DROP TABLE ##tmpTableToAnalyse
END
IF OBJECT_ID('tempdb..##tmpColumns') IS NOT NULL BEGIN
DROP TABLE ##tmpColumns
END
end
IF #intErrorCode<>0 BEGIN
RAISERROR(#errorMSG, 12, 1)
END
RETURN #intErrorCode
I store this procedure in the master database so that I can use it in every database like so:
sp_analysetable 'table_name', 1
// deep=1 for doing value analyses
And the output is:
column description constraint_type fk_table fk_column pos default null data_type length precision radix is_unique min_len max_len nulls blanks numerics distincts distinct_values remarks
id_individual NULL PRIMARY KEY NULL NULL 1 NULL NO int NULL 10 10 1 1 2 0 0 70 70 Many (70) unique,all numeric,
id_brand NULL NULL NULL NULL 2 NULL NO int NULL 10 10 0 1 1 0 0 70 2 2,3 same length,all numeric,
guid NULL NULL NULL NULL 3 (newid()) NO uniqueidentifier NULL NULL NULL 1 36 36 0 0 0 70 Many (70) unique,same length,
customer_id NULL NULL NULL NULL 4 NULL YES varchar 50 NULL NULL 0 NULL NULL 70 0 0 0 NULL all null,empty,
email NULL NULL NULL NULL 5 NULL YES varchar 100 NULL NULL 0 4 36 0 0 0 31 Many (31)
mobile NULL NULL NULL NULL 6 NULL YES varchar 50 NULL NULL 0 NULL NULL 70 0 0 0 NULL all null,empty,
initials NULL NULL NULL NULL 7 NULL YES varchar 50 NULL NULL 0 NULL NULL 70 0 0 0 NULL all null,empty,
title_short NULL NULL NULL NULL 8 NULL YES varchar 50 NULL NULL 0 NULL NULL 70 0 0 0 NULL all null,empty,
title_long NULL NULL NULL NULL 9 NULL YES varchar 50 NULL NULL 0 NULL NULL 70 0 0 0 NULL all null,empty,
firstname NULL NULL NULL NULL 10 NULL YES varchar 50 NULL NULL 0 NULL NULL 70 0 0 0 NULL all null,empty,
lastname NULL NULL NULL NULL 11 NULL YES varchar 50 NULL NULL 0 NULL NULL 70 0 0 0 NULL all null,empty,
address NULL NULL NULL NULL 12 NULL YES varchar 100 NULL NULL 0 NULL NULL 70 0 0 0 NULL all null,empty,
pc NULL NULL NULL NULL 13 NULL YES varchar 10 NULL NULL 0 NULL NULL 70 0 0 0 NULL all null,empty,
kixcode NULL NULL NULL NULL 14 NULL YES varchar 20 NULL NULL 0 NULL NULL 70 0 0 0 NULL all null,empty,
date_created NULL NULL NULL NULL 15 (getdate()) NO datetime NULL NULL NULL 1 19 19 0 0 0 70 Many (70) unique,same length,
created_by NULL NULL NULL NULL 16 (user_name()) NO varchar 50 NULL NULL 0 13 13 0 0 0 1 loyalz-public same length,
id_location_created NULL FOREIGN KEY location id_location 17 NULL YES int NULL 10 10 0 1 1 0 0 70 2 1,2 same length,all numeric,
id_individual_type NULL FOREIGN KEY individual_type id_individual_type 18 NULL YES int NULL 10 10 0 NULL NULL 70 0 0 0 NULL all null,empty,
optin NULL NULL NULL NULL 19 NULL YES int NULL 10 10 0 1 1 39 0 31 2 0,1 same length,

I wrote a useful store procedure to help identify and resolve the problem of text truncation (String or binary data would be truncated) when the INSERT SELECT statement is used. It compares fields CHAR, VARCHAR, NCHAR AND NVARCHAR only and returns an evaluation field by field in case of being the possible cause of the error.
EXEC dbo.GetFieldStringTruncate SourceTableName, TargetTableName
This stored procedure is oriented to the problem of text truncation when an INSERT SELECT statement is made.
The operation of this stored procedure depends on the user previously identifying the INSERT statement with the problem. Then inserting the source data into a global temporary table. The SELECT INTO statement is recommended.
You must use the same name of the field of the destination table in the alias of each field of the SELECT statement.
FUNCTION CODE:
DECLARE #strSQL nvarchar(1000)
IF NOT EXISTS (SELECT * FROM dbo.sysobjects where id = OBJECT_ID(N'[dbo].[GetFieldStringTruncate]'))
BEGIN
SET #strSQL = 'CREATE PROCEDURE [dbo].[GetFieldStringTruncate] AS RETURN'
EXEC sys.sp_executesql #strSQL
END
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
/*
------------------------------------------------------------------------------------------------------------------------
Description:
Syntax
---------------
dbo.GetFieldStringTruncate(SourceTable, TargetTable)
+---------------------------+-----------------------+
| SourceTableName | VARCHAR(255) |
+---------------------------+-----------------------+
| TargetTableName | VARCHAR(255) |
+---------------------------+-----------------------+
Arguments
---------------
SourceTableName
The name of the source table. It should be a temporary table using double charp '##'. E.g. '##temp'
TargetTableName
The name of the target table. It is the table that receives the data used in the INSERT INTO stament.
Return Type
----------------
Returns a table with a list of all the fields with the type defined as text and performs an evaluation indicating which field would present the problem of string truncation.
Remarks
----------------
This stored procedure is oriented to the problem of text truncation when an INSERT SELECT statement is made.
The operation of this stored procedure depends on the user previously identifying the INSERT statement with the problem. Then inserting the source data into a global temporary table. The SELECT INTO statement is recommended.
You must use the same name of the field of the destination table in the alias of each field of the SELECT statement.
Examples
====================================================================================================
--A. Test basic
IF EXISTS (SELECT * FROM sys.objects WHERE OBJECT_ID = OBJECT_ID(N'[dbo].[tblDestino]') AND TYPE IN (N'U'))
DROP TABLE tblDestino
CREATE TABLE tblDestino
(
Id INT IDENTITY,
Field1 VARCHAR(10),
Field2 VARCHAR(12),
Field3 VARCHAR(11),
Field4 VARCHAR(16),
Field5 VARCHAR(5),
Field6 VARCHAR(1),
Field7 VARCHAR(1),
Field8 VARCHAR(6),
Field9 VARCHAR(6),
Field10 VARCHAR(50),
Field11 VARCHAR(50),
Field12 VARCHAR(50)
)
INSERT INTO dbo.tblDestino
(
Field1 ,
Field2 ,
Field3 ,
Field4 ,
Field5 ,
Field6 ,
Field7 ,
Field8 ,
Field9 ,
Field10 ,
Field11 ,
Field12
)
SELECT
'123456789' , -- Field1 - varchar(10)
'123456789' , -- Field2 - varchar(12)
'123456789' , -- Field3 - varchar(11)
'123456789' , -- Field4 - varchar(16)
'123456789' , -- Field5 - varchar(5)
'123456789' , -- Field6 - varchar(1)
'123456789' , -- Field7 - varchar(1)
'123456789' , -- Field8 - varchar(6)
'123456789' , -- Field9 - varchar(6)
'123456789' , -- Field10 - varchar(50)
'123456789' , -- Field11 - varchar(50)
'123456789' -- Field12 - varchar(50)
GO
Result:
String or binary data would be truncated
*Here you get the truncation error. Then, we proceed to save the information in a global temporary table.
*IMPORTANT REMINDER: You must use the same name of the field of the destination table in the alias of each field of the SELECT statement.
Process:
IF OBJECT_ID('tempdb..##TEMP') IS NOT NULL DROP TABLE ##TEMP
go
SELECT
[Field1] = '123456789' ,
[Field2] = '123456789' ,
[Field3] = '123456789' ,
[Field4] = '123456789' ,
[Field5] = '123456789' ,
[Field6] = '123456789' ,
[Field7] = '123456789' ,
[Field8] = '123456789' ,
[Field9] = '123456789' ,
[Field10] = '123456789' ,
[Field11] = '123456789' ,
[Field12] = '123456789'
INTO ##TEMP
Result:
(1 row(s) affected)
Test:
EXEC dbo.GetFieldStringTruncate #SourceTableName = '##TEMP', #TargetTableName = 'tblDestino'
Result:
(12 row(s) affected)
ORIGEN Nombre Campo ORIGEN Maximo Largo DESTINO Nombre Campo DESTINO Tipo de campo Evaluación
-------------------------- -------------------- ------------------------ ----------------------- -------------------------
Field1 9 02 - Field1 VARCHAR(10)
Field2 9 03 - Field2 VARCHAR(12)
Field3 9 04 - Field3 VARCHAR(11)
Field4 9 05 - Field4 VARCHAR(16)
Field5 9 06 - Field5 VARCHAR(5) possible field with error
Field6 9 07 - Field6 VARCHAR(1) possible field with error
Field7 9 08 - Field7 VARCHAR(1) possible field with error
Field8 9 09 - Field8 VARCHAR(6) possible field with error
Field9 9 10 - Field9 VARCHAR(6) possible field with error
Field10 9 11 - Field10 VARCHAR(50)
Field11 9 12 - Field11 VARCHAR(50)
Field12 9 13 - Field12 VARCHAR(50)
====================================================================================================
------------------------------------------------------------------------------------------------------------
Responsible: Javier Pardo
Date: October 19/2018
WB tests: Javier Pardo
------------------------------------------------------------------------------------------------------------
*/
ALTER PROCEDURE dbo.GetFieldStringTruncate
(
#SourceTableName AS VARCHAR(255)
, #TargetTableName AS VARCHAR(255)
)
AS
BEGIN
BEGIN TRY
DECLARE #colsUnpivot AS NVARCHAR(MAX),
#colsUnpivotConverted AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
SELECT #colsUnpivot = stuff((
SELECT DISTINCT ',' + QUOTENAME(col.NAME)
FROM tempdb.sys.tables tab
INNER JOIN tempdb.sys.columns col
ON col.object_id = tab.object_id
INNER JOIN tempdb.sys.types typ
ON col.system_type_id = TYP.system_type_id
WHERE tab.NAME = #SourceTableName
FOR XML path('')
), 1, 1, '')
,#colsUnpivotConverted = stuff((
SELECT DISTINCT ',' + 'CONVERT(VARCHAR(MAX),' + QUOTENAME(col.NAME) + ') AS ' + QUOTENAME(col.NAME)
FROM tempdb.sys.tables tab
INNER JOIN tempdb.sys.columns col
ON col.object_id = tab.object_id
INNER JOIN tempdb.sys.types typ
ON col.system_type_id = TYP.system_type_id
WHERE tab.NAME = #SourceTableName
FOR XML path('')
), 1, 1, '')
--https://stackoverflow.com/questions/11158017/column-conflicts-with-the-type-of-other-columns-in-the-unpivot-list
IF OBJECT_ID('tempdb..##TablaConMaximos') IS NOT NULL DROP TABLE ##TablaConMaximos
set #query
= 'SELECT u.d AS colname, MAX(LEN(u.data)) as [maximo_largo]
INTO ##TablaConMaximos
FROM
(
SELECT ' + #colsUnpivotConverted + '
FROM ' + #SourceTableName + '
) T
UNPIVOT
(
data
for d in ('+ #colsunpivot +')
) u
GROUP BY u.d'
PRINT #query
exec sp_executesql #query;
------------------------------------------------------------------------------------------------------------
SELECT --'Nombre de campo' = RIGHT('00' + ISNULL(CONVERT(VARCHAR,col.column_id),''),2) + ' - ' + col.name + ' '
--, 'Tipo de campo' = ISNULL(CONVERT(VARCHAR,upper(typ.name)),'') + '(' + ISNULL(CONVERT(VARCHAR,col.max_length),'') + ')'
[ORIGEN Nombre Campo] = tcm.colname
, [ORIGEN Maximo Largo] = tcm.maximo_largo
, [DESTINO Nombre Campo] = DESTINO.[Nombre de campo]
, [DESTINO Tipo de campo] = DESTINO.[Tipo de campo]
, [Evaluación] = CASE WHEN DESTINO.maximo_largo < tcm.maximo_largo THEN 'possible field with error' ELSE '' END
--, *
FROM tempdb.sys.tables tab
INNER JOIN tempdb.sys.columns col
ON col.object_id = tab.object_id
INNER JOIN tempdb.sys.types typ
ON col.system_type_id = TYP.system_type_id
RIGHT JOIN
(
SELECT column_id
, [Nombre de campo] = RIGHT('00' + ISNULL(CONVERT(VARCHAR,col.column_id),''),2) + ' - ' + col.name + ' '
, [Tipo de campo] = ISNULL(CONVERT(VARCHAR,upper(typ.name)),'') + '(' + ISNULL(CONVERT(VARCHAR,col.max_length),'') + ')'
, [maximo_largo] = col.max_length
, [colname] = col.name
FROM sys.tables tab
INNER JOIN sys.columns col
ON col.object_id = tab.object_id
INNER JOIN sys.types typ
ON col.system_type_id = TYP.system_type_id
WHERE tab.NAME = #TargetTableName
) AS DESTINO
ON col.name = DESTINO.colname
INNER JOIN ##TablaConMaximos tcm
ON tcm.colname = DESTINO.colname
WHERE tab.NAME = #SourceTableName
AND typ.name LIKE '%char%'
ORDER BY col.column_id
END TRY
BEGIN CATCH
SELECT 'Internal error ocurred' AS Message
END CATCH
END
For now only supports the data types CHAR, VARCHAR, NCHAR and NVARCHAR. You can find the last versión of this code in the next link below and we help each other to improve it. GetFieldStringTruncate.sql
https://gist.github.com/jotapardo/210e85338f87507742701aa9d41cc51d

Change the length of the data type nchar(10) to nchar(255) at least.

this can also happen when you dont have adequate permissions

I had a similar issue. I was copying data from one table to an identical table in everything but name.
Eventually I dumped the source table into a temp table using a SELECT INTO statement.
SELECT *
INTO TEMP_TABLE
FROM SOURCE_TABLE;
I compared the schema of the source table to temp table. I found one of the columns was a varchar(4000) when I was expecting a varchar(250).
UPDATE:
The varchar(4000) issue can be explained here in case you are interested:
For Nvarchar(Max) I am only getting 4000 characters in TSQL?
Hope this helps.

This error is thrown when the column of a table puts constraint [ mostly length ]. . E.g. if database schema for column myColumn is CHAR(2), then when your call from any of your application to insert value, you must pass String of length two.
The error basically says it; string of length three and above is inconsistent to fit the length restriction specified by database schema. That's why SQL Server warns and throws data loss/ Truncation error.

Please try the following code:
CREATE TABLE [dbo].[Department](
[Department_name] char(10) NULL
)
INSERT INTO [dbo].[Department]([Department_name]) VALUES ('Family Medicine')
--error will occur
ALTER TABLE [Department] ALTER COLUMN [Department_name] char(50)
INSERT INTO [dbo].[Department]([Department_name]) VALUES ('Family Medicine')
select * from [Department]

Faced the same.
The length of the column in source table was more than destination
source column length - 50 and
destination column length - nvarchar(25) and increased it to Nvarchar(50) and it worked

In Acumatica ERP I have gotten the same error during Import of order.
String or binary data would be truncated in table 'MyDatabase.dbo.ARInvoice', column 'InvoiceNbr'. Truncated value 'Something'.
After doing steps described in this link How to fix the error of String or Binary truncation, I got another error of "Quantity would become negative", which is solved by creating some quantity in Receipts screen.

Related

What this error stands for : String or binary data would be truncated [duplicate]

I am involved in a data migration project. I am getting the following error when I try to insert data from one table into another table (SQL Server 2005):
Msg 8152, Level 16, State 13, Line 1
String or binary data would be truncated.
The source data columns match the data type and are within the length definitions of the destination table columns so I am at a loss as to what could be causing this error.
You will need to post the table definitions for the source and destination tables for us to figure out where the issue is but the bottom line is that one of your columns in the source table is bigger than your destination columns. It could be that you are changing formats in a way you were not aware of. The database model you are moving from is important in figuring that out as well.
As others have already said, one of your columns datatypes in the source table is larger than your destination columns.
A simple solution is to turn off the warning and allow truncation to take place. So, if you're receiving this error but you are sure it is acceptable for data in your old database/table to be truncated (cut to size) you can simply do the following;
SET ANSI_WARNINGS OFF;
-- Your insert TSQL here.
SET ANSI_WARNINGS ON;
As above, always remember to turn warnings back on again afterwards.
The issue is quite simple: one or more of the columns in the source query contains data that exceeds the length of its destination column. A simple solution would be to take your source query and execute Max(Len( source col )) on each column. I.e.,
Select Max(Len(TextCol1))
, Max(Len(TextCol2))
, Max(Len(TextCol3))
, ...
From ...
Then compare those lengths to the data type lengths in your destination table. At least one, exceeds its destination column length.
If you are absolutely positive that this should not be the case and do not care if it is not the case, then another solution is to forcibly cast the source query columns to their destination length (which will truncate any data that is too long):
Select Cast(TextCol1 As varchar(...))
, Cast(TextCol2 As varchar(...))
, Cast(TextCol3 As varchar(...))
, ...
From ...
SQL Server 2019 will finally return more meaningful error message.
Binary or string data would be truncated => error message enhancments
if you have that error (in production), it's not obvious to see which column or row this error comes from, and how to locate it exactly.
To enable new behavior you need to use DBCC TRACEON(460). New error text from sys.messages:
SELECT * FROM sys.messages WHERE message_id = 2628
2628 – String or binary data would be truncated in table ‘%.*ls’, column ‘%.*ls’. Truncated value: ‘%.*ls’.
String or Binary data would be truncated: replacing the infamous error 8152
This new message is also backported to SQL Server 2017 CU12 (and in an upcoming SQL Server 2016 SP2 CU), but not by default. You need to enable trace flag 460 to replace message ID 8152 with 2628, either at the session or server level.
Note that for now, even in SQL Server 2019 CTP 2.0 the same trace flag 460 needs to be enabled. In a future SQL Server 2019 release, message 2628 will replace message 8152 by default.
SQL Server 2017 CU12 also supports this feature.
Improvement: Optional replacement for "String or binary data would be truncated" message with extended information in SQL Server 2017
This SQL Server 2017 update introduces an optional message that contains the following additional context information.
Msg 2628, Level 16, State 6, Procedure ProcedureName, Line Linenumber
String or binary data would be truncated in table '%.*ls', column '%.*ls'.
Truncated value: '%.*ls'.
The new message ID is 2628. This message replaces message 8152 in any error output if trace flag 460 is enabled.
db<>fiddle demo
ALTER DATABASE SCOPED CONFIGURATION
VERBOSE_TRUNCATION_WARNINGS = { ON | OFF }
APPLIES TO: SQL Server (Starting with SQL Server 2019 (15.x)) and Azure SQL Database
Allows you to enable or disable the new String or binary data would be
truncated error message. SQL Server 2019 (15.x) introduces a new, more
specific error message (2628) for this scenario:
String or binary data would be truncated in table '%.*ls', column'%.*ls'. Truncated value: '%.*ls'.
When set to ON under database compatibility level 150, truncation
errors raise the new error message 2628 to provide more context and
simplify the troubleshooting process.
When set to OFF under database compatibility level 150, truncation
errors raise the previous error message 8152.
For database compatibility level 140 or lower, error message 2628
remains an opt-in error message that requires trace flag 460 to be
enabled, and this database scoped configuration has no effect.
One other potential reason for this is if you have a default value setup for a column that exceeds the length of the column. It appears someone fat fingered a column that had a length of 5 but the default value exceeded the length of 5. This drove me nuts as I was trying to understand why it wasn't working on any insert, even if all i was inserting was a single column with an integer of 1. Because the default value on the table schema had that violating default value it messed it all up - which I guess brings us to the lesson learned - avoid having tables with default value's in the schema. :)
Here is a slightly different answer. Your column names & lengths may all match, but perhaps you are specifying the columns in the wrong order in your SELECT statement. Say tableX and tableY have columns with the same name, but in different order
I am going to add one other possible cause of this error just because no one has mentioned it and it might help some future person (since the OP has found his answer). If the table you are inserting into has triggers, it could be the trigger is generating the error. I have seen this happen when table field definitions were changed, but audit tables were not.
If you’re on SQL Server 2016-2017:
to fix it, turn on trace flag 460
DBCC TRACEON(460, 1);
GO
and make sure you turn it off after:
DBCC TRACEOFF(460, 1);
GO
source
For the others, also check your stored procedure. In my case in my stored procedure CustomSearch I accidentally declared not enough length for my column, so when I entered a big data I received that error even though I have a big length on my database. I just changed the length of my column in my custom search the error goes away. This is just for the reminder. Thanks.
This can be a challenging error. Here are some notes taken from https://connect.microsoft.com/SQLServer/feedback/details/339410/ look for AmirCharania's comment.
I've adjusted the answer given by AmirCharania for data selected into an actual table, instead of a temp one. First select your dataset into a development table then run the following:
WITH CTE_Dev
AS (
SELECT C.column_id
,ColumnName = C.NAME
,C.max_length
,C.user_type_id
,C.precision
,C.scale
,DataTypeName = T.NAME
FROM sys.columns C
INNER JOIN sys.types T ON T.user_type_id = C.user_type_id
WHERE OBJECT_ID = OBJECT_ID('YOUR TARGET TABLE NAME HERE, WITH SCHEMA')
)
,CTE_Temp
AS (
SELECT C.column_id
,ColumnName = C.NAME
,C.max_length
,C.user_type_id
,C.precision
,C.scale
,DataTypeName = T.NAME
FROM sys.columns C
INNER JOIN sys.types T ON T.user_type_id = C.user_type_id
WHERE OBJECT_ID = OBJECT_ID('YOUR TEMP TABLE NAME HERE, WITH SCHEMA')
)
SELECT *
FROM CTE_Dev D
FULL OUTER JOIN CTE_Temp T ON D.ColumnName = T.ColumnName
WHERE ISNULL(D.max_length, 0) < ISNULL(T.max_length, 999)
Yes,I am also face these kind of problem.
REMARKS VARCHAR(500)
to
REMARKS VARCHAR(1000)
Here, I've change REMARKS filed length from 500 to 1000
Yep - "a pint into a half-pint pot will not go". I've not had much luck (for whatever reason) with the various SPs that folks have suggested, BUT as long as the two tables are in the same DB (or you can get them into the same DB), you can use INFORMATION_SCHEMA.COLUMNS to locate the errant field(s), thusly:
select c1.table_name,c1.COLUMN_NAME,c1.DATA_TYPE,c1.CHARACTER_MAXIMUM_LENGTH,c2.table_name,c2.COLUMN_NAME, c2.DATA_TYPE,c2.CHARACTER_MAXIMUM_LENGTH
from [INFORMATION_SCHEMA].[COLUMNS] c1
left join [INFORMATION_SCHEMA].[COLUMNS] c2 on
c1.COLUMN_NAME=c2.COLUMN_NAME
where c1.TABLE_NAME='MyTable1'
and c2.TABLE_NAME='MyTable2'
--and c1.DATA_TYPE<>c2.DATA_TYPE
--and c1.CHARACTER_MAXIMUM_LENGTH <> c2.CHARACTER_MAXIMUM_LENGTH
order by c1.COLUMN_NAME
This will let you scroll up and down, comparing field lengths as you go. The commented sections let you see (once uncommented, obviously) if there are data type mismatches, or specifically show those that differ in field length - cos I'm too lazy to scroll - just be aware that the whole thing is predicated on the source column names matching those of the target.
I came across this problem today, and in my search for an answer to this minimal informative error message i also found this link:
https://connect.microsoft.com/SQLServer/feedback/details/339410/please-fix-the-string-or-binary-data-would-be-truncated-message-to-give-the-column-name
So it seems microsoft has no plans to expand on error message anytime soon.
So i turned to other means.
I copied the errors to excel:
(1 row(s) affected)
(1 row(s) affected)
(1 row(s) affected)
Msg 8152, Level 16, State 14, Line 13
String or binary data would be truncated.
The statement has been terminated.
(1 row(s) affected)
counted the number of rows in excel, got to close to the records counter that caused the problem... adjusted my export code to print out the SQL close to it... then ran the 5 - 10 sql inserts around the problem sql and managed to pinpoint the problem one, see the string that was too long, increase size of that column and then big import file ran no problem.
Bit of a hack and a workaround, but when you left with very little choice you do what you can.
I was using empty string '' on on table creation and then receiving error 'Msg 8152, String or binary data would be truncated' on subsequent update. This was happening due to the update value containing 6 characters and being larger than the column definition anticipated. I used "SPACE" to get around this only because I knew I would be updating in bulk following the initial data creation i.e. the column was not going to remain empty for long.
SO BIG CAVEAT HERE: This is not a particularly slick solution but is useful in the case where you are pulling together a data set e.g. for one-off intelligence requests where you are creating a table for data mining, applying some bulk processing/interpretation and storing before and after results for later comparison/mining. This is a frequent occurrence in my line of work.
You can initially populate using the SPACE keyword i.e.
select
Table1.[column1]
,Table1.[column2]
,SPACE(10) as column_name
into table_you_are_creating
from Table1
where ...
Subsequent updates to "column_name" of 10 characters or less (substitute as applicable) will then be allowed without causing truncate error. Again, I would only use this in scenarios similar to that described in my caveat.
I've built a stored procedure that analyses a source table or query with several characteristics per column among which the minimum length (min_len) and maximum length (max_len).
CREATE PROCEDURE [dbo].[sp_analysetable] (
#tableName varchar(8000),
#deep bit = 0
) AS
/*
sp_analysetable 'company'
sp_analysetable 'select * from company where name is not null'
*/
DECLARE #intErrorCode INT, #errorMSG VARCHAR(500), #tmpQ NVARCHAR(2000), #column_name VARCHAR(50), #isQuery bit
SET #intErrorCode=0
IF OBJECT_ID('tempdb..##tmpTableToAnalyse') IS NOT NULL BEGIN
DROP TABLE ##tmpTableToAnalyse
END
IF OBJECT_ID('tempdb..##tmpColumns') IS NOT NULL BEGIN
DROP TABLE ##tmpColumns
END
if CHARINDEX('from', #tableName)>0
set #isQuery=1
IF #intErrorCode=0 BEGIN
if #isQuery=1 begin
--set #tableName = 'USE '+#db+';'+replace(#tableName, 'from', 'into ##tmpTableToAnalyse from')
--replace only first occurance. Now multiple froms may exists, but first from will be replaced with into .. from
set #tableName=Stuff(#tableName, CharIndex('from', #tableName), Len('from'), 'into ##tmpTableToAnalyse from')
exec(#tableName)
IF OBJECT_ID('tempdb..##tmpTableToAnalyse') IS NULL BEGIN
set #intErrorCode=1
SET #errorMSG='Error generating temporary table from query.'
end
else begin
set #tableName='##tmpTableToAnalyse'
end
end
end
IF #intErrorCode=0 BEGIN
SET #tmpQ='USE '+DB_NAME()+';'+CHAR(13)+CHAR(10)+'
select
c.column_name as [column],
cast(sp.value as varchar(1000)) as description,
tc_fk.constraint_type,
kcu_pk.table_name as fk_table,
kcu_pk.column_name as fk_column,
c.ordinal_position as pos,
c.column_default as [default],
c.is_nullable as [null],
c.data_type,
c.character_maximum_length as length,
c.numeric_precision as [precision],
c.numeric_precision_radix as radix,
cast(null as bit) as [is_unique],
cast(null as int) as min_len,
cast(null as int) as max_len,
cast(null as int) as nulls,
cast(null as int) as blanks,
cast(null as int) as numerics,
cast(null as int) as distincts,
cast(null as varchar(500)) as distinct_values,
cast(null as varchar(50)) as remarks
into ##tmpColumns'
if #isQuery=1 begin
SET #tmpQ=#tmpQ+' from tempdb.information_schema.columns c, (select null as value) sp'
end
else begin
SET #tmpQ=#tmpQ+'
from information_schema.columns c
left join sysobjects so on so.name=c.table_name and so.xtype=''U''
left join syscolumns sc on sc.name=c.column_name and sc.id =so.id
left join sys.extended_properties sp on sp.minor_id = sc.colid AND sp.major_id = sc.id and sp.name=''MS_Description''
left join information_schema.key_column_usage kcu_fk on kcu_fk.table_name = c.table_name and c.column_name = kcu_fk.column_name
left join information_schema.table_constraints tc_fk on kcu_fk.table_name = tc_fk.table_name and kcu_fk.constraint_name = tc_fk.constraint_name
left join information_schema.referential_constraints rc on rc.constraint_name = kcu_fk.constraint_name
left join information_schema.table_constraints tc_pk on rc.unique_constraint_name = tc_pk.constraint_name
left join information_schema.key_column_usage kcu_pk on tc_pk.constraint_name = kcu_pk.constraint_name
'
end
SET #tmpQ=#tmpQ+' where c.table_name = '''+#tableName+''''
exec(#tmpQ)
end
IF #intErrorCode=0 AND #deep = 1 BEGIN
DECLARE
#count_rows int,
#count_distinct int,
#count_nulls int,
#count_blanks int,
#count_numerics int,
#min_len int,
#max_len int,
#distinct_values varchar(500)
DECLARE curTmp CURSOR LOCAL FAST_FORWARD FOR
select [column] from ##tmpColumns;
OPEN curTmp
FETCH NEXT FROM curTmp INTO #column_name
WHILE ##FETCH_STATUS = 0 and #intErrorCode=0 BEGIN
set #tmpQ = 'USE '+DB_NAME()+'; SELECT'+
' #count_rows=count(0), '+char(13)+char(10)+
' #count_distinct=count(distinct ['+#column_name+']),'+char(13)+char(10)+
' #count_nulls=sum(case when ['+#column_name+'] is null then 1 else 0 end),'+char(13)+char(10)+
' #count_blanks=sum(case when ltrim(['+#column_name+'])='''' then 1 else 0 end),'+char(13)+char(10)+
' #count_numerics=sum(isnumeric(['+#column_name+'])),'+char(13)+char(10)+
' #min_len=min(len(['+#column_name+'])),'+char(13)+char(10)+
' #max_len=max(len(['+#column_name+']))'+char(13)+char(10)+
' from ['+#tableName+']'
exec sp_executesql #tmpQ,
N'#count_rows int OUTPUT,
#count_distinct int OUTPUT,
#count_nulls int OUTPUT,
#count_blanks int OUTPUT,
#count_numerics int OUTPUT,
#min_len int OUTPUT,
#max_len int OUTPUT',
#count_rows OUTPUT,
#count_distinct OUTPUT,
#count_nulls OUTPUT,
#count_blanks OUTPUT,
#count_numerics OUTPUT,
#min_len OUTPUT,
#max_len OUTPUT
IF (#count_distinct>10) BEGIN
SET #distinct_values='Many ('+cast(#count_distinct as varchar)+')'
END ELSE BEGIN
set #distinct_values=null
set #tmpQ = N'USE '+DB_NAME()+';'+
' select #distinct_values=COALESCE(#distinct_values+'',''+cast(['+#column_name+'] as varchar), cast(['+#column_name+'] as varchar))'+char(13)+char(10)+
' from ('+char(13)+char(10)+
' select distinct ['+#column_name+'] from ['+#tableName+'] where ['+#column_name+'] is not null) a'+char(13)+char(10)
exec sp_executesql #tmpQ,
N'#distinct_values varchar(500) OUTPUT',
#distinct_values OUTPUT
END
UPDATE ##tmpColumns SET
is_unique =case when #count_rows=#count_distinct then 1 else 0 end,
distincts =#count_distinct,
nulls =#count_nulls,
blanks =#count_blanks,
numerics =#count_numerics,
min_len =#min_len,
max_len =#max_len,
distinct_values=#distinct_values,
remarks =
case when #count_rows=#count_nulls then 'all null,' else '' end+
case when #count_rows=#count_distinct then 'unique,' else '' end+
case when #count_distinct=0 then 'empty,' else '' end+
case when #min_len=#max_len then 'same length,' else '' end+
case when #count_rows=#count_numerics then 'all numeric,' else '' end
WHERE [column]=#column_name
FETCH NEXT FROM curTmp INTO #column_name
END
CLOSE curTmp DEALLOCATE curTmp
END
IF #intErrorCode=0 BEGIN
select * from ##tmpColumns order by pos
end
IF #intErrorCode=0 BEGIN --Clean up temporary tables
IF OBJECT_ID('tempdb..##tmpTableToAnalyse') IS NOT NULL BEGIN
DROP TABLE ##tmpTableToAnalyse
END
IF OBJECT_ID('tempdb..##tmpColumns') IS NOT NULL BEGIN
DROP TABLE ##tmpColumns
END
end
IF #intErrorCode<>0 BEGIN
RAISERROR(#errorMSG, 12, 1)
END
RETURN #intErrorCode
I store this procedure in the master database so that I can use it in every database like so:
sp_analysetable 'table_name', 1
// deep=1 for doing value analyses
And the output is:
column description constraint_type fk_table fk_column pos default null data_type length precision radix is_unique min_len max_len nulls blanks numerics distincts distinct_values remarks
id_individual NULL PRIMARY KEY NULL NULL 1 NULL NO int NULL 10 10 1 1 2 0 0 70 70 Many (70) unique,all numeric,
id_brand NULL NULL NULL NULL 2 NULL NO int NULL 10 10 0 1 1 0 0 70 2 2,3 same length,all numeric,
guid NULL NULL NULL NULL 3 (newid()) NO uniqueidentifier NULL NULL NULL 1 36 36 0 0 0 70 Many (70) unique,same length,
customer_id NULL NULL NULL NULL 4 NULL YES varchar 50 NULL NULL 0 NULL NULL 70 0 0 0 NULL all null,empty,
email NULL NULL NULL NULL 5 NULL YES varchar 100 NULL NULL 0 4 36 0 0 0 31 Many (31)
mobile NULL NULL NULL NULL 6 NULL YES varchar 50 NULL NULL 0 NULL NULL 70 0 0 0 NULL all null,empty,
initials NULL NULL NULL NULL 7 NULL YES varchar 50 NULL NULL 0 NULL NULL 70 0 0 0 NULL all null,empty,
title_short NULL NULL NULL NULL 8 NULL YES varchar 50 NULL NULL 0 NULL NULL 70 0 0 0 NULL all null,empty,
title_long NULL NULL NULL NULL 9 NULL YES varchar 50 NULL NULL 0 NULL NULL 70 0 0 0 NULL all null,empty,
firstname NULL NULL NULL NULL 10 NULL YES varchar 50 NULL NULL 0 NULL NULL 70 0 0 0 NULL all null,empty,
lastname NULL NULL NULL NULL 11 NULL YES varchar 50 NULL NULL 0 NULL NULL 70 0 0 0 NULL all null,empty,
address NULL NULL NULL NULL 12 NULL YES varchar 100 NULL NULL 0 NULL NULL 70 0 0 0 NULL all null,empty,
pc NULL NULL NULL NULL 13 NULL YES varchar 10 NULL NULL 0 NULL NULL 70 0 0 0 NULL all null,empty,
kixcode NULL NULL NULL NULL 14 NULL YES varchar 20 NULL NULL 0 NULL NULL 70 0 0 0 NULL all null,empty,
date_created NULL NULL NULL NULL 15 (getdate()) NO datetime NULL NULL NULL 1 19 19 0 0 0 70 Many (70) unique,same length,
created_by NULL NULL NULL NULL 16 (user_name()) NO varchar 50 NULL NULL 0 13 13 0 0 0 1 loyalz-public same length,
id_location_created NULL FOREIGN KEY location id_location 17 NULL YES int NULL 10 10 0 1 1 0 0 70 2 1,2 same length,all numeric,
id_individual_type NULL FOREIGN KEY individual_type id_individual_type 18 NULL YES int NULL 10 10 0 NULL NULL 70 0 0 0 NULL all null,empty,
optin NULL NULL NULL NULL 19 NULL YES int NULL 10 10 0 1 1 39 0 31 2 0,1 same length,
I wrote a useful store procedure to help identify and resolve the problem of text truncation (String or binary data would be truncated) when the INSERT SELECT statement is used. It compares fields CHAR, VARCHAR, NCHAR AND NVARCHAR only and returns an evaluation field by field in case of being the possible cause of the error.
EXEC dbo.GetFieldStringTruncate SourceTableName, TargetTableName
This stored procedure is oriented to the problem of text truncation when an INSERT SELECT statement is made.
The operation of this stored procedure depends on the user previously identifying the INSERT statement with the problem. Then inserting the source data into a global temporary table. The SELECT INTO statement is recommended.
You must use the same name of the field of the destination table in the alias of each field of the SELECT statement.
FUNCTION CODE:
DECLARE #strSQL nvarchar(1000)
IF NOT EXISTS (SELECT * FROM dbo.sysobjects where id = OBJECT_ID(N'[dbo].[GetFieldStringTruncate]'))
BEGIN
SET #strSQL = 'CREATE PROCEDURE [dbo].[GetFieldStringTruncate] AS RETURN'
EXEC sys.sp_executesql #strSQL
END
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
/*
------------------------------------------------------------------------------------------------------------------------
Description:
Syntax
---------------
dbo.GetFieldStringTruncate(SourceTable, TargetTable)
+---------------------------+-----------------------+
| SourceTableName | VARCHAR(255) |
+---------------------------+-----------------------+
| TargetTableName | VARCHAR(255) |
+---------------------------+-----------------------+
Arguments
---------------
SourceTableName
The name of the source table. It should be a temporary table using double charp '##'. E.g. '##temp'
TargetTableName
The name of the target table. It is the table that receives the data used in the INSERT INTO stament.
Return Type
----------------
Returns a table with a list of all the fields with the type defined as text and performs an evaluation indicating which field would present the problem of string truncation.
Remarks
----------------
This stored procedure is oriented to the problem of text truncation when an INSERT SELECT statement is made.
The operation of this stored procedure depends on the user previously identifying the INSERT statement with the problem. Then inserting the source data into a global temporary table. The SELECT INTO statement is recommended.
You must use the same name of the field of the destination table in the alias of each field of the SELECT statement.
Examples
====================================================================================================
--A. Test basic
IF EXISTS (SELECT * FROM sys.objects WHERE OBJECT_ID = OBJECT_ID(N'[dbo].[tblDestino]') AND TYPE IN (N'U'))
DROP TABLE tblDestino
CREATE TABLE tblDestino
(
Id INT IDENTITY,
Field1 VARCHAR(10),
Field2 VARCHAR(12),
Field3 VARCHAR(11),
Field4 VARCHAR(16),
Field5 VARCHAR(5),
Field6 VARCHAR(1),
Field7 VARCHAR(1),
Field8 VARCHAR(6),
Field9 VARCHAR(6),
Field10 VARCHAR(50),
Field11 VARCHAR(50),
Field12 VARCHAR(50)
)
INSERT INTO dbo.tblDestino
(
Field1 ,
Field2 ,
Field3 ,
Field4 ,
Field5 ,
Field6 ,
Field7 ,
Field8 ,
Field9 ,
Field10 ,
Field11 ,
Field12
)
SELECT
'123456789' , -- Field1 - varchar(10)
'123456789' , -- Field2 - varchar(12)
'123456789' , -- Field3 - varchar(11)
'123456789' , -- Field4 - varchar(16)
'123456789' , -- Field5 - varchar(5)
'123456789' , -- Field6 - varchar(1)
'123456789' , -- Field7 - varchar(1)
'123456789' , -- Field8 - varchar(6)
'123456789' , -- Field9 - varchar(6)
'123456789' , -- Field10 - varchar(50)
'123456789' , -- Field11 - varchar(50)
'123456789' -- Field12 - varchar(50)
GO
Result:
String or binary data would be truncated
*Here you get the truncation error. Then, we proceed to save the information in a global temporary table.
*IMPORTANT REMINDER: You must use the same name of the field of the destination table in the alias of each field of the SELECT statement.
Process:
IF OBJECT_ID('tempdb..##TEMP') IS NOT NULL DROP TABLE ##TEMP
go
SELECT
[Field1] = '123456789' ,
[Field2] = '123456789' ,
[Field3] = '123456789' ,
[Field4] = '123456789' ,
[Field5] = '123456789' ,
[Field6] = '123456789' ,
[Field7] = '123456789' ,
[Field8] = '123456789' ,
[Field9] = '123456789' ,
[Field10] = '123456789' ,
[Field11] = '123456789' ,
[Field12] = '123456789'
INTO ##TEMP
Result:
(1 row(s) affected)
Test:
EXEC dbo.GetFieldStringTruncate #SourceTableName = '##TEMP', #TargetTableName = 'tblDestino'
Result:
(12 row(s) affected)
ORIGEN Nombre Campo ORIGEN Maximo Largo DESTINO Nombre Campo DESTINO Tipo de campo Evaluación
-------------------------- -------------------- ------------------------ ----------------------- -------------------------
Field1 9 02 - Field1 VARCHAR(10)
Field2 9 03 - Field2 VARCHAR(12)
Field3 9 04 - Field3 VARCHAR(11)
Field4 9 05 - Field4 VARCHAR(16)
Field5 9 06 - Field5 VARCHAR(5) possible field with error
Field6 9 07 - Field6 VARCHAR(1) possible field with error
Field7 9 08 - Field7 VARCHAR(1) possible field with error
Field8 9 09 - Field8 VARCHAR(6) possible field with error
Field9 9 10 - Field9 VARCHAR(6) possible field with error
Field10 9 11 - Field10 VARCHAR(50)
Field11 9 12 - Field11 VARCHAR(50)
Field12 9 13 - Field12 VARCHAR(50)
====================================================================================================
------------------------------------------------------------------------------------------------------------
Responsible: Javier Pardo
Date: October 19/2018
WB tests: Javier Pardo
------------------------------------------------------------------------------------------------------------
*/
ALTER PROCEDURE dbo.GetFieldStringTruncate
(
#SourceTableName AS VARCHAR(255)
, #TargetTableName AS VARCHAR(255)
)
AS
BEGIN
BEGIN TRY
DECLARE #colsUnpivot AS NVARCHAR(MAX),
#colsUnpivotConverted AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
SELECT #colsUnpivot = stuff((
SELECT DISTINCT ',' + QUOTENAME(col.NAME)
FROM tempdb.sys.tables tab
INNER JOIN tempdb.sys.columns col
ON col.object_id = tab.object_id
INNER JOIN tempdb.sys.types typ
ON col.system_type_id = TYP.system_type_id
WHERE tab.NAME = #SourceTableName
FOR XML path('')
), 1, 1, '')
,#colsUnpivotConverted = stuff((
SELECT DISTINCT ',' + 'CONVERT(VARCHAR(MAX),' + QUOTENAME(col.NAME) + ') AS ' + QUOTENAME(col.NAME)
FROM tempdb.sys.tables tab
INNER JOIN tempdb.sys.columns col
ON col.object_id = tab.object_id
INNER JOIN tempdb.sys.types typ
ON col.system_type_id = TYP.system_type_id
WHERE tab.NAME = #SourceTableName
FOR XML path('')
), 1, 1, '')
--https://stackoverflow.com/questions/11158017/column-conflicts-with-the-type-of-other-columns-in-the-unpivot-list
IF OBJECT_ID('tempdb..##TablaConMaximos') IS NOT NULL DROP TABLE ##TablaConMaximos
set #query
= 'SELECT u.d AS colname, MAX(LEN(u.data)) as [maximo_largo]
INTO ##TablaConMaximos
FROM
(
SELECT ' + #colsUnpivotConverted + '
FROM ' + #SourceTableName + '
) T
UNPIVOT
(
data
for d in ('+ #colsunpivot +')
) u
GROUP BY u.d'
PRINT #query
exec sp_executesql #query;
------------------------------------------------------------------------------------------------------------
SELECT --'Nombre de campo' = RIGHT('00' + ISNULL(CONVERT(VARCHAR,col.column_id),''),2) + ' - ' + col.name + ' '
--, 'Tipo de campo' = ISNULL(CONVERT(VARCHAR,upper(typ.name)),'') + '(' + ISNULL(CONVERT(VARCHAR,col.max_length),'') + ')'
[ORIGEN Nombre Campo] = tcm.colname
, [ORIGEN Maximo Largo] = tcm.maximo_largo
, [DESTINO Nombre Campo] = DESTINO.[Nombre de campo]
, [DESTINO Tipo de campo] = DESTINO.[Tipo de campo]
, [Evaluación] = CASE WHEN DESTINO.maximo_largo < tcm.maximo_largo THEN 'possible field with error' ELSE '' END
--, *
FROM tempdb.sys.tables tab
INNER JOIN tempdb.sys.columns col
ON col.object_id = tab.object_id
INNER JOIN tempdb.sys.types typ
ON col.system_type_id = TYP.system_type_id
RIGHT JOIN
(
SELECT column_id
, [Nombre de campo] = RIGHT('00' + ISNULL(CONVERT(VARCHAR,col.column_id),''),2) + ' - ' + col.name + ' '
, [Tipo de campo] = ISNULL(CONVERT(VARCHAR,upper(typ.name)),'') + '(' + ISNULL(CONVERT(VARCHAR,col.max_length),'') + ')'
, [maximo_largo] = col.max_length
, [colname] = col.name
FROM sys.tables tab
INNER JOIN sys.columns col
ON col.object_id = tab.object_id
INNER JOIN sys.types typ
ON col.system_type_id = TYP.system_type_id
WHERE tab.NAME = #TargetTableName
) AS DESTINO
ON col.name = DESTINO.colname
INNER JOIN ##TablaConMaximos tcm
ON tcm.colname = DESTINO.colname
WHERE tab.NAME = #SourceTableName
AND typ.name LIKE '%char%'
ORDER BY col.column_id
END TRY
BEGIN CATCH
SELECT 'Internal error ocurred' AS Message
END CATCH
END
For now only supports the data types CHAR, VARCHAR, NCHAR and NVARCHAR. You can find the last versión of this code in the next link below and we help each other to improve it. GetFieldStringTruncate.sql
https://gist.github.com/jotapardo/210e85338f87507742701aa9d41cc51d
Change the length of the data type nchar(10) to nchar(255) at least.
this can also happen when you dont have adequate permissions
I had a similar issue. I was copying data from one table to an identical table in everything but name.
Eventually I dumped the source table into a temp table using a SELECT INTO statement.
SELECT *
INTO TEMP_TABLE
FROM SOURCE_TABLE;
I compared the schema of the source table to temp table. I found one of the columns was a varchar(4000) when I was expecting a varchar(250).
UPDATE:
The varchar(4000) issue can be explained here in case you are interested:
For Nvarchar(Max) I am only getting 4000 characters in TSQL?
Hope this helps.
This error is thrown when the column of a table puts constraint [ mostly length ]. . E.g. if database schema for column myColumn is CHAR(2), then when your call from any of your application to insert value, you must pass String of length two.
The error basically says it; string of length three and above is inconsistent to fit the length restriction specified by database schema. That's why SQL Server warns and throws data loss/ Truncation error.
Please try the following code:
CREATE TABLE [dbo].[Department](
[Department_name] char(10) NULL
)
INSERT INTO [dbo].[Department]([Department_name]) VALUES ('Family Medicine')
--error will occur
ALTER TABLE [Department] ALTER COLUMN [Department_name] char(50)
INSERT INTO [dbo].[Department]([Department_name]) VALUES ('Family Medicine')
select * from [Department]
Faced the same.
The length of the column in source table was more than destination
source column length - 50 and
destination column length - nvarchar(25) and increased it to Nvarchar(50) and it worked
In Acumatica ERP I have gotten the same error during Import of order.
String or binary data would be truncated in table 'MyDatabase.dbo.ARInvoice', column 'InvoiceNbr'. Truncated value 'Something'.
After doing steps described in this link How to fix the error of String or Binary truncation, I got another error of "Quantity would become negative", which is solved by creating some quantity in Receipts screen.

Get Column Names of a Query in SQL Server

Let's say I have a query in SQL 2014:
SELECT EmployeeName, EmployeeAddress, EmployeeAge FROM dbo.Employee
I would like to dynamically go through the query, loop and get the name of the columns like EmployeeName, EmployeeAddress, and EmployeeAge.
I need this because I can have another query different than this I need to get the column names as well.
The sp_describe_first_result_set stored procedure will give you the column names and much more for any query. You simply need to pass the query in question to the #tsql parameter.
Please see below example use of the stored procedure:
DECLARE #queryDescription TABLE
(
s_hidden bit NULL
,column_ordinal int NULL
,name sysname NULL
,is_nullable bit NULL
,system_type_id int NULL
,system_type_name nvarchar(256) NULL
,max_length smallint NULL
,precision tinyint NULL
,scale tinyint NULL
,collation_name sysname NULL
,user_type_id int NULL
,user_type_database sysname NULL
,user_type_schema sysname NULL
,user_type_name sysname NULL
,assembly_qualified_type_name nvarchar(4000) NULL
,xml_collection_id int NULL
,xml_collection_database sysname NULL
,xml_collection_schema sysname NULL
,xml_collection_name sysname NULL
,is_xml_document bit NULL
,is_case_sensitive bit NULL
,is_fixed_length_clr_type bit NULL
,source_server sysname NULL
,source_database sysname NULL
,source_schema sysname NULL
,source_table sysname NULL
,source_column sysname NULL
,is_identity_column bit NULL
,is_part_of_unique_key bit NULL
,is_updateable bit NULL
,is_computed_column bit NULL
,is_sparse_column_set bit NULL
,ordinal_in_order_by_list smallint NULL
,order_by_list_length smallint NULL
,order_by_is_descending smallint NULL
,tds_type_id int NULL
,tds_length int NULL
,tds_collation_id int NULL
,tds_collation_sort_id tinyint NULL
)
DECLARE #query NVARCHAR(MAX) = 'SELECT EmployeeName, EmployeeAddress, EmployeeAge FROM dbo.Employee'
INSERT INTO #queryDescription
EXEC sp_describe_first_result_set #tsql = #query
SELECT Name AS ColumnName
,system_type_name AS DataTypeName
,column_ordinal AS Ordinal
FROM #queryDescription
This will return a comma-delimited list of the columns for the table named therein.
SELECT
(
SELECT DISTINCT STUFF( ( SELECT ',' + isc.name + ''
FROM sys.columns isc
WHERE OBJECT_NAME(isc.object_id) = 'TableName' FOR XML PATH('') ), 1,1,'')
AS SqlScript
)
You can find the column names of your talbes like so...
select t.name as TableName, c.Name as ColumnName
from sys.tables t
inner join sys.columns c on c.object_id = t.object_id
where t.name = 'yourTable'
So, you can wrap this in a cursor to do it for each table name, or just remove the WHERE clause to get it for ALL tables
Well, since the query can have complicated expressions that get resolved to "column" names, and they can have arbitrary names (as long as they're legal identifiers), you are looking at a string-parsing party!
Generally, a column name/alias will be either at the end of the column expression (before the comma or the start of the FROM clause), or some dbms allow you to also do "select myname=a+b, anothername=c+d, ...", and you can usually have anonymous columns as well.
Still, in the outermost select (everything before the from), you should be able to do a split by comma and then look for either the last string "token" that is before the comma (and preceded by "as" or just whitespace), and failing that you can look for the "colname=" part as a second pass, and then as a failsafe take the first n chars of the raw expression and use that for your "anonymous" names.
This is how the query parsers work after all, so it's not impossible, but I wouldn't want to have to code it! The complexity required to cover any legal query is going to be daunting.
For giggles, try thinking of the parsing rules for just these variants:
select col1 as NOTCOL1, col2 from table
select col1 + 1, col3=col2 from table
with x as (
select something from somewhere
)
select something as [Something with spaces for good measure] from x
select a.x, (b.col1) "look, a rainbow!"
from (
select col1 as x
from reused_table
) a
cross join reused_table b

Retrieving numerical values from a Nvarchar column

I have a table with a nvarchar column called Custom#5 which has the following data.
row number Custom#5
1 267.5
2 tbc
3
4 34
I want to be able to clean this data up so always returns a numerical value.
row number Custom#5
1 267.5
2 0
3 0
4 34
My current query is;
SELECT CASE
WHEN BomHeaders_1.Custom#5 NOT LIKE '%[^0-9]%'
THEN 0
WHEN BomHeaders_1.Custom#5 IS NULL
THEN 0
ELSE BomHeaders_1.Custom#5
END AS Custom5
FROM [FS25-W2K8\SQLEXPRESS].sagel50_46772.dbo.BomHeaders AS BomHeaders_1
INNER JOIN [FS25-W2K8\SQLEXPRESS].sagel50_46772.dbo.BomComponents AS BomComponents_1 ON BomHeaders_1.ID = BomComponents_1.HeaderID
INNER JOIN [FS25-W2K8\SQLEXPRESS].sagel50_46772.dbo.BomHeaders AS BomHeaders_2 ON BomComponents_1.StockCode = BomHeaders_2.BomReference
INNER JOIN manu_STOCK ON BomHeaders_1.BomReference = manu_STOCK.STOCK_CODE
WHERE (BomComponents_1.StockCode LIKE N'21%')
The current error i'm getting with this is
"Conversion failed when converting the nvarchar value '267.5' to data type int."
If you are using 2012 or greater, I would use the tryparse function
select coalesce(TRY_PARSE ( [Custom#5] AS decimal(18,2)),0)
declare #t table (R INT,C varchar(10))
inSERT INTO #t(R,c)values (1,'267.5'),(2,'tbc'),(3,''),(4,'34')
select R,CASE WHEN C LIKE '%[^a-zA-Z]%' THEN C ELSE CAST(0 AS VARCHAR) END from #t
If your using SQL Server < 2012 and have no option in using try_parse, you can use this:
DECLARE #string nvarchar(255)
SET #string = 'Hali891236.5€hHalo'
SELECT Substring(
#string,
PATINDEX('%[0-9.]%',#string),
PATINDEX('%[^0-9.]%',
Substring(
#string,
PATINDEX(
'%[0-9.]%',
#string
),
LEN(#string)
)
)-1
)
GO

TSQL - Dynamic Column Name in Dynamic SQL?

I have a "table A" with the following structure:
Act_Code ACT_TYPE_1 ACT_TYPE_2 ACT_TYPE_3 ACT_TYPE_4
-------- -------- ----------- ---------- -----------
ACT1 A NULL NULL NULL
ACT2 NULL B NULL NULL
ACT3 NULL NULL C NULL
ACT4 NULL NULL NULL D
ACT1 A NULL NULL NULL
As you can see, the ACT_TYPE data will always store into the field name which refers to the last digit of "Act_Code" fields:
Eg.
When Act_Code = "ACT1", the Act_Type is stored in field "ACT_TYPE_1"
When Act_Code = "ACT2", the Act_Type is stored in field "ACT_TYPE_2"
and so on...
Now, I want to read the data from the above "Table A" and insert into "Table B" which is having the following structure:
ACT ACT_TYPE
---- --------
1 A
2 B
3 C
4 D
Question:
*1. How can I add "dynamic column name" inside a select query?*
For example,
INSERT INTO Table_B (ACT, ACT_TYPE)
SELECT RIGHT(Act_Code,1), ## FROM Table_A
How can I handle the dynamic column name as per the symbol "##" above?
I've tried:
SET #sql = 'INSERT INTO Table_B (ACT,ACT_TYPE) '
SET #sql = #sql + 'SELECT RIGHT(Act_Code,1), '
SET #sql = #sql + '''ACT_TYPE_'' + RIGHT(Act_Code,1) FROM Table_A'
EXEC (#sql)
But it doesn't work!
Please help, thanks very much!
declare #Foo as Table ( Voot varchar(10), Plevny1 varchar(10), Plevny2 varchar(10) )
insert into #Foo ( Voot, Plevny1, Plevny2 ) values ( 'Thing1', 'a', 'A' ), ( 'Thing2', 'b', 'B' )
select SubString( Voot, 6, 1 ) as Vootette,
case SubString( Voot, 6, 1 )
when '1' then Plevny1
when '2' then Plevny2
else NULL end as Plevny
from #Foo
Depending on your specific requirements you may need to parse a multiple digit integer from the controlling column's value, may want to handle a default output value, may want to check for the "other" values in the row being NULL, ... .
Looks like you can do that without dynamic SQL:
insert Table_B
(Act, Act_Type)
select case Act_Code
when 'ACT1' then 1
when 'ACT2' then 2
when 'ACT3' then 3
when 'ACT4' then 4
end
, coalesce(ACT_TYPE_1, ACT_TYPE_2, ACT_TYPE_3, ACT_TYPE_4)

Convert Bit field table to table

I have a table showing locations with a BIT column for each tool in use at each location:
CREATE TABLE dbo.[ToolsSelected] (
[LocationID] NVARCHAR(40) NOT NULL,
[Tool1] INTEGER DEFAULT 0 NOT NULL,
[Tool2] INTEGER DEFAULT 0 NOT NULL,
[Tool3] INTEGER DEFAULT 0 NOT NULL,
[Tool4] INTEGER DEFAULT 0 NOT NULL,
PRIMARY KEY ([LocationID])
);
LocID Tool1 Tool2 Tool3 Tool4
----- ----- ----- ----- -----
AZ 0 1 1 0
NY 1 0 1 1
I need to convert this to a table by LocationID indicating which tools at which locations:
CREATE TABLE dbo.[ByLocation] (
[LocationID] NVARCHAR(40) NOT NULL,
[Tool] NVARCHAR(40) NOT NULL, -- Column title of ToolsSelected table
PRIMARY KEY ([LocationID], [Tool])
);
LocID Tool
----- -----
AZ Tool2
AZ Tool3
NY Tool1
NY Tool3
NY Tool4
The idea is that each location can select the tools they need, I then need to query the tools table to get details (versions, etc) for each tool selected. Each location is unique; each tool is unique. Is there a way to do this or a much better implementation?
Here is the answer to the immediate question, given only 4 tools columns:
SELECT LocID = LocationID, Tool
FROM
(
SELECT LocationID, Tool = 'Tool1' FROM dbo.ToolsSelected WHERE Tool1 = 1
UNION ALL
SELECT LocationID, Tool = 'Tool2' FROM dbo.ToolsSelected WHERE Tool2 = 1
UNION ALL
SELECT LocationID, Tool = 'Tool3' FROM dbo.ToolsSelected WHERE Tool3 = 1
UNION ALL
SELECT LocationID, Tool = 'Tool4' FROM dbo.ToolsSelected WHERE Tool4 = 1
) AS x
ORDER BY LocID, Tool;
With 40 columns, you could do the same thing, but along with the desire to generate this dynamically:
DECLARE #sql NVARCHAR(MAX);
SET #sql = N'';
SELECT #sql += '
UNION ALL
SELECT LocationID, Tool = ''' + name + '''
FROM dbo.ToolsSelected WHERE ' + name + ' = 1'
FROM sys.columns WHERE [object_id] = OBJECT_ID('dbo.ToolsSelected')
AND name LIKE 'Tool[0-9]%';
SELECT #sql = N'SELECT LocID = LocationID, Tool
FROM
(' + STUFF(#sql, 1, 17, '') + '
) AS x ORDER BY LocID, Tool;';
PRINT #sql;
-- EXEC sp_executesql #sql;
*BUT*
Storing these as separate columns is a recipe for disaster. So when you add Tool41, Tool42 etc. you have to change the schema then change all your code that passes the column names and 1/0 via parameters etc. Why not represent these as simple numbers, e.g.
CREATE TABLE dbo.LocationTools
(
LocID NVARCHAR(40),
ToolID INT
);
So in the above case you would store:
LocID Tool
----- ----
AZ 2
AZ 3
NY 1
NY 3
NY 4
Now when you pass in the checkboxes they've selected, presumably from the front end you are receiving two values, such as:
LocID: "NY"
Tools: "Tool1, Tool5, Tool26"
If that's about right, then you can populate the table when a user creates or changes their choice, first using a split function to break up the comma-separated list dictated by the checkboxes:
CREATE FUNCTION dbo.SplitTools
(
#ToolList NVARCHAR(MAX)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
(
SELECT ToolID = y.i.value('(./text())[1]', 'int')
FROM
(
SELECT x = CONVERT(XML,
'<i>' + REPLACE(REPLACE(#List, ',', '</i><i>'), 'Tool', '')
+ '</i>').query('.')
) AS a CROSS APPLY x.nodes('i') AS y(i)
);
GO
(You forgot to tell us which version of SQL Server you are using - if 2008 or above you could use a table-valued parameter as an alternative to a split function.)
Then a procedure to handle it:
CREATE PROCEDURE dbo.UpdateLocationTools
#LocID NVARCHAR(40),
#Tools NVARCHAR(MAX)
AS
BEGIN
SET NOCOUNT ON;
-- in case they had previously selected tools
-- that are no longer selected, clear first:
DELETE dbo.LocationTools WHERE LocID = #LocID;
INSERT dbo.LocationTools(LocID, ToolID)
SELECT #LocID, ToolID
FROM dbo.SplitTools(#Tools);
END
GO
Now you can add new tool #s without changing schema or code, since your list of checkboxes could also be generated from your data - assuming you have a dbo.Tools table or want to add one. This table could also be used for data integrity purposes (you could put a foreign key on dbo.LocationTools.ToolID).
And you can generate your desired query very simply:
SELECT LocID, Tool = 'Tool' + CONVERT(VARCHAR(12), ToolID)
FROM dbo.LocationTools
ORDER BY LocID, ToolID;
No redundant data, no wide tables with unmanageable columns, and a proper index can even help you search for, say, all locations using Tool3 efficiently...

Resources