Pass table and column names as parameters and retrieve value - sql

I am trying to built generic query to pass column name I want to count on and table name I want to select value.
So far this is my code:
ALTER PROCEDURE [dbo].[GenericCountAll]
#TableName VARCHAR(100),
#ColunName VARCHAR(100)
AS
BEGIN
DECLARE #table VARCHAR(30);
DECLARE #Rowcount INT;
SET #table = N'SELECT COUNT(' + #ColunName +') FROM ' + #TableName + '';
EXEC(#table)
SET #Rowcount = ##ROWCOUNT
SELECT #Rowcount
END
Trying to execute like this:
EXEC GenericCountAll 'T_User', 'Id';
but looks like I get two results, first result always returning a value of 1, and the second result returns the real count. Can anyone take a look?

Don't create dynamic sql like that! Imagine if I ran:
EXEC GenericCountAll '*/DROP PROCEDURE dboGenericCountAll;--', '1);/*';
The resulting executed SQL would be:
SELECT COUNT(1);/*) FROM */ DROP PROCEDURE dboGenericCountAll;--
That would, quite simply, DROP your procedure. And that's just a simple example. If i knew I could keep doing malicious things, I might even be able to create a new login or user, and make the a db_owner or sysadmin (depending on the permissions of what ever is being used to run that procedure).
I don't know what the point of the ##ROWCOUNT is either, I doubt that's needed. Thus, to make this SAFE you would need to do something like this:
ALTER procedure [dbo].[GenericCountAll]
#TableName sysname, --Note the datatype change
#ColumnName sysname
AS
BEGIN
DECLARE #SQL nvarchar(MAX);
SELECT N'SELECT COUNT(' + QUOTENAME(c.[name]) + N') AS RowCount' + NCHAR(10) +
N'FROM ' + QUOTENAME(s.[name]) + N'.' + QUOTENAME(t.name) + N';'
FROM sys.tables t
JOIN sys.schemas s ON t.schema_id = s.schema_id
JOIN sys.columns c ON t.object_id = c.object_id
WHERE t.[name] = #TableName
AND c.[name] = #ColumnName;
/*
If either the column or the table doesn't exist, then #SQL
will have a value of NULL. This is a good thing, as it
is a great way to further avoid injection, if a bogus
table or column name is passed
*/
IF #SQL IS NOT NULL BEGIN;
PRINT #SQL; --Your best debugging friend
EXEC sp_executesql #SQL;
END ELSE BEGIN;
RAISERROR(N'Table does not exist, or the Column does not exist for the Table provided.',11,1);
END;
END

Related

Stored procedure to copy table using dynamic query in SQL Server

I want to create a stored procedure for coping table using dynamic query.
I followed this step for creating stored proceduce, link:-https://stackoverflow.com/questions/8698231/sql-server-stored-procedures-to-copy-tables
but I got an error:
Could not find stored procedure 'Select * into tblDetail from salesDetail'
Here is my code:
ALTER PROCEDURE sp_copyOneTableToAnother
#newtable nvarchar(50),
#oldtable nvarchar(50)
AS
BEGIN
DECLARE #sql nvarchar(1000)
SET #sql = 'SELECT * INTO ' + #newtable +
' FROM ' + #oldtable
EXEC #sql
END
exec sp_copyOneTableToAnother #newtable='tblDetail',#oldtable='salesDetail'
The stored procedure was created from above syntax but while calling sp_copyOneTableToAnother, I get an error. Please help me solve it.
There are several problems here, first, your procedure name starts with sp_, which is reserved by Microsoft for Special / System Procedures. That should go.
Next, your parameter types are wrong; the correct data type for an object is a sysname, a synonym of nvarchar(128) NOT NULL, not varchar.
Next, the injection issue; you blindly inject the values of your parameters into your statement and hope that said values aren't malicious. Validate the value of #oldtable and properly quote both parameters.
Finally, the execution should be done by sp_executesql; not using it promotes further injection issues as you can't parametrise EXEC (#SQL) statements.
You also don't define your schemas, which you really should be. I add them as NULLable parameters here, and get the USER's default schema
This results in something like this:
CREATE OR ALTER PROCEDURE dbo.CopyOneTableToAnother #NewTable sysname,
#OldTable sysname,
#NewSchema sysname = NULL,
#OldSchema sysname = NULL AS
BEGIN
SET NOCOUNT ON;
SELECT #NewSchema = ISNULL(#NewSchema,default_schema_name),
#OldSchema = ISNULL(#OldSchema,default_schema_name)
FROM sys.database_principals
WHERE name = USER_NAME();
DECLARE #SQL nvarchar(MAX);
SELECT #SQL = N'SELECT * INTO ' + QUOTENAME(#NewSchema) + N'.' + QUOTENAME(#NewTable) + N' FROM ' + QUOTENAME(s.[name]) + N'.' + QUOTENAME(t.[name]) + N';'
FROM sys.schemas s
JOIN sys.tables t ON s.schema_id = t.schema_id
WHERE s.name = #OldSchema
AND t.[name] = #OldTable;
EXEC sys.sp_executesql #SQL;
END;
db<>fiddle

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;

SELECT INTO dynamic temp table using procedure with dynamic parameter

I can't find a full solution to my problem. I have an existing stored procedure that takes dynamic input as a parameter value. I need to execute this procedure (with a dynamic variable) and would like to somehow SELECT * INTO #mytable without having to declare the schema of the temp table.
I've tried using OPENROWSET but it doesn't allow me to specify the variable (only hard-code it):
select * into #table from openrowset('SQLNCLI', 'Server=localhost;Trusted_Connection=yes;', 'exec SERVER..MYSTOREDPROCEDURE #parameter = 123')
The only other way I'm aware of is wrapping it in a string and using EXEC(#sql), but I can't figure out how to "SELECT * INTO #table" from that.
What are my options? Can I create a UDF table function that can return a dynamic table? Doubtful...
I'm guessing here, but you'll need to do the whole thing in dynamic SQL. So, instead, something like:
DECLARE #SQL nvarchar(MAX);
DECLARE #param nvarchar(10) = 123;
SET #SQL = N'SELECT *' + NCHAR(10) +
N'INTO #table' + NCHAR(10) +
N'FROM OPENROWSET(''SQLNCLI'', ''Server=localhost;Trusted_Connection=yes;'', ''exec SERVER..MYSTOREDPROCEDURE #parameter = ' + QUOTENAME(#param,N'''') +N''');' + NCHAR(10) +
N'SELECT *' + NCHAR(10) + --Of course, I assume you're doing something more complex that a SELECT *.
N'FROM #table;';
PRINT #SQL;
EXEC sp_executesql #SQL;
I think I figured it out. I really only had to modify the temp variable names to put the temp table into the global scope. This works for me with my actual tables (I renamed them for the purposes of this post).
IF OBJECT_ID ('tempdb..##mytemptable') is not null drop table ##mytemptable
declare #id int = 112
declare #sql nvarchar(max) ='SELECT * INTO ##mytemptable FROM OPENROWSET(''SQLNCLI'', ''Server=localhost;Trusted_Connection=yes;'', ''exec SERVER..MyStoredProcedure #ID =' + convert(varchar(10), id) + ''')'
-- invokes and inserts into ##mytemptable
exec sp_executesql #sql
-- now I can query from the global scope
select * from ##mytemptable

How to return all values in the first column of a table without specifying column's name?

I need to create a dynamic sql query in order to return all values in the first column of a table. The table's name needs to be a variable #tableName ( i will run this query for multiple tables depending on several conditions). Also, i don't know exactly the first column's name but it is formed out of Id_#tableName .
I need something like below but written dinamically:
select
(select column_name from INFORMATION_SCHEMA.columns where table_Name= #tableName and ordinal_position=1)
from #tableName
Could you please help me? Thank you in advance!
USE AdventureWorks;
GO
DECLARE #ObjectName SYSNAME = 'Sales.SalesOrderHeader';
DECLARE #SQL NVARCHAR(MAX);
SELECT
#SQL = 'SELECT ' + QUOTENAME(name) + ' FROM ' + #ObjectName
FROM
sys.columns
WHERE
object_id = OBJECT_ID(#ObjectName)
AND column_id = 1;
EXEC sp_executesql #SQL;

List all columns referenced in all procedures of all databases

Is there a way that I can get all the columns and tables referenced in all the stored procedures in all the databases in an instance? The output should be:
Database Procedure Table Column
-------- --------- ----- ------
This will get the list you're after, however it won't help you if you have such column references embedded in dynamic SQL (and may not find references that rely on deferred name resolution). SQL Server doesn't parse the text of the stored procedure to come up with the DMV output.
Try now with COLLATE clauses to deal with cases where you have databases on the same server with different collations.
DECLARE #sql NVARCHAR(MAX) = N'';
SELECT #sql += N'UNION ALL
SELECT
[database] = ''' + REPLACE(name, '''', '''''') + ''',
[procedure] = QUOTENAME(s.name) + ''.'' + QUOTENAME(p.name)
COLLATE Latin1_General_CI_AI,
[table] = QUOTENAME(referenced_schema_name) + ''.''
+ QUOTENAME(referenced_entity_name)
COLLATE Latin1_General_CI_AI,
[column] = QUOTENAME(referenced_minor_name)
COLLATE Latin1_General_CI_AI
FROM ' + QUOTENAME(name) + '.sys.schemas AS s
INNER JOIN ' + QUOTENAME(name) + '.sys.procedures AS p
ON s.[schema_id] = p.[schema_id]
CROSS APPLY ' + QUOTENAME(name)
+ '.sys.dm_sql_referenced_entities'
+ '(QUOTENAME(s.name) + ''.'' + QUOTENAME(p.name), N''OBJECT'') AS d
WHERE d.referenced_minor_id > 0'
FROM sys.databases
WHERE database_id > 4
AND [state] = 0;
SET #sql = STUFF(#sql,1,11,'');
EXEC sp_executesql #sql;
Also the CROSS APPLY syntax won't work if you have databases that are in 80 compatibility mode. Just make sure you don't execute the code in such a database and it should work fine (even if some of the target databases are in 80).
To list all SP Name have contain the specified column name:
SELECT OBJECT_NAME(M.object_id), M.*
FROM sys.sql_modules M
JOIN sys.procedures P
ON M.object_id = P.object_id
WHERE M.definition LIKE '%ColumnName%'
Here is yet another way to do this. This is very DIRTY but I like it. Why? Because I came up with it. Anyway it is using Dynamic SQL inside Dynamic SQL to insert dependency information into temp table that can be queried.
This can be modified into a SP that you can run from time to time to update dependencies information, also temp table can be changes to real table if you want to store it.
IF OBJECT_ID('tempdb.dbo.#SPDependencyDetails') IS NOT NULL
DROP TABLE #SPDependencyDetails
CREATE TABLE #SPDependencyDetails
(
Or_Object_Database NVARCHAR(128)
,Or_Object_Name NVARCHAR(128)
,Ref_Database_Name NVARCHAR(128)
,Ref_Schema_Name NVARCHAR(128)
,Ref_Object_Name NVARCHAR(128)
,Ref_Column_Name NVARCHAR(128)
,Is_Selected BIT
,Is_Updated BIT
,Is_Select_All BIT
,Is_All_Columns_Found BIT
)
DECLARE #database_name VARCHAR(100)
DECLARE database_cursor CURSOR
FOR
SELECT name
FROM sys.databases
WHERE database_id > 4
OPEN database_cursor
FETCH NEXT FROM database_cursor
INTO #database_name
WHILE ##FETCH_STATUS = 0 --Outer Loop begin
BEGIN
DECLARE #WholeLotofSQL NVARCHAR(MAX) = '
DECLARE #object_name VARCHAR(150)
,#sqlstatement NVARCHAR(2500)
DECLARE object_cursor CURSOR --Inner cursor, iterates list of objects that match type
FOR
SELECT name
FROM '+#database_name+'.sys.objects AS o
WHERE o.type = ''P'' --Change Object type to find dependencies of Functions, Views and etc.
ORDER BY 1
OPEN object_cursor
FETCH NEXT FROM object_cursor INTO #object_name
WHILE ##FETCH_STATUS = 0 --Inner Loop Begin
BEGIN
SET #sqlstatement = ''USE '+#database_name+';
INSERT INTO #SPDependencyDetails
SELECT DB_NAME() AS Or_Object_Database
,'''''' + #object_name + '''''' AS Or_Object_Name
,CASE WHEN referenced_database_name IS NULL THEN DB_NAME()
ELSE referenced_database_name
END AS Ref_Database_Name
,referenced_schema_name AS Ref_Schema_Name
,referenced_entity_name AS Ref_Object_Name
,referenced_minor_name AS Ref_Column_Name
,is_selected
,is_updated
,is_select_all
,is_all_columns_found
FROM sys.dm_sql_referenced_entities(''''dbo.'' + #object_name + '''''', ''''OBJECT'''');''
EXEC sys.sp_executesql #sqlstatement
FETCH NEXT FROM object_cursor INTO #object_name
END
CLOSE object_cursor
DEALLOCATE object_cursor'
EXEC sys.sp_executesql #WholeLotofSQL
FETCH NEXT FROM database_cursor INTO #database_name
END
CLOSE database_cursor;
DEALLOCATE database_cursor;
SELECT Or_Object_Database as 'Database'
,Or_Object_Name as 'Procedure'
,Ref_Object_Name as 'Table'
,Ref_Column_Name as 'Column
FROM #SPDependencyDetails