I have 1000s of tables like ABC_0001, ABC_0002, ABC_0003 and so on.. and I would like to do 2 things:
From each table, I am looking to copy columns x, y and z in new table.
However not all tables have all 3 columns e.g. ABC_0001 is missing column x, ABC_0002 is missing y and z or any other combinations.
How can I check if the columns is present in selected table and if not assign default value say "0".
There are actually many variables instead of x, y, z which needs to be copied from all the tables into new table. I have this list in a separate table (say TABLE_COL_NAME with variable name, var type e.g. a int, b varchar(200) and so on) and this will change in future, hence I would like to create NEW_TABLE and assign variables in #VarNames dynamically using TABLE_COL_NAME.
Thanks Annamalai for helping with below base code. cheers.
Edit: This is SSMS v18.7
DECLARE
#TableName VARCHAR(500)
,#Sql NVARCHAR(MAX) = ''
,#Id INT
,#VarNames VARCHAR(8000)
DROP TABLE IF EXISTS TABLE_NEW
CREATE TABLE TABLE_NEW (x int, y varchar(200), z varchar(500))
SET #VarNames = 'x, y, z'
DECLARE Table_Cursor CURSOR
FOR
SELECT
ROW_NUMBER() OVER (ORDER BY TABLE_NAME ASC) Id
,TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = 'BASE TABLE'
AND TABLE_NAME LIKE 'ABC%'
OPEN Table_Cursor
FETCH NEXT FROM Table_Cursor INTO #Id,#TableName
WHILE ##FETCH_STATUS = 0
BEGIN
IF(#Id = 1)
BEGIN
SET #Sql = #Sql + 'SELECT ' + #VarNames + 'FROM '+#TableName ----Modify the columns based on your column names
SELECT #SQL
END
ELSE
BEGIN
SET #Sql = #Sql + ' UNION ALL SELECT '+ #VarNames + 'FROM '+#TableName --Modify the columns based on your column names
END
FETCH NEXT FROM Table_Cursor INTO #Id,#TableName
END
CLOSE Table_Cursor
DEALLOCATE Table_Cursor
INSERT INTO TABLE_NEW
EXEC (#Sql)
You have a lousy data model. It looks like you need some code to help fix it.
You can construct a union all statement as follows. First, construct the appropriate logic for the column names for each table:
select 'select ' +
coalesce(max(case when c.column_name = 'x' then c.column_name end), 'null as x') + ', ' +
coalesce(max(case when c.column_name = 'y' then c.column_name end), 'null as y') + ', ' +
coalesce(max(case when c.column_name = 'z' then c.column_name end), 'null as z') + ' ' +
'from ' + c.table_name
from information_schema.columns c
where table_name like 'abc_%';
group by c.table_name;
Then construct the SQL statement:
select string_agg(sql,
'
union all
'
)
from (select 'select ' +
coalesce(max(case when c.column_name = 'x' then c.column_name end), 'null as x') + ', ' +
coalesce(max(case when c.column_name = 'y' then c.column_name end), 'null as y') + ', ' +
coalesce(max(case when c.column_name = 'z' then c.column_name end), 'null as z') + ' ' +
'from ' + c.table_name as sql
from information_schema.columns c
where table_name like 'abc_%'
group by c.table_name
) t;
You can then assign this to a variable and run it. A SQL Fiddle illustrates this.
Note that the above is intentionally simplified to focus on the steps, not taking into account:
The _ in the table name is a wildcard for like.
The schema name as well as the table name.
Column names that need to be escaped.
You can also use the constructed SQL to create a view rather than execute it directly.
Related
I have to write update using dynamic sql becaus i know only name of column that I want to update and names of columns which I will use to join tables in my update. But I don't know the numbers of tables and names. Names of tables I will get in parameter of my procedure in this way
declare #Tables = N'Customer,Employee,Owner'
So I want to have update like this:
update t
set [Status] = 100
from
TemporaryTable t
left join Customer t1 on t1.RecordId = t.RecordId
left join Employee t2 on t2.RecordId = t.RecordId
left join Owner t3 on t3.RecordId =t.RecordId
where
t1.RecordId is null
and t2.RecordId is NULL
and t3.RecordId is null
I know that each table will have column RecordId and want to left join this tables to my TemporaryTable on this column but I don't know the names and numbers of tables. For example I will have one, two, or ten tables with different names. I know that this tables names will be save in parameter #Tables in that way:
#Tables = N'Customer,Employee,Owner'
There is possilble to write this update in dynamic way?
This is an answer, which helps ... to write update using dynamic sql ... and only shows how to generate a dynamic statement. It's based on string splitting. From SQL Server 2016+ you may use STRING_SPLIT() (because here the order of the substrings is not important). For previous versions you need to find a string splitting function.
T-SQL:
DECLARE #Tables nvarchar(max) = N'Customer,Employee,Owner'
DECLARE #join nvarchar(max) = N''
DECLARE #where nvarchar(max) = N''
DECLARE #stm nvarchar(max) = N''
SELECT
#join = #join + CONCAT(
N' LEFT JOIN ',
QUOTENAME(s.[value]),
N' t',
ROW_NUMBER() OVER (ORDER BY (SELECT 1)),
N' ON t',
ROW_NUMBER() OVER (ORDER BY (SELECT 1)),
N'.RecordId = t.RecordId'
),
#where = #where + CONCAT(
N' AND t',
ROW_NUMBER() OVER (ORDER BY (SELECT 1)),
N'.RecordId is NULL'
)
FROM STRING_SPLIT(#Tables, N',') s
SET #stm = CONCAT(
N'UPDATE t SET [Status] = 100 ',
N'FROM TemporaryTable t',
#join,
N' WHERE ',
STUFF(#where, 1, 5, N'')
)
PRINT #stm
EXEC sp_executesql #stm
Notes:
One note, that I think is important - consider passing tables names using table value type for parameter, not as comma-separated text.
It seems like this will suit your needs, though I don't fully understand what you're trying to do. Here we're constructing the final SQL in two pieces (#s and #where) and then concatenating into the final SQL at the end.
declare #Tables varchar(100) = N'Customer,Employee,Owner'
declare #tablenames table (tablename nvarchar(100))
insert #tablenames (tablename)
select value
from string_split(#Tables, ',');
declare #where varchar(max) = ''
declare #s varchar(max) = '
update t
set [Status] = 100
from TemporaryTable t'
select #s += '
left join ' + tablename + ' on ' + tablename + '.RecordId = t.RecordId'
, #where += case when #where = '' then '' else ' and ' end + tablename + '.RecordId is null
'
from #tablenames
print #s + char(13) + ' where ' + #where
exec( #s + char(13) + ' where ' + #where)
I have some bulk views to create for an entire database.
To create a view the general syntax is as follows:
CREATE VIEW [TABLE_NAME]
AS
SELECT [COLUMN1], [COLUMN2], [COLUMN3], [COLUMN4]
FROM [TABLE_NAME]
WITH CHECK OPTION;
I would like to set the column names in the script above by querying the column names ([COLULMN1], [COLUMN2], etc) from INFORMATION_SCHEMA.COLUMNS.
Is there a way to achieve this by table name?
COALESCE is your friend good programmer. What you want to do is get a csv list of COLUMNS. Then using dynamic sql you can auto generate the rest of the code.
declare #columns AS VARCHAR(MAX)
SELECT #COLUMNS = NULL
select #COLUMNS = coalesce(#columns+',','')+c.name from syscolumns as c
inner join sysobjects as o on c.id = o.id
WHERE O.NAME = 'change me to your table name'
SELECT #COLUMNS
SELECT ' CREATE VIEW ' + 'COOL VIEW NAME' + ' AS ' +
' SELECT ' + #COLUMNS +
' FROM '+ ' change me to your table name '+
' WITH CHECK OPTION;'
EDIT
I purposely didn't declare the view anywhere. If you want to declare the view just execute the scripts like so. BUT YOU SHOULD NEVER just execute code on your servers without reading it all I purposely excluded the execution part as I think it is bad judgement just to cut and paste code and execute it without understanding/testing.
DECLARE #sql varchar(max)
SELECT #sql = ' CREATE VIEW ' + 'COOL VIEW NAME' + ' AS ' +
' SELECT ' + #COLUMNS +
' FROM '+ ' change me to your table name '+
' WITH CHECK OPTION;'
EXEC(#sql);
Here's one option... replace "MyTableName" with the table name you want, or wrap it in a cursor that reads TABLE_NAME from INFORMATION_SCHEMA.VIEWS into #tableName:
DECLARE #tableName sysname;
DECLARE #sql nvarchar(max);
DECLARE #columnList nvarchar(max);
SELECT #tableName = 'MyTableName';
SELECT #columnList = '';
SELECT #columnList += CASE WHEN LEN(#columnList) = 0 THEN '' ELSE ', ' END + COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = #tableName ORDER BY ORDINAL_POSITION;
SELECT #sql = 'CREATE VIEW [TABLE_NAME] AS
SELECT ' + #columnList + '
FROM [' + #tableName + ']
WITH CHECK OPTION;'
PRINT #sql
EXEC(#sql);
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)
My requirement is to compare data between two environments and i there is diff in both tables of both environments, insert that data to a temp table and display it.The above solution is not suiting for my scenario. I will explain my scenario in a better way.
In a Curor Cur1, I have all data of DEV from a Table(Report) where Rep_ID=1, Getting corresponding data from the TEST of REPORT Table where Rep_ID=1 In a while loop I am comparing the data of DEV and TEST
if (#DevData1 <> #TestData1)
BEGIN Get ColumnName from Report table where #DevData1 =1 Insert Into #TempTable (ColumnName, DevData1, TestData1)
ENDS Cur1 Ends
When I try to get the column name for a varchar column, I am getting the column name properly with the below query
Declare #ColStrRep nvarchar(1000)= 'select #retValOut= Col.value(''local-name(.)'', ''varchar(max)'') from (select * from Rep_attr where Rep_Name = '''+#reptName +''' for xml path(''''), type) as T(XMLCol) cross apply T.XMLCol.nodes(''*'') as n(Col) where Col.value(''.'', ''varchar(100)'') = '+#reptName +''
print #ColStrRep
EXEC Sp_executesql #ColStrRep,N'#retValOut nvarchar(100) out',#Column_Name OUT
But when I try to get the columnName for an integer column, and that too when we have the same value as 1 in the table( like RepID=1, Flag=1 , IsEmpty=1 etc), the query is getting confused and instead of Rep_ID, it retrieves the column IsEmpty. SO I need another query which just give me the columnname for a columnValue.
Thanks and Regards,
Sajitha
This solution will search using LIKE operator for varchar columns (i.e. column like '%5%') and a strict value for int columns (i.e. column=5)
DECLARE #table_name SYSNAME = 'your_table',
#search_string VARCHAR(100) = '5', --what to search
#column_name SYSNAME,
#type_name SYSNAME,
#sql_string VARCHAR(2000)
BEGIN TRY
DECLARE columns_cur CURSOR
FOR
SELECT columns.name, types.name type_name FROM sys.columns
JOIN sys.types ON columns.system_type_id = types.system_type_id
JOIN sys.objects ON columns.object_id=objects.object_id
WHERE objects.type = 'U' AND objects.name=#table_name
AND types.name IN ('varchar', 'nvarchar', 'int', 'bigint', 'smallint') --types of columns which you want to use for search
OPEN columns_cur
FETCH NEXT FROM columns_cur INTO #column_name, #type_name
WHILE (##FETCH_STATUS = 0)
BEGIN
IF #type_name IN ( 'varchar', 'nvarchar')
SET #sql_string = 'IF EXISTS (SELECT * FROM ' + #table_name + ' WHERE [' + #column_name + '] LIKE ''%' + #search_string + '%'') RAISERROR(''' + #table_name + ', ' + #column_name + ''',0,1) WITH NOWAIT'
ELSE
SET #sql_string = 'IF EXISTS (SELECT * FROM ' + #table_name + ' WHERE [' + #column_name + '] = TRY_CAST(''' + #search_string + ''' AS '+ #type_name +')) RAISERROR(''' + #table_name + ', ' + #column_name + ''',0,1) WITH NOWAIT'
EXECUTE(#sql_string)
FETCH NEXT FROM columns_cur INTO #column_name, #type_name
END
CLOSE columns_cur
DEALLOCATE columns_cur
END TRY
BEGIN CATCH
CLOSE columns_cur
DEALLOCATE columns_cur
RAISERROR(' - No access to table %s',0,1,#table_name) WITH NOWAIT
END CATCH
Thanks for the suggestion.
But I could manage the situation with the below query.
In case, the column value is a varchar, then the below query gives me the column Name.
Declare #ColStrDesc nvarchar(1000)= 'select
#retValOut= Col.value(''local-name(.)'', ''varchar(max)'')
from (select *
from Rep_attr
where Rep_Name = '''+#reptName +'''
for xml path(''''), type) as T(XMLCol)
cross apply
T.XMLCol.nodes(''*'') as n(Col)
where Col.value(''.'', ''varchar(100)'') = '''+#rep_Desc +''''
print #ColStrDesc
EXEC Sp_executesql #ColStrDesc,N'#retValOut nvarchar(100) out',#Column_Name OUT
In case, the column Value is an integer , thenn below query gives me column name.
Declare #ColErr nvarchar(1000)= 'SELECT #retValOut= STUFF(''''
+ CASE WHEN EXISTS (SELECT 1 FROM [dbo].[Rep_attr] WHERE Cast([Rep_Errs] as VARCHAR(64)) = '+#rep_Errs+') THEN '' Rep_Errs'' ELSE '''' END , 1, 1, '''')'
EXEC Sp_executesql #ColErr,N'#retValOut nvarchar(100) out',#Column_Name OUT
Does anyone know how to check a a variable against all database table with columns storing the same type of information? I have a poorly designed database that stores ssn in over 60 tables within one database. some of the variations of columns in the various tables include:
app_ssn
ca_ssn
cand_ssn
crl_ssn
cu_ssn
emtaddr_ssn
re_ssn
sfcart_ssn
sfordr_ssn
socsecno
ssn
Ssn
SSN
I want to create a stored procedure that will accept a value and check it against every table that has 'ssn' in the name.Does anyone have idea as to how to do this?
-- I assume that table/column names don't need to be surrounded by square braces. You may want to save matches in a table - I just select them. I also assume ssn is a char.
alter proc proc1
#search1 varchar(500)
as
begin
set nocount on
declare #strsql varchar(500)
declare #curtable sysname
declare #prevtable sysname
declare #column sysname
select top 1 #curtable= table_schema+'.'+table_name, #column=column_name
from INFORMATION_SCHEMA.COLUMNS
where CHARINDEX('ssn',column_name) > 0
order by table_schema+'.'+table_name +column_name
-- make sure that at least one column has ssn in the column name
if #curtable is not null
begin
while (1=1)
begin
set #strsql = 'select * from ' +#curtable +' where '+''''+#search1+''''+ ' = '+#column
print #strsql
-- any matches for passed in ssn will match here...
exec (#strsql)
set #prevtable = #curtable+#column
select top 1 #curtable= table_schema+'.'+table_name, #column=column_name
from INFORMATION_SCHEMA.COLUMNS
where CHARINDEX('ssn',column_name) > 0
and table_schema+'.'+table_name +column_name> #prevtable
order by table_schema+'.'+table_name +column_name
-- when we run out of columns that contain ssn we are done...
if ##ROWCOUNT = 0
break
end
end
end
What you will need to do is some research. But here is where you can start;
SELECT tbl.NAME AS TableName
,cl.NAME AS ColumnName
,IDENTITY(INT, 1, 1) AS ID
INTO #ColumnsToLoop
FROM sys.tables tbl
JOIN sys.columns cl ON cl.object_id = tbl.object_id
This will give you the table / column relation then you can simply build a dynamic SQL string based on each row in the query above (basically loop it) and use EXEC or sp_execsql. So basically;
DECLARE #Loop int = (select min(ID) From #ColumnsToLoop),#MX int = (Select MAX(ID) From #ColumnsToLoop)
WHILE(#Loop<=#MX)
BEGIN
DECLARE #SQL nvarchar(MAX) = 'SQL String'
//Construct the dynamic SQL String
EXEC(#SQL);
SET #Loop += 1
END
Perhaps I went a little too crazy with this one, but let me know. I thought it would best the primary key of the search results with the table name so you could join it to your tables. I also managed to do it without a single cursor or loop.
DECLARE #SSN VARCHAR(25) = '%99%',
#SQL VARCHAR(MAX);
WITH CTE_PrimaryKeys
AS
(
SELECT TABLE_CATALOG,
TABLE_SCHEMA,
TABLE_NAME,
column_name
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE D
WHERE OBJECTPROPERTY(OBJECT_ID(constraint_name), 'IsPrimaryKey') = 1
),
CTE_Columns
AS
(
SELECT A.*,
CONCAT(A.TABLE_CATALOG,'.',A.TABLE_SCHEMA,'.',A.TABLE_NAME) AS FullTableName,
CASE WHEN B.COLUMN_NAME IS NOT NULL THEN 1 ELSE 0 END AS IsPrimaryKey
FROM INFORMATION_SCHEMA.COLUMNS A
LEFT JOIN CTE_PrimaryKeys B
ON A.TABLE_CATALOG = B.TABLE_CATALOG
AND A.TABLE_SCHEMA = B.TABLE_SCHEMA
AND A.TABLE_NAME = B.TABLE_NAME
AND A.COLUMN_NAME = B.COLUMN_NAME
),
CTE_Select
AS
(
SELECT
'SELECT ' +
--This returns the pk_col casted as Varchar and the table name in another columns
STUFF((SELECT ',CAST(' + COLUMN_NAME + ' AS VARCHAR(MAX)) AS pk_col,''' + B.TABLE_NAME + ''' AS Table_Name'
FROM CTE_Columns B
WHERE A.Table_Name = B.TABLE_NAME
AND B.IsPrimaryKey = 1
FOR XML PATH ('')),1,1,'')
+ ' FROM ' + fullTableName +
--This is where I list the columns where LIKE desired SSN
' WHERE ' +
STUFF((SELECT COLUMN_NAME + ' LIKE ''' + #SSN + ''' OR '
FROM CTE_Columns B
WHERE A.Table_Name = B.TABLE_NAME
--This is where I filter so I only get desired columns
AND (
--Uncomment the Collate if your database is case sensitive
COLUMN_NAME /*COLLATE SQL_Latin1_General_CP1_CI_AS*/ LIKE '%ssn%'
--list your column Names that don't have ssn in them
--OR COLUMN_NAME IN ('col1','col2')
)
FOR XML PATH ('')),1,0,'') AS Selects
FROM CTE_Columns A
GROUP BY A.FullTableName,A.TABLE_NAME
)
--Unioning them all together and getting rid of last trailing "OR "
SELECT #SQL = COALESCE(#sql,'') + SUBSTRING(selects,1,LEN(selects) - 3) + ' UNION ALL ' + CHAR(13) --new line for easier debugging
FROM CTE_Select
WHERE selects IS NOT NULL
--Look at your code
SELECT SUBSTRING(#sql,1,LEN(#sql) - 11)