How to resize all NVARCHAR(n) columns at once? - sql-server

I wonder if something like the following is possible to achieve:
ALTER TABLE dbo.[Foo]
ALTER COLUMN * NVARCHAR(500)
WHERE Columns NVARCHAR(n) < NVARCHAR(500);

you can use information_schema.columns and build a dynamic sql query to do generate query. Still you need to copy the result and run them manually.
select 'alter table '+TABLE_SCHEMA+'.'+TABLE_NAME+' alter column '+COLUMN_NAME+' NVARCHAR(500)'
from INFORMATION_SCHEMA.columns
where DATA_TYPE = 'nvarchar'
and CHARACTER_MAXIMUM_LENGTH < 500
and table_name = 'foo'
and TABLE_SCHEMA = 'dbo'
edited below
use the below code to include null/not null
select 'alter table ['+TABLE_SCHEMA+'].['+TABLE_NAME+'] alter column ['+COLUMN_NAME+'] NVARCHAR(500) '+case when IS_NULLABLE = 'YES' then 'NULL' else 'NOT NULL' end
from INFORMATION_SCHEMA.columns
where DATA_TYPE = 'nvarchar'
and CHARACTER_MAXIMUM_LENGTH < 500
and table_name = 'foo'
and TABLE_SCHEMA = 'dbo'

Related

Query INFORMATION_SCHEMA.COLUMNS on all databases?

