Alter tables in a schema - sql-server

I am trying to set a default value to a column(Inserted_time), but first i need to check if the column exists in the tables. If the column doesn't exist, I need to add that column and give it a default value.
I am working with Sql Server Management Studio.
So far I have written this code:
IF EXISTS ( select TABLE_NAME from INFORMATION_SCHEMA.COLUMNS where TABLE_CATALOG = 'DB_COPY' and COLUMN_NAME = 'Inserted_Time')
begin
ALTER TABLE table_name ADD CONSTRAINT [Inserted_Time_Def] SET DEFAULT (sysdatetimeoffset()) FOR [Inserted_Time]
end
else
ALTER TABLE table_name ADD COLUMN [Inserted_Time] CONSTRAINT [Inserted_Time_Def] DEFAULT (sysdatetimeoffset()) WITH VALUES
Once I retrieve the tables that has the column, I need to add that table_name to the Alter command. But I am not able to do that. Can someone please tell me how to use the table_names retrieved from select statement in the alter statement?

First, you want to put all the table names in a temporary table so you can loop through it.
After, you can use a cursor to execute a command for each table name.
In my example, I only printed the command I wanted to execute. That way you can be sure the code will do what you want first.
Example :
select TABLE_NAME As TableName INTO #TablesList from INFORMATION_SCHEMA.COLUMNS where TABLE_CATALOG = 'DB_COPY' and COLUMN_NAME = 'Inserted_Time'
DECLARE #TablesCursor as CURSOR;
DECLARE #TableName as NVARCHAR(max);
DECLARE #CommandToExecute as NVARCHAR(max);
SET #TablesCursor = CURSOR FOR SELECT TableName FROM #TablesList;
OPEN #TablesCursor;
FETCH NEXT FROM #TablesCursor INTO #TableName;
WHILE ##FETCH_STATUS = 0
BEGIN
SET #CommandToExecute = 'ALTER TABLE ' + #TableName + ' WHAT YOU WANNA DO '
PRINT #CommandToExecute
--EXEC(#CommandToExecute)
FETCH NEXT FROM #TablesCursor INTO #TableName;
END
CLOSE #TablesCursor;
DEALLOCATE #TablesCursor;

Assuming that every table is in a different schema, then you could do something like this:
DECLARE #SQL nvarchar(MAX);
SET #SQL = STUFF((SELECT NCHAR(13) + NCHAR(10) +
CASE WHEN EXISTS (SELECT 1
FROM INFORMATION_SCHEMA.COLUMNS C
WHERE T.TABLE_SCHEMA = C.TABLE_SCHEMA
AND T.TABLE_NAME = C.TABLE_SCHEMA
AND C.COLUMN_NAME = N'Inserted_Time') THEN N'ALTER TABLE ' + QUOTENAME(T.TABLE_SCHEMA) + N'.' + QUOTENAME(T.TABLE_NAME) + N' ADD CONSTRAINT [Inserted_Time_Def] DEFAULT (sysdatetimeoffset()) FOR [Inserted_Time];'
ELSE N'ALTER TABLE ' + QUOTENAME(T.TABLE_SCHEMA) + N'.' + QUOTENAME(T.TABLE_NAME) + N' ADD COLUMN [Inserted_Time] CONSTRAINT [Inserted_Time_Def] DEFAULT (sysdatetimeoffset());'
END
FROM INFORMATION_SCHEMA.TABLES T
WHERE T.TABLE_CATALOG = N'DB_COPY'
FOR XML PATH(N''),TYPE).value('.','nvarchar(MAX)'),1,2,N'');
PRINT #SQL; --Your best friend. If more than 4,000 characters, use SELECT
EXECUTE sp_executesql #SQL;
This will very likely hugely out perform a CURSOR solution if you have a large number of schemas.

Related

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);

Search for a string in any column of all tables

