Invalid object name when iterating over all tables - sql

I am trying to iterate over all the tables with a given schema name and make a copy in the same db with another given schema.
This is the script I am using:
use DoctorWho
declare #sql_query as nvarchar(max)
select #sql_query = concat('insert into doctor_generated.' , table_name , ' select * from ' , table_name , ';')
FROM INFORMATION_SCHEMA.tables
WHERE table_schema LIKE 'dbo%';
exec (#sql_query);
However this throws an error:
Invalid object name 'doctor_generated.tblEpisodeEnemy
Upon searching this error, I've refreshed the local cache & made sure I am using the correct db.
Is there anything I am missing?

I suspect what you actually want is something like this. Firstly use string aggregation for your dynamic statement; I assume you are on a fully supported version of SQL Server as you don't state you aren't. Next use QUOTENAME to properly quote your objects and avoid injection.
Then you can execute your dynamic statement:
DECLARE #SQL nvarchar(MAX),
#CRLF nchar(2) = NCHAR(13) + NCHAR(10);
SELECT #SQL = STRING_AGG(N'SELECT * INTO doctor_generated.' + QUOTENAME(t.name) + N' FROM ' + QUOTENAME(s.name) + N'.' + QUOTENAME(t.name) + N';',#CRLF)
FROM sys.schemas s
JOIN sys.tables t ON s.schema_id = t.schema_id
WHERE s.[name] = N'dbo';
--PRINT #SQL;
EXEC sys.sp_executesql #SQL;

Related

Iterate thru on database tables and delete some of them based on the name of the table

Hello I would like to loop thru my database tables and delete the ones that I don't need. Also I would like this code to be a stored procedure.
I would like to iterate thru on this select's table_name_to_be_deleted:
SELECT name as table_name_to_be_deleted
FROM sys.tables
WHERE 7=7
and name like 'x_%'
and modify_date< dateadd(day,-10,GETDATE())
And drop every table that I have in the table_name_to_be_deleted column
drop table *variable*
Sorry no minimum viable product as I am not that familiar in T-SQL, but I would much appreciate your help!
You can use Dynamic SQL to do this. Making use of the sys.schemas and sys.tables you could do something like this:
CREATE PROC dbo.DeleteArchives #OlderThan date AS
BEGIN
DECLARE #SQL nvarchar(MAX),
#CRLF nchar(2) = NCHAR(13) + NCHAR(10);
SET #SQL = STUFF((SELECT #CRLF +
N'DROP TABLE ' + QUOTENAME(s.[name]) + N'.' + QUOTENAME(t.[name])
FROM sys.schemas s
JOIN sys.tables t ON s.schema_id = t.schema_id
WHERE t.[name] LIKE N'x[_]%'
AND t.modify_date < #OlderThan
FOR XML PATH (N''),TYPE).value('.','nvarchar(MAX)'),1,2,N'');
EXEC sys.sp_executesql #SQL;
END;

Securing Dynamic SQL from SQL Injection