Is there a way to query INFORMATION_SCHEMA.COLUMNS across all databases? It appears that USE db must be specified.
I tried this, but it returns nothing when using PowerShell Invoke-Sqlcmd.
IF DB_ID('db') IS NOT NULL
BEGIN
SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME LIKE 'BID_BY_CPT_SPECIAL'
ORDER BY TABLE_CATALOG,TABLE_SCHEMA,TABLE_NAME,ORDINAL_POSITION
;
END
This query is used in a PowerShell script.
$q = #"
DROP TABLE IF EXISTS #Temp;
SELECT DatabaseName=cast('' as varchar(100))
, TABLE_NAME
, COLUMN_NAME
, DATA_TYPE
, ORDINAL_POSITION
Into #Temp
FROM INFORMATION_SCHEMA.COLUMNS
Where 1=0
Declare #SQL varchar(max) = '
USE ? ;
Insert Into #Temp
SELECT DatabaseName=''?''
, TABLE_NAME
, COLUMN_NAME
, DATA_TYPE
, ORDINAL_POSITION
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME LIKE ''BID_BY_CPT_SPECIAL''
ORDER BY TABLE_CATALOG,TABLE_SCHEMA,TABLE_NAME,ORDINAL_POSITION
'
EXEC sp_MSforeachdb #SQL
Select * From #Temp
"#
foreach ($db in $DatabaseInstances) {
$r = Invoke-Sqlcmd -Query $q -ServerInstance $db
=== Update
This query is working with Invoke-Sqlcmd.
$q = #"
DROP TABLE IF EXISTS #Temp;
SELECT DatabaseName=cast('' as varchar(100))
,TABLE_NAME
,COLUMN_NAME
,DATA_TYPE
,ORDINAL_POSITION
,CHARACTER_MAXIMUM_LENGTH
,NUMERIC_PRECISION
,NUMERIC_SCALE
,IS_NULLABLE
Into #Temp
FROM INFORMATION_SCHEMA.COLUMNS
Where 1=0
Declare #SQL varchar(max) = '
USE ? ;
Insert Into #Temp
SELECT DatabaseName=''?''
,TABLE_NAME
,COLUMN_NAME
,DATA_TYPE
,ORDINAL_POSITION
,CHARACTER_MAXIMUM_LENGTH
,NUMERIC_PRECISION
,NUMERIC_SCALE
,IS_NULLABLE
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME LIKE ''$TableName''
ORDER BY TABLE_CATALOG,TABLE_SCHEMA,TABLE_NAME,ORDINAL_POSITION
'
EXEC sp_MSforeachdb #SQL
Select * From #Temp
"#
Take a look into sp_MSforeachdb
This will collect data into one Temp table. Personally, I would move your WHERE to the final SELECT
Example
SELECT DatabaseName=cast('' as varchar(100))
, TABLE_NAME
, COLUMN_NAME
, DATA_TYPE
, ORDINAL_POSITION
Into #Temp
FROM INFORMATION_SCHEMA.COLUMNS
Where 1=0
Declare #SQL varchar(max) = '
USE ? ;
Insert Into #Temp
SELECT DatabaseName=''?''
, TABLE_NAME
, COLUMN_NAME
, DATA_TYPE
, ORDINAL_POSITION
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME LIKE ''BID_BY_CPT_SPECIAL''
ORDER BY TABLE_CATALOG,TABLE_SCHEMA,TABLE_NAME,ORDINAL_POSITION
'
EXEC sp_MSforeachdb #SQL
Select * From #Temp

Inalid column name after creating

I am creating SQL calls (SQL Server 17) on the fly from a C# program to update columns if they have invalid settings (size, datatype, ect). I am creating a new column and transferring data to the new column.
I constantly get an error saying Invalid Column Name (Line 3 in example), even though it follows the create statement, and the entire thing is wrapped in an IF that isn't even called most of the time.
I realize the "reason" is the TEMP column doesn't exist at the time of the call, but if the line is actually processed it would. Any suggestions on how to resolve this would be great.
SQL example:
IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'AD' and COLUMN_NAME = 'ADD1' AND (data_type <> 'varchar' OR character_maximum_length <> '100' OR is_nullable <> 'no' OR (column_default IS NOT NULL AND column_default <> '(null)') ))
BEGIN
ALTER TABLE [dbo].[AD] ADD [tempADD1] [varchar] (100) NOT NULL ;
UPDATE [dbo].[AD] SET tempADD1 = ADD1;
IF EXISTS (SELECT * FROM sysobjects s JOIN syscolumns c ON s.parent_obj = c.id WHERE s.xtype = 'd' AND c.cdefault = s.id AND parent_obj= object_id('AD') AND c.name ='tempADD1')
BEGIN
DECLARE #find varchar(150);
SET #find = (SELECT s.name FROM sysobjects s JOIN syscolumns c ON s.parent_obj = c.id
WHERE s.xtype = 'd' AND c.cdefault = s.id AND parent_obj= object_id('AD') AND c.name ='tempADD1');
EXEC('ALTER TABLE [dbo].[AD] DROP ' + #find + ';');
END
ALTER TABLE [dbo].[AD] DROP COLUMN [tempADD1];
EXEC sp_rename 'AD.tempADD1', 'ADD1', 'COLUMN'
END
Couple options.
1) Make separate calls to the database (less efficient).
First call, creates column.
Second call, updates column (it exists now, so no error).
2) Make one call to the database, and "hide" the query so it can't be validated, in a nested dynamic SQL query.
ex:
DECLARE #SQL NVARCHAR(MAX)
IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'AD' and COLUMN_NAME = 'ADD1' AND (data_type <> 'varchar' OR character_maximum_length <> '100' OR is_nullable <> 'no' OR (column_default IS NOT NULL AND column_default <> '(null)') ))
BEGIN
ALTER TABLE [dbo].[AD] ADD [tempADD1] [varchar] (100) NOT NULL ;
SET #SQL='UPDATE [dbo].[AD] SET tempADD1 = ADD1;'
EXEC SP_EXECUTESQL #SQL
...
...
Lastly, I would avoid doing this:
EXEC('ALTER TABLE [dbo].[AD] DROP ' + #find + ';');
This should be executed in the same manner I showed above using SP_EXECUTESQL instead.
If the column doesn't exist at the time of the call and causing an error, wrap the call in dynamic TSQL. This will delay the compilation until execution.
SET #SQL = 'ALTER TABLE [dbo].[AD] DROP COLUMN [tempADD1];'
EXEC SP_EXECUTESQL #SQL;

Only Change Decimal's Scale

I've too many columns with DECIMAL(A,B). Some of them have column default, some of them nullable, etc.
Instead of using:
ALTER TABLE TABLE_NAME ALTER COLUMN COLUMN_NAME DECIMAL(A,C)
Is there method that simply updates the SCALE of the DECIMAL?
You can try with modify keyword
ALTER TABLE "table_name" MODIFY "column_name" "New Data Type";
Here is one way
DECLARE #sql VARCHAR(max)= ''
SET #sql = (SELECT 'ALTER TABLE TABLE_NAME ALTER COLUMN ' + COLUMN_NAME + ' DECIMAL(A,C);'
FROM information_schema.columns
WHERE table_name = 'TABLE_NAME'
AND data_type = 'DECIMAL'
AND NUMERIC_SCALE = --B
--add relevant filters
FOR xml path(''))
--print #sql
EXEC (#sql)
Don't forget to replace A and C with proper Precision and Scale
Use can achieve it by executing a dynamic SQL query. Use stuff function to concatenate each alter statement and retrieve the column names and other details from information_schema.columns. Create an other variable to hold the new numeric_scale value and take numeric_precision from the information_schema.columns itself.
Query
declare #sql as varchar(max);
declare #i as int = 3; -- change accordingly
select #sql = stuff((
select 'alter table ' + [table_name]
+ ' alter column ' + [column_name] + ' decimal('
+ cast([numeric_precision] as varchar(100)) + ',' + cast(#i as varchar(100)) + ');'
from information_schema.columns
where table_name = 'your_table_name'
and data_type = 'decimal'
for xml path('')
)
, 1, 0, ''
);
exec(#sql);

Test if a column exist in index sqlserver

I have this sqlserver query to generate a query text that I can use to update all columns types from nvarchar/ nchar to varchar/char.
SELECT
AlterSql = CONCAT('ALTER TABLE ', TABLE_SCHEMA, '.', TABLE_NAME, ' ALTER COLUMN ', COLUMN_NAME, ' ', SUBSTRING(DATA_TYPE, 2, LEN(DATA_TYPE)), '(', CHARACTER_MAXIMUM_LENGTH, ') ', CASE IS_NULLABLE WHEN 'YES' THEN 'NULL' WHEN 'NO' THEN 'NOT NULL' ELSE 'ERROR' END, CHAR(13), CHAR(10), 'GO')
, *
FROM INFORMATION_SCHEMA.COLUMNS
WHERE DATA_TYPE IN ('nchar', 'nvarchar')
The issue is when I running it, an error displayed that it can't cast the columns exists in indexs.
So I should test if the column exist in index or no, then I delete the index, covert and recreate my index.
How can I modify my script to do it ?
Thanks a lot
First, you should check CONSTRAINT exit or not, if so then drop it as following
IF EXISTS (
SELECT * FROM dbo.sysobjects WHERE id =
OBJECT_ID(N'[CONSTRAINTNAME]') AND type = 'D'
)
BEGIN
ALTER TABLE [dbo].[_COMPANY] DROP CONSTRAINT [CONSTRAINTNAME]
END
Then use your statement
SELECT
AlterSql = CONCAT('ALTER TABLE ', TABLE_SCHEMA, '.', TABLE_NAME, ' ALTER COLUMN ', COLUMN_NAME, ' ', SUBSTRING(DATA_TYPE, 2, LEN(DATA_TYPE)), '(', CHARACTER_MAXIMUM_LENGTH, ') ', CASE IS_NULLABLE WHEN 'YES' THEN 'NULL' WHEN 'NO' THEN 'NOT NULL' ELSE 'ERROR' END, CHAR(13), CHAR(10), 'GO')
, *
FROM INFORMATION_SCHEMA.COLUMNS
WHERE DATA_TYPE IN ('nchar', 'nvarchar')
The procedure sp_helpindex does, what you want, you can exec it into table in memory and proceed
declare #t table
(
index_name sysname collate database_default NOT NULL,
index_description nvarchar(512) ,
index_keys sysname
)
insert into #t exec sp_helpIndex 'items'
--do whatever with this data here
select * from #t
And i join to Sean Lange question: why you need it?

SQL Server : ALTER COLUMN only if existing column has a smaller length

I am trying to create a script which will alter a column only if it is not of the correct size. Something like this.
IF NOT EXISTS (SELECT COL_LENGTH('dbo.TSC701_OCT_CONTEXT', 'sql_stmt') = 1000 )
BEGIN
ALTER TABLE dbo.TSC701_OCT_CONTEXT
ALTER COLUMN sql_stmt VARCHAR(1000)
END
Sorry for the screwed up syntax
You actually do not need the SELECT statement in the IF clause.
IF (COL_LENGTH('dbo.TSC701_OCT_CONTEXT', 'sql_stmt') < 1000)
BEGIN
ALTER TABLE [Table Name]
ALTER COLUMN [Column Name] varchar(1000) null
END
Also, if your column needs to be NOT NULL or is already set to NOT NULL, you will want to switch the null from above out for NOT NULL.
If your goal is to check every column in a database, you can use the below code. Just be aware this could be very slow and could cause unintended errors and issues. I would backup whatever database you plan to use this on immediately before running it and I would also run it on a small test database or one table to see if it does what you want without taking too long.
DECLARE #TABLE_CATALOG NVARCHAR(128), #TABLE_SCHEMA NVARCHAR(128), #TABLE_NAME NVARCHAR(128), #COLUMN_NAME NVARCHAR(128), #DATA_TYPE NVARCHAR(128)
DECLARE #IS_NULLABLE NVARCHAR(3)
DECLARE #sql NVARCHAR(1000)
WHILE EXISTS (SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_CATALOG='teoe' AND CHARACTER_MAXIMUM_LENGTH < 1000)
BEGIN
SELECT TOP 1
#TABLE_CATALOG = TABLE_CATALOG,
#TABLE_SCHEMA = TABLE_SCHEMA,
#TABLE_NAME = TABLE_NAME,
#COLUMN_NAME = COLUMN_NAME,
#IS_NULLABLE = IS_NULLABLE,
#DATA_TYPE = DATA_TYPE
FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_CATALOG='teoe'
AND CHARACTER_MAXIMUM_LENGTH < 1000
SET #sql = 'ALTER TABLE [' + #TABLE_CATALOG + '].' + #TABLE_SCHEMA + '.[' + #TABLE_NAME + '] ALTER COLUMN [' + #COLUMN_NAME + '] '+ #DATA_TYPE + '(1000) ' +
CASE WHEN #IS_NULLABLE = 'YES' THEN 'NULL' ELSE 'NOT NULL' END
EXECUTE #sql
BREAK
END
You can try it like this.
IF (SELECT CASE WHEN COL_LENGTH('dbo.TSC701_OCT_CONTEXT', 'sql_stmt') <> 1000 THEN 1 END) = 1
BEGIN ALTER TABLE dbo.TSC701_OCT_CONTEXT alter column sql_stmt varchar(1000) END
IF 1000 = (SELECT CHARACTER_MAXIMUM_LENGTH FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_CATALOG='Database Name' AND TABLE_NAME='Table Name' AND COLUMN_NAME='Column Name')
BEGIN
ALTER TABLE [Table Name]
ALTER COLUMN [Column Name] varchar(1000) null
END
Here is what I ended up with:
IF NOT EXISTS (SELECT 1 FROM sys.columns
WHERE name = 'sql_stmt' AND object_id = object_id('CONTEXT') and max_length = 1000)
BEGIN
ALTER TABLE dbo.CONTEXT ALTER COLUMN [sql_stmt] varchar (1000) NULL
END
GO

Resources