For example this is my ID: 07E485
I need to find this ID in all tables wherever it is found
All columns, which might carry this value, are sort of string-type...
Something like: select * from **alltables** where **anyColumn**='07E485'
The following query will return all tables in the database yourDBName whose name contains 07E485.
SELECT TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = 'BASE TABLE' AND
TABLE_CATALOG = 'yourDBName' AND
TABLE_NAME LIKE '%07E485%'
If I misread your requirement, and you instead wanted to find all tables precisely named 07E485 in any database, then you can use the following query:
SELECT TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = 'BASE TABLE' AND
TABLE_NAME = '07E485'
In each database you have a view called INFORMATION_SCHEMA.COLUMNS, you can use this view to query through all of your tables.
This is the way I'ld do it, if anyone know a better way, feel free.. :)
SET NOCOUNT ON
DECLARE #Table varchar(255), #Schema varchar(255), #SQL varchar(MAX)
DECLARE table_cursor CURSOR FOR
SELECT TABLE_NAME, TABLE_SCHEMA
FROM INFORMATION_SCHEMA.COLUMNS -- This is a system view where you can see all columns of a database.
WHERE UPPER(COLUMN_NAME) = 'ID' -- This makes sure you don't loop through any tables that don't have a Column called 'ID'.
OPEN table_cursor
FETCH NEXT FROM table_cursor INTO #Table, #Schema
WHILE ##FETCH_STATUS = 0 BEGIN
-- This part creates your queries.
SET #SQL = 'SELECT * FROM '+#Schema+'.'+#Table+'
WHERE CAST(ID as varchar) = ''07E485''' -- Casting ID to varchar to avoid data type errors.
-- This executes the query.
EXEC(#SQL)
-- If a result is found, i.e. ID is equal to '07E485' somewhere in the table, Table name is printed on the "Messages" tab.
IF ##ROWCOUNT > 0 PRINT #Table
FETCH NEXT FROM table_cursor INTO #Table, #Schema
END
CLOSE table_cursor
DEALLOCATE table_cursor
To see which tables contain id = '07E485', go to "Messages" and you will have a list of them.
UPDATE My answer completely re-written
Try it like this: This dynamic SQL will check all string-type columns if they are equal to the given search string. You might want to add more data types to the output to get a better look onto the table's row. But one cannot simply put SELECT * as there are data types not allowed in XML without extra effort.
Secondly, by using QUOTENAME, I avoid syntax errors due to column or table names with blanks...
DECLARE #Search VARCHAR(10)='07E485';
DECLARE #cmd VARCHAR(MAX);
WITH TableNames AS
(
SELECT t.*
,t.TABLE_CATALOG + '.' + t.TABLE_SCHEMA + '.' + t.TABLE_NAME AS FullTblName
,QUOTENAME(t.TABLE_CATALOG)+ '.' + QUOTENAME(t.TABLE_SCHEMA) + '.' + QUOTENAME(t.TABLE_NAME) AS FullTblNameQuoted
,
STUFF(
(
SELECT 'OR ' + QUOTENAME(c.COLUMN_NAME) + '=''' + #Search + ''' '
FROM INFORMATION_SCHEMA.COLUMNS AS c
WHERE c.TABLE_CATALOG=t.TABLE_CATALOG AND c.TABLE_SCHEMA=t.TABLE_SCHEMA AND c.TABLE_NAME=t.TABLE_NAME
AND DATA_TYPE LIKE '%char%' --add more types if needed
FOR XML PATH('')
),1,3,'') AS WhereFilter
FROM INFORMATION_SCHEMA.TABLES AS t
WHERE TABLE_TYPE='BASE TABLE'
)
SELECT #cmd = STUFF(
(
SELECT DISTINCT 'UNION ALL SELECT (SELECT ' + (SELECT STUFF((SELECT ',' + QUOTENAME(COLUMN_NAME)
FROM INFORMATION_SCHEMA.COLUMNS AS c
WHERE c.TABLE_CATALOG=TableNames.TABLE_CATALOG
AND c.TABLE_NAME =TableNames.TABLE_NAME
AND c.DATA_TYPE LIKE '%char%'
FOR XML PATH('')),1,1,'')) + ' FROM ' + FullTblNameQuoted
+ ' WHERE ' + WhereFilter
+ ' FOR XML PATH(''row''),ROOT(''' + REPLACE(REPLACE(FullTblName,'.','_'),' ','') + '''),TYPE) AS XmlData '
FROM TableNames
WHERE WhereFilter IS NOT NULL
FOR XML PATH('')
),1,10,'')
SET #cmd='SELECT XmlData FROM(' + #cmd + ') AS tbl WHERE XmlData IS NOT NULL;'
PRINT LEN(#cmd)
EXEC(#cmd)

TSQL Find MIN/MAX LEN ColName over many tables

I need to find the MAX LEN for all responses in column across many tables. Some of these column names match across tables, some don't. If they match, I need to use the column name through all the tables that it matches. So, say 10 tables with a total of 200 different column names. I created a TempCol table. I also have all the table names in a temp table. My thought is to add cols to the TempCol table for Table1MAX, Table1MIN, Table2MAX, Table2MIN etc.
This finds the MAX (IF Col_1 is in Table1):
SELECT MAX(LEN(Col_1)) FROM Table1
I'd like to fill in the TempCol table, with the values from above, but I keep hitting a wall.
Anybody have any ideas?
Here's one way, for the MAX length only; you can use it as a starting point and modify/extend it to include more information in the results table:
create table dbo.Results (
TableName sysname not null,
ColumnName sysname not null,
MaxLength int not null default 0,
primary key (TableName, ColumnName)
)
insert into dbo.Results (TableName, ColumnName)
select object_name(sc.object_id), sc.name
from sys.columns sc
join sys.types st
on sc.system_type_id = st.system_type_id
-- get data for varchar(max) columns only
where st.name = 'varchar' and sc.max_length = -1
declare #TableName sysname, #ColumnName sysname, #sql nvarchar(max)
declare TablesAndColumns cursor local fast_forward
for
select TableName, ColumnName
from dbo.Results
open TablesAndColumns
fetch next from TablesAndColumns into #TableName, #ColumnName
while ##fetch_status = 0
begin
set #sql = 'update dbo.Results set MaxLength = (select isnull(max(len(' + #ColumnName + ')),0) ' +
' from ' + #TableName + ') ' +
' where TableName = ''' + #TableName + ''' and ColumnName = ''' + #ColumnName + ''''
print #sql
exec sp_executesql #sql
fetch next from TablesAndColumns into #TableName, #ColumnName
end
close TablesAndColumns
deallocate TablesAndColumns
select * from dbo.Results
Using cursors and dynamic SQL isn't always a good idea, but for a one-time system task like this it's a reasonable approach.

Creating a "date updated" column in MS SQL?

Is there any simple way to create a column in MS SQL that will track the last time a record was updated?
I would like to have two fields. One to track when the record was created. That one is simple. Create a datetime field and set its default to getdate(). However the second field seams to be a bit more tricky. I want it to have the latest date (and time) the record was modified.
My options are:
Include getdate() in every update statement - not an option, these tables will be accessed from MS Access
Allow updates only through an SP. - not an option, these tables will be accessed from MS Access
Create triggers for each table - the DB is recreated on many machines and I am afraid it will conflict or be forgotten or get out of synch.
Are there any other options?
Triggers are pretty much your only option here. What is to stop anyone from updating tables with SSMS, those updates would not update the date updated column in that case
Option 4:
Create a stored procedure that automatically creates triggers for all the tables in your database. In SQL 2005, optionally run this trigger any time any table is created (using a DDL trigger).
CREATE PROC UpdateTriggersCreate
AS
DECLARE
#TableSchema sysname,
#TableName sysname,
#PrimaryKeys nvarchar(4000),
#ObjectName nvarchar(4000)
#TriggerName nvarchar(4000),
#SQL nvarchar(4000);
SET #TableName = '';
SET #TableSchema = '';
WHILE 1 = 1 BEGIN
SELECT TOP 1
#TableSchema = TABLE_SCHEMA,
#TableName = TABLE_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE
COLUMN_NAME = 'LastUpdatedDate'
AND (
TABLE_SCHEMA > #TableSchema
OR (
TABLE_SCHEMA = #TableSchema
AND TABLE_NAME > #TableName
)
)
ORDER BY TABLE_SCHEMA, TABLE_NAME;
IF ##RowCount = 0 BREAK;
IF NOT EXISTS (
SELECT *
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS C
WHERE
C.CONSTRAINT_TYPE = 'PRIMARY KEY'
AND C.TABLE_SCHEMA = #TableSchema
AND C.TABLE_NAME = #TableName
) BEGIN
PRINT '-- Not processing table ''' + #TableSchema + '.' + #TableName + ''' because automatic last updated triggers cannot be used on tables with no primary key.';
CONTINUE;
END;
SET #PrimaryKeys = NULL;
SELECT #PrimaryKeys = Coalesce(#PrimaryKeys + ' AND T.', 'T.') + QuoteName(Y.COLUMN_NAME) + ' = I.' + QuoteName(Y.COLUMN_NAME)
FROM
INFORMATION_SCHEMA.TABLE_CONSTRAINTS T
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE Y
ON T.CONSTRAINT_CATALOG = Y.CONSTRAINT_CATALOG
AND T.CONSTRAINT_SCHEMA = Y.CONSTRAINT_SCHEMA
AND T.CONSTRAINT_NAME = Y.CONSTRAINT_NAME
WHERE
T.CONSTRAINT_TYPE = 'PRIMARY KEY'
AND T.TABLE_SCHEMA = #TableSchema
AND T.TABLE_NAME = #TableName;
-- order is not important which is good because ORDER BY is unreliable in this case
SET #ObjectName = #TableSchema + '.' + #TableName;
SET #TriggerName = 'TR_' + Replace(#ObjectName, '.', '_') + '_U_TimeUpdated';
SET #SQL = 'IF Object_ID(''' + #TriggerName + ''', ''TR'') IS NOT NULL DROP TRIGGER ' + #TriggerName;
EXEC sp_executesql #SQL;
SET #SQL = 'CREATE TRIGGER ' + #TriggerName + ' ON ' + #ObjectName + ' FOR INSERT
AS
SET NOCOUNT ON
UPDATE T
SET T.LastUpdatedDate = GetDate()
FROM
' + #ObjectName + ' T
INNER JOIN Inserted I ON ' + #PrimaryKeys;
EXEC sp_executesql #SQL;
END;
Once you have a stored procedure like this, schedule it to run once a day or (in sql 2005 and up) in response to the DDL creation of tables.
Update 1
The code now handles schema properly, and looks up the primary keys. It also reports on and skips tables that have no primary key.
I'm not sure if I worked out all the syntax errors--I adapted it from code I've done this in before and didn't actually test it. I'm sure you can figure it out.
Stored procs are an option with Access, but you have to intercept the event in VBA and call a stored proc instead, followed by Me.Undo.
I've done it but many moons ago and don't have sample code to show you, sorry.
Otherwise triggers are the usual way, either before or after ones.

Drop multiple tables with string

I have tables like lg-010-a..., lg-010-ac..., and so on, I have abc database,
I have a command window:
drop table from abc where Table_Name like 'lg-010-%'
Will this drop all the tables starting with lg-010-?
Try something like this:
declare #sql varchar(max)
declare #tablenames varchar(max)
select #tablenames = coalesce(#tablenames + ', ','') + Table_Name from INFORMATION_SCHEMA.TABLES
where Table_Name like ('lg-010-%')
set #sql = 'drop table ' + #tablenames
exec (#sql)
This queries the INFORMATION_SCHEMA.TABLES table to retrieve table names that match your criteria, then concatenates them together into a comma delimited string.
This string is than added to a 'Drop table ' statement and executed.
Drop table can take multiple comma delimited table names.
(I had originally had this query sys.tables but some research revealed that while they are currently equivalent, the Information_Schema method is quaranteed to work in future versions)
Unfortunately you can't do it like that. One way is:
SELECT 'DROP TABLE ' + name FROM sysobjects WHERE name LIKE '%lg-010-a%' AND [type] IN ('P')
This will just print out the DROP TABLE statement for each table - you can then copy and paste this output and run it. You can just put an EXECUTE in the loop instead of the PRINT, but I've done it this way so you can see what's going on/check the output first.
I had an issue where the accepted answer was not doing anything. I discovered that I had to add the prefix name of the database to the code to get it to work. If your tables are not dbo.tablename try this.
declare #sql varchar(max)
declare #tablenames varchar(max)
SELECT
#tablenames = COALESCE(#tablenames + ', ','') + 'YourDatabaseName.' + Table_Name
FROM
INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = 'BASE TABLE'
AND TABLE_NAME LIKE 'AP2%'
AND (RIGHT(TABLE_NAME, 6) < 201708)
SET #sql = 'drop table ' + #tablenames
EXEC (#sql)
GO
Unfortunately you can't do it like that.
One way is:
DECLARE #TableName NVARCHAR(128)
SELECT TOP 1 #TableName = TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME LIKE 'lg-010-%'
ORDER BY TABLE_NAME ASC
WHILE (##ROWCOUNT > 0)
BEGIN
PRINT 'DROP TABLE [' + #TableName + ']'
SELECT TOP 1 #TableName = TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME LIKE 'lg-010-%'
AND TABLE_NAME > #TableName
ORDER BY TABLE_NAME ASC
END
This will just print out the DROP TABLE statement for each table - you can then copy and paste this output and run it. You can just put an EXECUTE in the loop instead of the PRINT, but I've done it this way so you can see what's going on/check the output first.
CREATE PROCEDURE dbo.drop_MsSqlTables1 #createDate smalldatetime
AS
declare #flag int =1
declare #tname varchar(50)
declare #sql varchar(max)
select row_number() over (order by name) as num, '[dbo].[' + name +']' as table_name into #temp from sys.tables where name like ('EmpInfo_%') and create_date<#createDate
declare #count int = (select count(*) from #temp)
select * from #temp
while #flag <= #count
begin
set #tname = (select table_name from #temp where num = #flag)
set #sql = 'drop table ' + #tname
exec (#sql)
set #flag = #flag+1
end

Resources