Quickest way to alter datatypes - sql-server-2005

A database needs to be upgraded and as part of the upgrade, I want to convert all TEXT/NTEXT fields in the dbo schema to the NVARCHAR(MAX) datatype. The data in those fields cannot be modified, though.
This upgrade needs to be done through an SQL Script!
So, how to do this?
(Without having to write ALTER TABLE bla ALTER COLUMN blabla NVARCHAR(MAX) blablabla...)

In the assumption you only use "system types" in your tables you can look up the columns with the types you want to change and call the updates in a cursor.
declare #nsql nvarchar(2000)
, #table sysname
, #column sysname
, #type sysname
, #is_nullable bit
declare cur cursor local read_only
for
select tableName = OBJECT_NAME([object_id]), columnName = name, type = TYPE_NAME(system_type_id), is_nullable from sys.columns where TYPE_NAME(system_type_id) in ('text', 'ntext')
open cur
fetch next from cur into #table, #column, #type, #is_nullable
WHILE (##FETCH_STATUS = 0)
BEGIN
select #nsql = N'ALTER TABLE ' + quotename(#table) + N' ALTER COLUMN ' + quotename(#column) + N' '
if #type = N'text'
select #nsql = #nsql + N' VARCHAR(MAX)'
if #type = N'ntext'
select #nsql = #nsql + N' NVARCHAR(MAX)'
if #is_nullable = 0
select #nsql = #nsql + N' NOT NULL'
exec(#nsql)
fetch next from cur into #table, #column, #type, #is_nullable
END
close cur
deallocate cur
If you also use "alias types" you will have to go through a bit more pain to fix them.

Related

In dynamic SQL, table is not being created

I have to use the configurations table to create a table if the table_status is new, and I have written the code but the table 'Dimcampaign' is not being created.
Initially you set #Tablestatus to a SQL statement, but the while loop requires #Tablestatus = 'New'. You should change how you initialize your #Tablestatus variable so that the logic flows into the while loop.
I would guess that you actually want #Tablestatus to be initialized to the value in your dbo.configuration table. For example:
declare #Tablestatus varchar(MAX)
select #Tablestatus = tablestatus from dbo.configuration
That being said, your while loop does not have a way to terminate. For example, inside the loop you should reset #Tablestatus so that it can become something other than 'New' so the loop can end.
For example:
while #Tablestatus = 'New' begin
-- maybe do some stuff here
update dbo.configuration set tablestatus = 'Old'
-- maybe do some other stuff here
select #Tablestatus = tablestatus from dbo.configuration
end
Looking a little more at your post, I don't really understand why you have a while loop there. It looks like you want something like this:
-- Initialize variables with values from dbo.configuration
declare #Tablestatus varchar(MAX)
select #Tablestatus = tablestatus from dbo.configuration
declare #If_New_Table_then_PrimaryKey NVARCHAR(MAX)
select #If_New_Table_then_PrimaryKey = If_New_Table_then_PrimaryKey from dbo.configuration
declare #NewColumn varchar(max)
select #NewColumn = Newcolumn from dbo.configuration
declare #Datatypes NVARCHAR(MAX)
select #Datatypes = Datatypes from dbo.configuration
declare #sql varchar(100)
-- Now that your variables are initialized you
-- can use the variables to take the appropriate action
if #Tablestatus = 'New' and #If_New_Table_then_PrimaryKey = 'Yes'
begin
set #sql = 'Create table' + #tablename + '(' + #Newcolumn + #Datatypes + ' PRIMARY KEY' + '(' + #Newcolumn + ')' + ');'
print (#sql)
exec(#sql)
end
if #Tablestatus = 'Old'
begin
set #sql = 'Alter table' + #tablename + 'ADD' + #Newcolumn + #Datatypes
print (#sql)
exec(#sql)
end
I like playing around with cursors and dynamic SQL. Something like this should do the trick, I'd add a whole lot of checking though! What if tablename contains spaces (you might like to use QUOTENAME), or what if datatypes doesn't contain a valid datatype? Etc etc, I can think of 100 things that can go wrong. Generally, when you want to do this, you need to rethink your design. But I have been in situations where I had to do something similar.
DECLARE #Tablestatus NVARCHAR(4000)
, #tablename NVARCHAR(4000)
, #If_New_Table_then_PrimaryKey NVARCHAR(4000)
, #NewColumn NVARCHAR(4000)
, #Datatypes NVARCHAR(4000)
, #sql NVARCHAR(4000);
DECLARE cur CURSOR LOCAL FAST_FORWARD FOR
SELECT tablestatus
, tablename
, If_New_Table_then_PrimaryKey
, Newcolumn
, Datatypes
FROM dbo.configuration;
OPEN cur;
FETCH NEXT FROM cur
INTO #Tablestatus
, #tablename
, #If_New_Table_then_PrimaryKey
, #NewColumn
, #Datatypes;
WHILE ##FETCH_STATUS = 0
BEGIN;
IF #Tablestatus = 'New'
AND #If_New_Table_then_PrimaryKey = 'Yes'
BEGIN;
SET #sql = N'Create table ' + #tablename + N'(' + #NewColumn + N' ' + #Datatypes + N' PRIMARY KEY' + N'(' + #NewColumn + N')' + N');';
PRINT ( #sql );
EXEC sys.sp_executesql #sql;
END
IF #Tablestatus = 'Old'
BEGIN;
SET #sql = N'Alter table ' + #tablename + N'ADD ' + #NewColumn + N' ' +#Datatypes;
PRINT ( #sql );
EXEC sys.sp_executesql #sql;
END;
FETCH NEXT FROM cur
INTO #Tablestatus
, #tablename
, #If_New_Table_then_PrimaryKey
, #NewColumn
, #Datatypes
END;
CLOSE cur;
DEALLOCATE cur;

Loop through Query and Update field

I'm trying to loop through a fields defined in a query to an update statement.
I have the following SQL:
Declare #SQL varchar(max)
#SQL= 'Select [a],[b],[c],[d],[e]....[z]
From Table1;'
I want to be able to loop through all the fields [a]-[z] and update via the following statement:
Update Table 1
Set [a] = Case when [a] = 'Not at all' Then 0
when [a] = 'Very Much' Then 10 End
Field names are not actually [a]..[z]; I can't run the the update statement on the whole table, only a specific set of field names.
Struggling to write it programatically in SQL Server.
Declare #SQL varchar(max)
Declare #name varchar(100)
DECLARE #getid CURSOR
Set #getid = cursor for
SELECT name
FROM
sys.dm_exec_describe_first_result_set('Select [a],[b],[c],[d],[e]....[z]
From Table1', NULL, 0)
Open #getid
FETCH NEXT
FROM #getid INTO #name
WHILE ##FETCH_STATUS = 0
BEGIN
SET #SQL = 'Update Table1
Set ' + #name + ' = Case when ' + #name +'= ''Very Much'' Then ''10''
when ' + #name + ' = ''Not at all'' Then ''0''
Else ' + #name + ' End'
Exec(#SQL)
FETCH NEXT
FROM #getid INTO #name
END
CLOSE #getid
DEALLOCATE #getid
Basically dm_exec_describe_first_result_set is grabbing the fieldnames and outputting it as a recordset. Then we are just passing the the each of the records to #name and use it form our update statement and then executing it for each record passed.
Hope this helps someone else! Curious to see if there is a better way.
I think if you want to make it a little more generic I would do something like the following code. This will allow you to not have to write the specific query for every table you want to do this to and you could potentially filter out columns you do not want in the future.
To be clear, I borrowed the SQL to do the actual UPDATE from #Dale-K post and just made it pretty.
DECLARE #strSQL NVARCHAR(1000)
DECLARE #strTable NVARCHAR(100)
DECLARE #strColName VARCHAR(100)
SET #strTable = N'Table1'
CREATE TABLE #COLUMNS(ColName varchar(100))
SET #strSQL = ' select COLUMN_NAME
from INFORMATION_SCHEMA.COLUMNS
where TABLE_NAME = #TableName and DATA_TYPE in (''nvarchar'', ''varchar'')'
INSERT INTO #COLUMNS
EXEC sp_executeSQL #strSQL, N'#TableName nvarchar(100)', #TableName = #strTable
DECLARE csrColumns CURSOR LOCAL FORWARD_ONLY FOR
SELECT ColName FROM #COLUMNS
OPEN csrColumns
FETCH csrColumns INTO #strColName
WHILE ##FETCH_STATUS = 0
BEGIN
SET #strSQL = N'UPDATE ' + #strTable + '
SET ' + #strColName + ' = CASE WHEN ' + #strColName +'= ''Very Much'' THEN ''10''
WHEN ' + #strColName + ' = ''Not at all'' THEN ''0''
ELSE ' + #strColName + ' END'
exec sp_ExecuteSQL #strSQL
FETCH csrColumns INTO #strColName
END
CLOSE csrColumns
DEALLOCATE csrColumns

Best Way To Convert All "SMALLINT" Columns Within A Database Schema To "BIT"? (SQL)

How do I convert all smallint type columns from my database to bit types?
I am using SQL Server 2008.
In SQL Server you can do it with ALTER TABLE my_table ALTER COLUMN my_column [new_datatype].
Be careful of things like default values because I haven't tested with them.
Example 1 - will give a list of queries for you to review / amend / execute (safer option).
DECLARE #TableSchema VARCHAR(100)
DECLARE #TableName VARCHAR(100)
DECLARE #ColumnName VARCHAR(100)
DECLARE #Query NVARCHAR(1000)
DECLARE #FromDataType NVARCHAR(50)
DECLARE #ToDataType NVARCHAR(50)
SET #TableSchema = 'dbo';
SET #FromDataType = 'smallint';
SET #ToDataType = 'bit';
DECLARE c CURSOR FAST_FORWARD FOR
SELECT TABLE_NAME, COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = #TableSchema
AND TABLE_NAME <> 'sysdiagrams'
AND DATA_TYPE = #FromDataType
OPEN c;
FETCH NEXT FROM c INTO #TableName, #ColumnName;
WHILE ##FETCH_STATUS = 0
BEGIN
SET #Query = N'ALTER TABLE ' + #TableName + N' ALTER COLUMN ' + #ColumnName + N' ' + #ToDataType -- + CHAR(13) + N'GO'
PRINT #Query
EXEC sp_EXECUTESQL #Query
FETCH NEXT FROM c INTO #TableName, #ColumnName;
END
CLOSE c;
DEALLOCATE c;
Example 2 - will execute (recommend running example 1 first!)
DECLARE #TableSchema VARCHAR(100)
DECLARE #TableName VARCHAR(100)
DECLARE #ColumnName VARCHAR(100)
DECLARE #Query NVARCHAR(1000)
DECLARE #FromDataType NVARCHAR(50)
DECLARE #ToDataType NVARCHAR(50)
SET #TableSchema = 'dbo';
SET #FromDataType = 'smallint';
SET #ToDataType = 'bit';
DECLARE c CURSOR FAST_FORWARD FOR
SELECT TABLE_NAME, COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = #TableSchema
AND TABLE_NAME <> 'sysdiagrams'
AND DATA_TYPE = #FromDataType
OPEN c;
FETCH NEXT FROM c INTO #TableName, #ColumnName;
WHILE ##FETCH_STATUS = 0
BEGIN
SET #Query = N'ALTER TABLE ' + #TableName + N' ALTER COLUMN ' + #ColumnName + N' ' + #ToDataType + CHAR(13) + N' GO'
PRINT #Query
FETCH NEXT FROM c INTO #TableName, #ColumnName;
END
CLOSE c;
DEALLOCATE c;
Please be careful doing this and test it on a backup database first. The following query will create ALTER statements for each column in your database that have SMALLINT datatype to convert them to BIT.
select 'ALTER TABLE ' + QUOTENAME(o.Name) + ' ALTER COLUMN ' + QUOTENAME(c.Name) + ' BIT' as Command
from sys.objects o
inner join sys.columns c
on o.object_id = c.object_id
where system_type_id = 52
and o.Type = 'U'
Also, be sure each column listed only contains 1 or 0 or you will get truncation errors when you run the script.
If you are asking about converting the column data type, I don't thing you can do that directly. You can add a new column as BIT and populate it from the old column, then drop the old column and rename the new one back to the old name.
See the online docs for more info.

How to do a huge search for Primary Key ID's that is used across the database where these Primary Key ID's have similar values in columns

BackDrop: We are researching why a number of accounts were missed in a process. We have went back to as far as we have data. We now have a rather large list of accounts that for whatever reason were missed. Now this process without going into too much detail is VERY VERY complex and we need to know why these accounts and only these accounts were missed. As any DataBase we have many many automated procedures that run all the time, so there is really at this point no telling what on earth happened to cause these accounts to get missed. My only bet I think at solving this is to find similarities between these accounts. Obviously we have tried looking at the more common places and have since found nothing.
Issue: I want to use SQL to return all the tablenames and columnnames in our database Where these list of accounts have the same value in a column or columns of a table. I have created a query to find tablenames, columns, and so forth but dont know how to bring it all together to create one query that will give me all the results I want. I am certain that a cursor will need to be used and lots of inner joining but I am just not sure how this should be done.
Again:
Lets say I have account numbers 123456 and 654321 and I know our DataBase has 3,000 tables with a column reference to account number with a name of either AccountNumber, AccountNum, or Account. I want to search and find all tables that have a column with the name AccountNumber, AccountNum, or Account that has a value of 123456 or 654321. Then with these tables, for each table I want to take the rows Where the column whether the name be AccountNumber, AccountNum, or Account has a value of either 123456 and 654321 and then for each of those rows I want to check each column of each row to see if the columns on a row for account number 123456 is eqaul to a column on a row for account number 654321 , if so then I want it to return the column name and the tablename. This way I can see what these accounts have in common.
ADVANCED PORTION:
IF some poor soul is able to do the above then I'd also like to create a query that will return
The tablename and when it was updated. I would get the updated value by checking each column in each table and if the column has a type of "timestamp" or a default value of "GetDate()" then that column would be used as updated. In final result set that shows were all changes have happened for those account nubmers it will order by updated.
A first approach, rustic (I'm not that used to T-SQL, I did more PL/SQL), but which may help you going further, AND TESTED IN SQL SERVER 2008. Hope it works in 2005...)
So, we create two procedures, one calling the other
The provided code can only check, in one time
- for 2 differents IDs
- for all concerned fields (Account, AccountNum, AccountNumber)
The idea (checking for AccountNumber column)
Find the tables (in table INFORMATION_SCHEMA.columns, which lists your database tables) which have a column with one of the 3 names provided
For every table found :
create a dynamic query :
select count(*) from <THE_TABLE> where <Account_column_name> IN (123456 654321);
If we have 2 results (mean that our Ids are both present in table), we launch the second procedure, with parameters : #TableName = <THE_TABLE>, #FieldName = <Account_column_name>, #FirstId = 123456, #SecondId = 654321
We find the column names for <THE_TABLE> (again in INFORMATION_SCHEMA.columns).
For every column found :
create a dynamic query
select count(*) from <THE_TABLE> as T1
inner join <THE_TABLE> as T2 on T1.<COLUMN_NAME> = T2.<COLUMN_NAME>
where T1.<Account_column_name>= 123456
and T2.<Account_column_name>= 654321
if count(*) = 1, it means that the same value exists in the same column of the same table for the given ids.
In that case, we print <THE_TABLE> and <THE_COLUMN>
To launch search, in sql management studio, just make
EXEC GetSimilarValuesForFieldAndValue 123456, 654321
and in the "Messages" part, you should have a list of "results".
CREATE procedure [dbo].[GetSimilarValuesForFieldAndValue](#FirstId int, #SecondId int)
AS
DECLARE #sql nvarchar(MAX);
DECLARE #params NVARCHAR(MAX);
DECLARE #Count int;
DECLARE #Name NVARCHAR(MAX);
DECLARE #FieldName NVARCHAR(MAX);
DECLARE db_cursor CURSOR for
select TABLE_NAME, COLUMN_NAME FROM INFORMATION_SCHEMA.columns
where COLUMN_NAME IN('Account', 'AccountNumber', 'AccountNum');
OPEN db_cursor
FETCH next from db_cursor into #Name, #FieldName
while ##FETCH_STATUS = 0
begin
select #sql =
N' SELECT #Count=Count(*) FROM ' + #Name +
N' WHERE ' +#FieldName+' IN (#FirstId,#SecondId)'
SELECT #params = N'#FieldName NVARCHAR(MAX), #FirstId int, #SecondId int, #Count int out'
EXEC sp_executesql #sql, #params, #FieldName, #FirstId, #SecondId, #Count OUT
if (#Count = 2)
begin
exec dbo.CompareFields #Name, #FieldName, #FirstId, #SecondId
end
FETCH NEXT FROM db_cursor INTO #Name, #FieldName
end
close db_cursor
DEALLOCATE db_cursor
GO
The second one :
CREATE procedure [dbo].[CompareFields](#TableName NVARCHAR(MAX), #FieldName NVARCHAR(MAX), #FirstId int, #SecondId int)
as
DECLARE #ColumnName NVARCHAR(MAX)
DECLARE #Sql NVARCHAR(MAX)
DECLARE #Params NVARCHAR(MAX)
DECLARE #Count int
DECLARE cfCursor CURSOR FOR
SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS
where TABLE_NAME = ''+#TableName+' '
AND COLUMN_NAME <> ' '+#FieldName+''
OPEN cfCursor
FETCH next from cfCursor into #ColumnName
while ##FETCH_STATUS = 0
begin
select #Sql =
N' SELECT #Count = count(*) from ' +#TableName + ' T1 '+
N' INNER JOIN ' + #TableName + ' T2 ON T1.' +#ColumnName + ' = T2.' + #ColumnName +
N' WHERE T1.' +#FieldName + ' = '+ CAST(#FirstId as varchar) +
N' AND T2.' + #FieldName + ' = '+CAST(#SecondId as varchar)
SELECT #Params =
N'#TableName VARCHAR(MAX), #ColumnName VARCHAR(MAX), '+
N'#FieldName VARCHAR(MAX), #FirstId int, #SecondId int, #Count int out'
exec sp_executesql #sql, #Params, #TableName, #ColumnName, #FieldName, #FirstId, #SecondId, #Count OUT
if #Count = 1
begin
--print tableName and column Name if value is identic
print 'Table : ' + #TableName + ' : same value for ' + #ColumnName
end
FETCH NEXT FROM cfCursor INTO #ColumnName
end
close cfCursor
DEALLOCATE cfCursor
GO
I actually had to do this for Guids at one point. Here is the script for doing with Guids. One sec and I'll work on modifying it to suit your needs:
DECLARE #table VARCHAR(100)
DECLARE #column VARCHAR(100)
DECLARE #value INT
SET #value = '06B8BD6C-A8EC-4EB3-9562-6666EE86952D'
DECLARE table_cursor CURSOR
FOR select tbl.Name, cols.name as TableName FROM sys.columns cols JOIN
sys.tables tbl on cols.object_id = tbl.object_id
where system_type_id = 36
OPEN table_cursor
FETCH NEXT FROM table_cursor
INTO #table, #column;
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #SQL NVARCHAR(1000)
SET #SQL = 'SELECT ''' + #Table + ''' AS TBL,''' +
#column + ''' AS COL FROM [' + #table + ']
WITH(NOLOCK) WHERE ' + #column + ' = ''' + CAST(#value AS VARCHAR(50)) + ''''
print #sql
EXEC sp_executesql #Sql
FETCH NEXT FROM table_cursor
INTO #table, #column;
END
CLOSE table_cursor
DEALLOCATE table_cursor
Updated to handle for searching on a field name:
DECLARE #table VARCHAR(100)
DECLARE #column VARCHAR(100)
DECLARE #value UNIQUEIDENTIFIER
SET #value = --ENTER YOUR ACCOUNT NUMBER HERE
DECLARE table_cursor CURSOR
select tbl.Name, cols.name as TableName FROM sys.columns cols JOIN
sys.tables tbl on cols.object_id = tbl.object_id
where cols.Name = 'AccountNumber'
OR cols.Name = 'AccountNum' OR cols.Name = 'Account'
OPEN table_cursor
FETCH NEXT FROM table_cursor
INTO #table, #column;
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #SQL NVARCHAR(1000)
SET #SQL = 'SELECT ''' + #Table + ''' AS TBL,''' + #column +
''' AS COL FROM [' + #table + '] WITH(NOLOCK)
WHERE ' + #column + ' = ''' + CAST(#value AS VARCHAR(50)) + ''''
print #sql
EXEC sp_executesql #Sql
FETCH NEXT FROM table_cursor
INTO #table, #column;
END
CLOSE table_cursor
DEALLOCATE table_cursor

Find specific word in all rows in MS SQL database and eventually replace it

Is there a way to scan all tables in MS SQL 2008 R2 Database and replace one word to another? Or if replace is not possible maybe just possibility to list all rows with specific word (and corresponding table next to it for reference)?
If it's not possible to do purely in SQL then I can use C# as well.
There is no "out of the box" solution for this, but it's not very hard to write a stored procedure that does this.
For example, the procedure below will loop over all the tables and then loop over all the columns of type varchar and nvarchar and replace the string #value with #newvalue. This is just a proof of concept and can be enhanced greatly to make it faster by adding a where clause that checks if the string contains the value for example. (with LIKE or using full text indexes).
create proc ReplaceStrings(
#value nvarchar(maX)
, #newvalue nvarchar(max)
)
AS
declare #table_id int
, #name sysname
, #fieldname sysname
, #sql nvarchar(max)
, #fields nvarchar(max)
if #value = ''
begin
raiserror('The search value can not be empty', 16, 1)
return (-1)
end
declare tab cursor read_only local
for
select object_id, name from sys.tables
open tab
fetch next from tab into #table_id, #name
while ##FETCH_STATUS = 0
begin
SELECT #sql = N'UPDATE ' + QUOTENAME(#name) + '
set '
, #fields = NULL
declare field cursor read_only local
for
select name from sys.columns where object_id = #table_id and system_type_id in (type_id('varchar'), type_id('nvarchar'))
open field
fetch next from field into #fieldname
while ##FETCH_STATUS = 0
begin
set #fields = coalesce(#fields + ',', '') + N' ' + quotename(#fieldname) + ' = REPLACE(' + quotename(#fieldname) + ', #value, #newvalue)'
fetch next from field into #fieldname
end
close field
deallocate field
set #sql += #fields
print #sql
exec sp_executesql #sql , N'#value nvarchar(max), #newvalue nvarchar(max)', #value, #newvalue
fetch next from tab into #table_id, #name
end
close tab
deallocate tab
return (0)
Call the procedure like this:
exec ReplaceStrings 'haha', 'hihi'