I have a dynamic script running on all of the objects in a database and change the schema name for every one from [dbo] to the database name.
The script is working just fine, I would like to know if I can do anything better in order to secure it from SQL Injection?
BEGIN TRANSACTION
/* Change schema to all objects in database (from dbo)*/
DECLARE #SchemaName SYSNAME = db_name();
DECLARE #SQL NVARCHAR(MAX) = N'IF Not Exists (select 1 from sys.schemas where schema_id = SCHEMA_ID(#NewSchemaName))
EXEC(''CREATE SCHEMA ''+#NewSchemaName+'''')' + NCHAR(13) + NCHAR(10);
SELECT #SQL = #SQL + N'EXEC(''ALTER SCHEMA ''+#NewSchemaName+'' TRANSFER [' + SysSchemas.Name + '].[' + DbObjects.Name + ']'');' + NCHAR(13) + NCHAR(10)
FROM sys.Objects DbObjects
INNER JOIN sys.Schemas SysSchemas
ON DbObjects.schema_id = SysSchemas.schema_id
WHERE SysSchemas.Name = 'dbo'
AND (DbObjects.Type IN ('U', 'P', 'V'))
EXECUTE sp_executesql #sql, N'#NewSchemaName sysname', #NewSchemaName = #SchemaName
ROLLBACK
In my quest of securing this one, I used this great article by Thom Andrews:
Dos and Don'ts of Dynamic SQL
this is where I started: github.com/NathanLifshes
The script below should be much more secure.
Note the use of the QUOTENAME function in the beginning of the script.
This would work because if you use the QUOTENAME function "inline" inside an EXEC command, you may get a syntax error. So you need to apply it at an earlier stage.
As luck would have it, you have such an "earlier" stage when you initialize the #SchemaName variable:
BEGIN TRANSACTION
/* Change schema to all objects in database (from dbo)*/
DECLARE #SchemaName SYSNAME = QUOTENAME(db_name());
DECLARE #SQL NVARCHAR(MAX) = N'IF Not Exists (select 1 from sys.schemas where schema_id = SCHEMA_ID(#NewSchemaName))
EXEC(''CREATE SCHEMA ''+#NewSchemaName+'''')' + NCHAR(13) + NCHAR(10);
SELECT #SQL = #SQL + N'EXEC(''ALTER SCHEMA ''+#NewSchemaName+'' TRANSFER ' + QUOTENAME(SysSchemas.Name) + '.' + QUOTENAME(DbObjects.Name) + ''');' + NCHAR(13) + NCHAR(10)
FROM sys.Objects DbObjects
INNER JOIN sys.Schemas SysSchemas
ON DbObjects.schema_id = SysSchemas.schema_id
WHERE SysSchemas.Name = 'dbo'
AND (DbObjects.Type IN ('U', 'P', 'V'))
PRINT #SQL
EXECUTE sp_executesql #sql, N'#NewSchemaName sysname', #NewSchemaName = #SchemaName
ROLLBACK
In this case, I don't see an actual risk for SQL injection, since no value is supplied by a user. The script takes only the database name as an input. The only option to utilize SQL injection is by injecting a command into a database name. This is possible, of course. In order to protect against this option, you should use the QUOTENAME function to properly quote the schema name within your dynamic script.

View columns from tables where name like 'ENG_Parameters_%'

I am trying to create a SQL View that will load columns:
[item],[manufacturer_item],[symbol],[footprint]
from any table that matches the name ENG_Parameter_%.
I need to leave it generic because, at any point in time, ENG_Parameter_% might have new or removed tables, so I cannot just hard-code any table names.
Is there a SQL command that could be made to generate this?
The dynamic sql to create the views:
DECLARE #SQL nvarchar(2000);
SET #SQL=N' SELECT
''CREATE VIEW [vw'' + t.name + '']
AS
SELECT [item],[manufacturer_item],[symbol],[footprint]
FROM ['' + t.name + '']'' AS sql_for_view
FROM sys.columns c
INNER JOIN sys.tables t
ON c.object_id=t.object_id
WHERE c.name LIKE ''ENG_Parameter_%''';
exec sp_executesql #SQL;
Thanks to #OwlsSleeping answer (StackOverflow SQL-UNION ALL), this ended up with exactly what I needed:
DECLARE #SQL nvarchar(max)
select #SQL = COALESCE(#SQL , '') + 'SELECT [item],[manufacturer_item],[symbol],
[footprint] FROM [' + TABLE_NAME + '] UNION ALL '
FROM INFORMATION_SCHEMA.TABLES where TABLE_NAME LIKE 'ENG_Parameters_%'
SELECT #SQL = LEFT(#SQL, LEN(#SQL) - 11)
exec sp_executesql #SQL;

Update all SQL Server columns after SQL injection attack

I have been notified that a company website we have has had a problem, and quickly I can see it had a SQL injection attack. I do not manage the website and have no access to the files (and know how I can prevent this in the future) but my current task is to clean the database. It seems there is HTML appended to almost all varchar columns in a Microsoft SQL Server database.
Is there any way in an easy query or function I can run to check all columns for the offending HTML and update the columns in all tables?
For example a column that was:
---------------------------
|Title
---------------------------
|product1
is now
---------------------------
|Title
---------------------------
|product1</title><style>.atpd{position:absolute;clip:rect(400px,auto,auto,400px);}</style><div class=atpd>Apply here <a href=http://abbypaydayloansonline.com >online payday loans</a></div>
Thanks in advance.
Here's one option, assuming the offending text always starts with </title> and that </title> wouldn't naturally appear in the data.
UPDATE dbo.table_name
SET Title = LEFT(Title, CHARINDEX('</title>', Title)-1)
WHERE Title LIKE '%</title>%'
AND Title LIKE '%abbypaydayloansonline.com%';
If you need to do this for multiple columns across multiple tables (assuming, again, that </title> appears first and </title> would never have appeared naturally in the data prior to the incident), you don't need an explicit cursor:
DECLARE #sql NVARCHAR(MAX);
SET #sql = N'';
SELECT #sql = #sql + N'UPDATE ' + QUOTENAME(OBJECT_SCHEMA_NAME([object_id]))
+ '.' + QUOTENAME(OBJECT_NAME([object_id]))
+ ' SET ' + QUOTENAME(name) + ' = LEFT(' + QUOTENAME(name)
+ ', CHARINDEX(''</title>'', ' + QUOTENAME(name) + ')-1)
WHERE ' + QUOTENAME(name) + ' LIKE ''%</title>%''
AND ' + QUOTENAME(name) + ' LIKE ''%abbypaydayloansonline.com%'''
+ ';' + CHAR(13) + CHAR(10)
FROM sys.columns
WHERE OBJECTPROPERTY([object_id], 'IsMsShipped') = 0
AND system_type_id IN (35,99,167,175,231,239,231);
SELECT #sql;
-- EXEC sp_executesql #sql;
Absolutely inspect the output before running it. Note that Management Studio will only show you a small subset of the actual command that will get executed, so you might also want to run this query to see all of the tables and columns that will be checked:
SELECT [table] = QUOTENAME(OBJECT_SCHEMA_NAME([object_id]))
+ '.' + QUOTENAME(OBJECT_NAME([object_id])),
[column] = name
FROM sys.columns
WHERE OBJECTPROPERTY([object_id], 'IsMsShipped') = 0
AND system_type_id IN (35,99,167,175,231,239,231)
ORDER BY [table], [column];
Assuming you don't want to have to type in all your queries manually, I would recommend creating a CURSOR (as much as I hate cursors) and loop through the system tables, something like this:
SELECT t.name AS table_name,
SCHEMA_NAME(schema_id) AS schema_name,
c.name AS column_name
FROM sys.tables AS t
INNER JOIN sys.columns c ON t.OBJECT_ID = c.OBJECT_ID
Then you can run dynamic SQL to do something like #AaronBertrand suggested.
Declare your cursor and fetch your variables into #table_name and #column_name. Then run something like (untested):
DECLARE #updateSQL NVARCHAR(MAX)
SET #updateSQL = 'UPDATE ' + #table_name + ' SET ' + #column_name + ' = LEFT(' + #column_name + ', CHARINDEX(''</title>'', ' + #column_name + ')-1)'
EXECUTE (#updateSQL)
Good luck.

Search all columns of a table for a value?

I've looked for an answer to this, but all I can find is people asking how to search all columns of ALL tables in a database for a value. I just want to search all columns for a specific table. The code people have come up with for the all tables question is complicated and hard for me to figure out where exactly it's searching a specific table. Can somebody help me out? Thanks
Just use some third party tool. There are several that are 100% free and you can’t go wrong with any of these because they will save you a ton of time.
ApexSQL Search (searches both schema and data), SSMS Toolpack (searches schema and data but not free for SQL Server 2012), SQL Search (searches data only).
Frankly, I don’t really understand why even very experienced DBAs bother writing scripts for this if they can use some tool for free that will do the job.
I have no idea of the column types or data values you're searching for, but I'd guess you're trying to search for a substring among multiple text columns.
This is a job for Full-Text Search.
Don't waste time with LIKE '%' + #SearchStr + '%'. You have to write a lot of complicated code to support it, and that solution won't perform well anyway.
In a similar question I mentioned SQL Workbench/J.
The command that searches the database can also be limited to just one table. So even if that question was PostgreSQL specific, the tool works for SQL Server as well as far as I know.
I modified this stored proc to take a table name as the second parameter and just search that table for the data:
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[SearchOneTable]') AND type in (N'P', N'PC'))
DROP PROCEDURE [dbo].[SearchOneTable]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROC [dbo].[SearchOneTable]
(
#SearchStr nvarchar(100) = 'A',
#TableName nvarchar(256) = 'dbo.Alerts'
)
AS
BEGIN
CREATE TABLE #Results (ColumnName nvarchar(370), ColumnValue nvarchar(3630))
--SET NOCOUNT ON
DECLARE #ColumnName nvarchar(128), #SearchStr2 nvarchar(110)
SET #SearchStr2 = QUOTENAME('%' + #SearchStr + '%','''')
--SET #SearchStr2 = QUOTENAME(#SearchStr, '''') --exact match
SET #ColumnName = ' '
WHILE (#TableName IS NOT NULL) AND (#ColumnName IS NOT NULL)
BEGIN
SET #ColumnName =
(
SELECT MIN(QUOTENAME(COLUMN_NAME))
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = PARSENAME(#TableName, 2)
AND TABLE_NAME = PARSENAME(#TableName, 1)
AND DATA_TYPE IN ('char', 'varchar', 'nchar', 'nvarchar')
AND QUOTENAME(COLUMN_NAME) > #ColumnName
)
IF #ColumnName IS NOT NULL
BEGIN
INSERT INTO #Results
EXEC
(
'SELECT ''' + #TableName + '.' + #ColumnName + ''', LEFT(' + #ColumnName + ', 3630)
FROM ' + #TableName + ' (NOLOCK) ' +
' WHERE ' + #ColumnName + ' LIKE ' + #SearchStr2
)
END
END
SELECT ColumnName, ColumnValue FROM #Results
END
GO
Here is a solution that, like #Decker97's approach, figures out from metadata which columns are eligible for string search. Assumes 2005+. Supports text/ntext (though you shouldn't be using those anymore), char/nchar/varchar/nvarchar, and even puts the leading N on the search string where appropriate. Does not support xml columns.
What it does do slightly differently is that it returns a single resultset for each table, not for every single column, so the output is only one row per match no matter how many columns match.
DECLARE #SearchTerm nvarchar(255) = N'foo',
#TableName nvarchar(128) = NULL,
#sql nvarchar(max) = N'';
;WITH tables(obj_name, obj_id, columns) AS
(
SELECT obj_name = QUOTENAME(s.name) + N'.' + QUOTENAME(t.name),
obj_id = [object_id],
columns = (
SELECT N',' + QUOTENAME(c.name)
FROM sys.columns AS c
WHERE c.[object_id] = t.[object_id]
ORDER BY c.column_id FOR XML PATH(N''),
TYPE).value(N'./text()[1]', N'nvarchar(max)')
FROM sys.tables AS t INNER JOIN sys.schemas AS s
ON t.[schema_id] = s.[schema_id]
WHERE (t.name = #TableName OR #TableName IS NULL)
AND EXISTS
(
SELECT 1 FROM sys.columns AS c
WHERE c.[object_id] = t.[object_id]
AND c.system_type_id IN (35,99,167,175,231,239)
)
)
SELECT #sql += N'SELECT N' + char(39)
+ REPLACE(obj_name, char(39), char(39) + char(39))
+ char(39) + columns + N' FROM ' + obj_name + N' WHERE '
+ STUFF((
SELECT N' OR ' + QUOTENAME(name) + N' LIKE ' + CASE
WHEN c.system_type_id IN (99,231,239)
THEN 'N' ELSE N'' END
+ char(39) + N'%' + #SearchTerm + N'%' + char(39)
FROM sys.columns AS c WHERE c.[object_id] = tables.obj_id
AND c.system_type_id IN (35,99,167,175,231,239)
ORDER BY name FOR XML PATH(''), TYPE
).value(N'./text()[1]', N'nvarchar(max)')
+ char(59) + char(13) + char(10), 1, 4, N'')
FROM tables;
PRINT #sql;
--EXEC sys.sp_executeSQL #sql;
Depending on the number of searchable columns in your system, PRINT won't necessarily show you the full command, and you might think there is a bug in the code (or at least a bug in PRINT) that somehow truncates the text. You can increase the size of Results to Text output in SSMS settings, but that still won't be enough. You can use SELECT CONVERT(xml, #sql); instead (see this tip for more info).
If you are on SQL Server 2017 or greater
The new function STRING_AGG() allows you to simplify the code quite a bit, and if you have lots of existing code where you concatenate strings using FOR XML PATH, it can be useful to update those to more modern methods as you revisit them. So here's a version that uses STRING_AGG() in its place:
DECLARE #SearchTerm nvarchar(255) = N'foo',
#TableName nvarchar(128) = NULL,
#sql nvarchar(max) = N'';
;WITH tables(obj_name, obj_id, columns) AS
(
SELECT obj_name = QUOTENAME(s.name) + N'.' + QUOTENAME(t.name),
obj_id = [object_id],
columns = (SELECT STRING_AGG(QUOTENAME(c.name), N',')
WITHIN GROUP (ORDER BY c.column_id)
FROM sys.columns AS c WHERE c.[object_id] = t.[object_id]
AND c.system_type_id IN (35,99,167,175,231,239))
FROM sys.tables AS t INNER JOIN sys.schemas AS s
ON t.[schema_id] = s.[schema_id]
WHERE (t.name = #TableName OR #TableName IS NULL)
)
SELECT #sql += N'SELECT N' + char(39)
+ REPLACE(obj_name, char(39), char(39) + char(39))
+ char(39) + N',' + columns + N' FROM ' + obj_name + N' WHERE '
+ (SELECT STRING_AGG(QUOTENAME(name) + N' LIKE ' + CASE
WHEN c.system_type_id IN (99,231,239)
THEN 'N' ELSE N'' END
+ char(39) + N'%' + #SearchTerm + N'%' + char(39),
N' OR ') + N';' + char(13) + char(10)
FROM sys.columns AS c WHERE c.[object_id] = tables.obj_id
AND c.system_type_id IN (35,99,167,175,231,239))
FROM tables WHERE columns IS NOT NULL;
PRINT #sql;
--EXEC sys.sp_executeSQL #sql;
More dynamic SQL resources
This sounds like you just want to know which table and column some data is stored, not that you want to know that during the execution of your code, or change it. I also had this problem and this solved it:
Download your database in SQL format (using phpmyadmin, for example), open it with a text editor and search for the occurrences you want.
I have come across this issue, normally after uploading data from a CSV file where I had to modify the commas ',' in text fields so the data would load properly & once in SQL Server, the need comes to change the modified character back to a comma & it's helpful to be able to search the entire table. Greg Robidoux at mssqltips has posted a Stored Procedure that does just this, searches the Columns of a specified Table for a particular String value. You can find it along with a SPROC that does not use the cursor & more details here:
https://www.mssqltips.com/sqlservertip/1522/searching-and-finding-a-string-value-in-all-columns-in-a-sql-server-table/
I have posted the original SPROC below:
USE master
GO
CREATE PROCEDURE dbo.sp_FindStringInTable #stringToFind VARCHAR(100), #schema sysname, #table sysname
AS
DECLARE #sqlCommand VARCHAR(8000)
DECLARE #where VARCHAR(8000)
DECLARE #columnName sysname
DECLARE #cursor VARCHAR(8000)
BEGIN TRY
SET #sqlCommand = 'SELECT * FROM [' + #schema + '].[' + #table + '] WHERE'
SET #where = ''
SET #cursor = 'DECLARE col_cursor CURSOR FOR SELECT COLUMN_NAME
FROM ' + DB_NAME() + '.INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = ''' + #schema + '''
AND TABLE_NAME = ''' + #table + '''
AND DATA_TYPE IN (''char'',''nchar'',''ntext'',''nvarchar'',''text'',''varchar'')'
EXEC (#cursor)
OPEN col_cursor
FETCH NEXT FROM col_cursor INTO #columnName
WHILE ##FETCH_STATUS = 0
BEGIN
IF #where <> ''
SET #where = #where + ' OR'
SET #where = #where + ' [' + #columnName + '] LIKE ''' + #stringToFind + ''''
FETCH NEXT FROM col_cursor INTO #columnName
END
CLOSE col_cursor
DEALLOCATE col_cursor
SET #sqlCommand = #sqlCommand + #where
PRINT #sqlCommand
EXEC (#sqlCommand)
END TRY
BEGIN CATCH
PRINT 'There was an error. Check to make sure object exists.'
PRINT error_message()
IF CURSOR_STATUS('variable', 'col_cursor') <> -3
BEGIN
CLOSE col_cursor
DEALLOCATE col_cursor
END
END CATCH
I've found the best answer is just to select * from the table and then copy & paste into Excel and hit Ctrl+F
Cutesie little work-around that involves a bit less copy-paste, since the command can be produced easily using queries.
Invert the IN operator in a WHERE clause as VALUE IN <fields> (as opposed to the more common use case of FIELD IN <values>).
SELECT col_1, col_2, ... , col_n
FROM <table>
WHERE CAST(<value> AS varchar(max)) IN
(
CAST(col_1 AS varchar(max)),
CAST(col_2 AS varchar(max)),
...,
CAST(col_n AS varchar(max))
)
Since varchar is a pretty malleable data type, this becomes pretty foolproof (you can throw ISNULL/NULLIF to modify as needed), and depending on the use case can probably be used across more than one search value.
A more robust solution, using dynamic execution and PL/SQL would be to write a procedure to dynamically build a view of the target table (via reading e.g. MySQL's information_schema schema, Oracle's SYS schema, etc.), constrained to a where clause containing the input string hard-coded into a series of 'OR'-concatenated/IN clauses for filter conditions.