My requirement is some thing like this
I have a table tblDetails which contains all the details about the table
TblCol TblName ColNo ColName Type Length Sacle KeyType description
Now based on values in tblDetails I want to create the tables.
Here is a (very) basic example of how to approach this sort of dynamic SQL. This example does not add indexes, keys, or anything fancy - just some tables and columns with datatypes, but it should get you started in the right direction:
DECLARE #sql NVARCHAR(MAX)
DECLARE #tblName nvarchar(255)
DECLARE tbl_cursor CURSOR LOCAL FOR
SELECT DISTINCT tblName
FROM tblDetails
ORDER BY tblName
OPEN tbl_cursor
FETCH NEXT FROM tbl_cursor INTO #tblName
--OUTER LOOP THROUGH TABLES:
WHILE ##FETCH_STATUS = 0
BEGIN
SET #sql = 'CREATE TABLE ' + #tblName + '( ';
DECLARE #colName nvarchar(255),
#type nvarchar(50),
#length nvarchar(50),
#ctr int = 0;
DECLARE col_cursor CURSOR LOCAL FOR
SELECT [colName], [Type], [Length]
FROM tblDetails
WHERE tblName = #tblName
ORDER BY ColNo
OPEN col_cursor
FETCH NEXT FROM col_cursor INTO #colName, #type, #length
--INNER LOOP THROUGH COLUMNS
WHILE ##FETCH_STATUS = 0
BEGIN
IF #ctr != 0 --if this is not the first column, prefix w/ comma
SET #sql += ', ';
SET #sql += '[' + #colName + '] ' + #type;
IF #type IN ('CHAR','VARCHAR','NCHAR','NVARCHAR','BINARY','VARBINARY') --add size if appropriate
SET #sql += '(' + #length + ')'
SET #ctr += 1;
FETCH NEXT FROM col_cursor INTO #colName, #type, #length
END --inner loop
CLOSE col_cursor
SET #sql += ')';
EXECUTE sp_executesql #sql
FETCH NEXT FROM tbl_cursor INTO #tblName
END --outer loop
CLOSE tbl_cursor
Related
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
How do I write the below code in SQL? The query would update each column in the table from the result.
DECLARE #table_list NVARCHAR -- What data type do I use to hold a list?
DECLARE #column_list NVARCHAR
SET #table_list = (SELECT DISTINCT [ID_TABLE_NAME] FROM dbo.VMO) -- there are more than one results
SET #column_list = (SELECT DISTINCT [USR_COL_NAME] FROM dbo.VMO) -- there are more than one results
foreach(#table IN #table_list)
{
foreach(#column IN #column_list)
{
UPDATE #table
SET #column = '101211'
WHERE #column = '10120'
}
}
You can use CURSORs to act like a foreach loop:
DECLARE #table NVARCHAR(255)
DECLARE #column NVARCHAR(255)
DECLARE OUTER_CURSOR CURSOR
FOR SELECT DISTINCT [ID_TABLE_NAME] FROM dbo.VMO
OPEN OUTER_CURSOR
FETCH NEXT FROM OUTER_CURSOR INTO #table
WHILE (##FETCH_STATUS <> -1)
BEGIN
DECLARE INNER_CURSOR CURSOR
FOR SELECT DISTINCT [USR_COL_NAME] FROM dbo.VMO
OPEN INNER_CURSOR
FETCH NEXT FROM INNER_CURSOR INTO #column
WHILE (##FETCH_STATUS <> -1)
BEGIN
DECLARE #strQuery NVARCHAR(MAX)
SET #strQuery = 'UPDATE [' + #table + '] SET [' + #column + '] = ''101211'' WHERE [' + #column + '] = ''10120'';'
EXEC(#strQUERY)
FETCH NEXT FROM INNER_CURSOR INTO #column
END
CLOSE INNER_CURSOR
DEALLOCATE INNER_CURSOR
FETCH NEXT FROM OUTER_CURSOR INTO #table
END
CLOSE OUTER_CURSOR
DEALLOCATE OUTER_CURSOR
T-SQL doesn't have a FOR loop, perhaps it has a WHILE (Transact-SQL)
Use SQL like below:
DECLARE #table_list NVARCHAR -- What data type do I use to hold a list?
DECLARE #column_list NVARCHAR
SET #table_list = (SELECT DISTINCT [ID_TABLE_NAME] FROM dbo.VMO) -- there are more than one results
SET #column_list = (SELECT DISTINCT [USR_COL_NAME] FROM dbo.VMO) -- there are more than one results
WHILE #column_list > #table_list
BEGIN
UPDATE #table
SET #column = '101211'
WHERE #column = '10120'
END;
I have n tables that is unknown before runtime with tablename always being tablename1, tablename2... tablenameN. The first column of each table is always Name. The challenge is to change that column name in each table to Name1, Name2.. NameN. I know I should be using sp_rename and a loop. Having trouble building up the query, I'm pretty new to SQL. Help would be appreciated. THanks
This should do the rename:
DECLARE #counter INT;
DECLARE #tableName NVARCHAR(100);
DECLARE #columnName NVARCHAR(100);
DECLARE #newColumnName NVARCHAR(100);
SET #counter = 1;
WHILE #counter < 65536
BEGIN
SET #tableName = 'tableName' + CAST(#counter AS NVARCHAR)
IF EXISTS(SELECT * FROM sys.tables WHERE name = #tableName)
BEGIN
SET #columnName = #tableName + N'.name';
SET #newColumnName = N'name' + CAST(#counter AS NVARCHAR);
EXEC sp_rename #objname=#columnName, #newName=#newColumnName;
END
ELSE
BEGIN
SET #counter = 65536
END
SET #counter = #counter + 1
END
It's a bit crude though.. and renames only 65535 tables and full amount only when there's none missing in between.
Uncomment sql_exec when you're sure it does what you're expecting :)
DECLARE #TableName sysname, #ColName sysname
DECLARE #num sysname
DECLARE #sql nvarchar(4000)
DECLARE cTables CURSOR FOR SELECT name from dbo.sysobjects where Category = 0 AND type NOT IN (N'F', N'FN', N'IF', N'TF', N'P', N'TR', N'V', N'K') AND name like 'tablename%'
OPEN cTables
FETCH NEXT FROM cTables INTO #TableName
WHILE ##FETCH_STATUS = 0
BEGIN
SET #num = SUBSTRING(#Tablename, 10, 5)
SET #sql = N'sp_RENAME ''' + #TableName + '.[Name]'' , ''[Name' + #num + ']'', ''COLUMN'''
PRINT #sql
-- EXEC sp_sqlexec #sql
FETCH NEXT FROM cTables INTO #TableName
END
CLOSE cTables;
DEALLOCATE cTables;
Here's a SP - give it a try ;-)
CREATE PROCEDURE dbo.Rename
(
#n INT
)
AS
BEGIN
SET NOCOUNT ON
DECLARE #Stmt NVARCHAR(MAX)
DECLARE #i INT
DECLARE #tabname NVARCHAR(MAX)
DECLARE #colname NVARCHAR(MAX)
SET #i = 1
WHILE #i <= #n
BEGIN
SET #tabname = N'tablename' + CAST(#i AS NVARCHAR(MAX))
SET #colname = N'name' + CAST(#i AS NVARCHAR(MAX))
IF EXISTS(SELECT TOP 1 1 FROM sys.tables t WHERE t.name = #tabname)
BEGIN
SET #Stmt = N'EXEC sp_rename ''' + #tabname + '.[name]'', ''' + #colname +''',''COLUMN'''
--PRINT #Stmt
EXEC sp_executesql #Stmt
END
SET #i = #i + 1
END
END
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
I have a stored procedure that contains a cursor to loop through SQL records and populates the string which I will use later as my email text. I'm trying to print it out to verify before I can proceed with it but it seems to not populate the string. Here is my stored procedure in SQL Server 2005.
CREATE PROCEDURE [dbo].[spBody]
AS
DECLARE #MyCursor CURSOR
DECLARE #emailBody nvarchar(max)
DECLARE #statusName nvarchar(max)
DECLARE #deptCode nvarchar(max)
DECLARE #instructors nvarchar(max)
DECLARE #meetingTime nvarchar(max)
SET #MyCursor = CURSOR FAST_FORWARD For
Select StatusName, DeptCode, Instructors, Description from MyTable where StatusID = (select CAST(value AS INT) from Table2 where ConfigOption = 'RequiredStatus')
Open #MyCursor
FETCH NEXT FROM #MyCursor INTO #statusName, #deptCode, #instructors, #meetingTime
WHILE ##FETCH_STATUS = 0
BEGIN
SET #emailBody = #emailBody + #statusName + ' ' + #deptCode + ' ' + #instructors + ' ' + #meetingTime
FETCH NEXT FROM #MyCursor INTO #statusName, #deptCode, #instructors, #meetingTime
END
CLOSE #MyCursor
Print #emailBody
DEALLOCATE #MyCursor
It's because #emailBody starts out as NULL, and any concatenation with NULL yields NULL by default. Do a
SET #emailBody = '';
at the beginning of your script.
Also, strongly consider adding a SET NOCOUNT ON; statement at the top of your stored procedure -- not having NOCOUNT ON can greatly slow the execution of your proc.
Why do you need a cursor for this string concat. Wont the following query suffix
DECLARE #emailBody nvarchar(max)
Set #emailBody = ''
Select #emailBody = #emailBody + StatusName + ' ' + DeptCode + ' ' + Instructors + ' ' + [Description] from MyTable where StatusID = (select CAST(value AS INT) from Table2 where ConfigOption = 'RequiredStatus')
Print #emailBody