I want to get from user variables: num for 'Top' clause and Tbl name from northwind DB,
and to get back result set of top 5, my script is down
create procedure sp_getTop5
(
#num int ,
#tbl nvarchar(max)
)
as
declare #res nvarchar(max);
set #res = 'select top '+str(#num)+' * from '+#tbl
exec #res
---- check
exec sp_getTop5 5, employees
Wow... There's nothing else I can say other than that is one of the most terrifying dynamic statements I have ever seen. You're literally giving a malicious person access to 2GB worth of characters to inject with (that's 1,073,741,824 characters with a nvarchar(MAX)) against a statement that can easily suffer injection. They would LITERALLY be able to do anything they wanted with enough time (and permissions). Please take the time to read my article on Dos and Don'ts of Dynamic SQL and have a look at Little Bobby Tables to understand how dangerous what you have is.
As for your SQL, I don't care that you haven't really asked a question, you need to fix that massive hole in your security model now:
CREATE PROC getTop5 #Num int, #schema sysname, #table sysname AS --Removed sp_ prefix, see after the answer
BEGIN
DECLARE #SQL nvarchar(MAX);
SELECT #SQL = N'SELECT TOP (#Num) 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] = #schema
AND t.[name] = #table;
EXEC sp_executesql #SQL, N'#Num int', #Num;
END;
GO
Then you can execute it as below:
EXEC dbo.getTop5 5, N'dbo', N'YourTable';
This will (as a commenter just reminded me) give N arbitrary rows from the table (not the "Top" rows, and tables don't have an inbuilt order). So a TOP without an ORDER BY means the rows returned could be different every time the query is run.
As for my comment about the prefix: Is the sp_ prefix still a no-no?
Related
I have a stored procedure that gets these parameters:
#TaskId UNIQUEIDENTIFIER,
#ColumnName VARCHAR(255) = NULL,
#CheckBoxValue VARCHAR(255) = NULL
Then I have an UPDATE statement like this:
UPDATE [RedMarkItems]
SET #ColumnName = #CheckBoxValue
WHERE [TaskId] = #TaskId
If I run my stored procedure like:
exec usp_RedMarkItem_Insert
#TaskId = '82ab0c4b-9342-46fa-acbe-c00b87571bf9',
#ColumnName = Item7,
#CheckBoxValue = 1,
#CurrentUser = '6074caea-7a8e-4699-9451-16c2eaf394ef'
It does not affect table just says
Commands completed successfully
But values still the same, but if I replace values in UPDATE statement like
UPDATE [RedMarkItems]
SET Item7 = 1
WHERE [TaskId] = '82ab0c4b-9342-46fa-acbe-c00b87571bf9'
It works! Why is it not working if I use parameters? Regards
The mistake you're making here is you're under the impression that a variable/parameter can be used in place for an object's name. Simply put, it can't. An object's name must be a literal, so you can't do something like:
DECLARE #TableName sysname;
SET #TableName = N'MyTable';
SELECT *
FROM #TableName;
For things like this you need to use dynamic SQL, and (just as importantly) secure dynamic SQL.
Firstly, I would change #ColumnName to the datatype sysname (which is a synonym for nvarchar(128)) and #Checkbox to a bit (A Checkbox can have only 3 values: True/1, False/0 and NULL, so an nvarchar(255) is a very poor data choice). Then your Stored Procedure will look something like:
DECLARE #SQL nvarchar(MAX);
SELECT #SQL = N'UPDATE RedMarkItems' + NCHAR(10) +
N'SET ' + QUOTENAME(c.[name]) + N' = #Checkbox' + NCHAR(10) +
N'WHERE TaskID = #ID;'
FROM sys.tables t
JOIN sys.columns c ON t.object_id = c.object_id
WHERE c.[name] = #ColumnName
AND t.[name] = 'RedMarkItems';
EXEC sp_executesql #SQL, N'#Checkbox bit, #ID uniqueindentifier', #Checkbox = #CheckBoxValue, #ID = #TaskId;
The reason for the references to the sys tables is to ensure that the column does indeed exist. If it does not, then no SQL will be run. This is just an extra safety measure, alongside the use of QUOTENAME.
What you did was set #ColumnName equal to the value in #CheckBoxValue 0 or more times (based on how many rows exist in the table). Likely not what you intended...
Instead, you will either want to use dynamic SQL (set #sql = 'UPDATE … ' + QUOTENAME(#ColumnName) + 'rest of sql') or otherwise build a case statement to handle each column based on the value you are trying to update dynamically. SQL needs to bind the statement at compile time, so you need to have that available at compile time for the query processor to make sure that the column is real, has the right types to do type derivation, etc. Using parameters as you did would prevent all of that logic from working (assuming the semantics were as you intended in your posted question).
Please be careful as SQL injection attacks are possible on non-validated parameters. You would want to make sure that the column name is a valid column name and not something that allows arbitrary SQL code execution under the context of the running transaction.
Ideally I'd like to execute several sql statements as a single exec sp_executesql statement. An example would be where I use one IF EXISTS to determine if a second statement is run:
drop proc ai_ImportDataAddListPosn
go
create proc ai_ImportDataAddListPosn(#ParamTableName NVARCHAR(255), #debug INT )
AS
BEGIN
DECLARE #sql AS NVARCHAR(4000)
SET #sql = N'IF NOT EXISTS(SELECT * FROM syscolumns INNER JOIN sysobjects ON syscolumns.id = sysobjects.id WHERE sysobjects.name = ''' + #ParamTableName + ''' AND Syscolumns.name = ''ListPosn'');'
+ 'alter table [' + #ParamTableName + '] add ListPosn int identity(1,1)'
IF #debug = 1 PRINT #sql
EXEC sp_executesql #sql
END
Go
EXEC ai_ImportDataAddListPosn DeptForMove, 1
I realise that this example does not test for the existence of the table first, its just a simplified example not the real problem. I also am aware of SQL injection and how to combat it. I'm reasonably happy that both statements are good SQL
I thought the ";" may act as a statement terminator
I also am aware of SQL injection and how to combat it. I'm reasonably
happy that both statements are good SQL
There's scant evidence of that in the question. You should be using parameterisation and QUOTENAME.
I thought the ";" may act as a statement terminator
It does but you don't want a statement terminator there.
IF 1=1; SELECT 'Foo';
is invalid syntax.
IF 1=1 SELECT 'Foo';
would work fine however. You just need to replace the semicolon after your Boolean_expression with white space.
I want to create a SQL tabled-value function that will receive a query as n parameter through my API. In my function I want execute that query. The query will be a SELECT statement.
This is what I have done so far and what to achieve but it is not the correct way to do so.
CREATE FUNCTION CUSTOM_EXPORT_RESULTS (
#query varchar(max),
#guid uniqueidentifier,
#tableName varchar(200))
RETURNS TABLE
AS
RETURN
(
-- Execute query into a table
SELECT *
INTO #tableName
FROM (
EXEC(#query)
)
)
GO
Please suggest the correct way!
Try this one -
CREATE PROCEDURE dbo.sp_CUSTOM_EXPORT_RESULTS
#query NVARCHAR(MAX) = 'SELECT * FROM dbo.test'
, #guid UNIQUEIDENTIFIER
, #tableName VARCHAR(200) = 'test2'
AS BEGIN
SELECT #query =
REPLACE(#query,
'FROM',
'INTO [' + #tableName + '] FROM')
DECLARE #SQL NVARCHAR(MAX)
SELECT #SQL = '
IF OBJECT_ID (N''' + #tableName + ''') IS NOT NULL
DROP TABLE [' + #tableName + ']
' + #query
PRINT #SQL
EXEC sys.sp_executesql #SQL
RETURN 0
END
GO
Output -
IF OBJECT_ID (N'test2') IS NOT NULL
DROP TABLE [test2]
SELECT * INTO [test2] FROM dbo.test
What I see in your question is encapsulation of:
taking a dynamic SQL expression
executing it to fill a parametrized table
Why do you want to have such an encapsulation?
First, this can have a negative impact on your database performance. Please read this on EXEC() and sp_executesql() . I hope your SP won't be called from multiple parts of your application, because this WILL get you into trouble, at least performance-wise.
Another thing is - how and where are you constructing your SQL? Obviously you do it somewhere else and it seems its manually created. If we're talking about a contemporary application, there are lot of OR/M solutions for this and manual construction of TSQL in runtime should be always avoided if possible. Not to mention EXEC is not guarding you against any form of SQL injection attacks. However, if all of this is a part of some database administration TSQL bundle, forget his paragraph.
At the end, if you want to simply load a new table from some existing table (or part of it) as a part of some administration task in TSQL, consider issuing a SELECT ... INTO ... This will create a new target table structure for you (omitting indexes and constraints) and copy the data. SELECT INTO will outperform INSERT INTO SELECT because SELECT INTO gets minimally logged.
I hope this will get you (and others) at least a bit on the right track.
You can use stored procedure as well, here is the code that you can try.
CREATE FUNCTION CUSTOM_EXPORT_RESULTS
(
#query varchar(max),
#guid uniqueidentifier,
#tableName varchar(200)
)
RETURNS TABLE
AS
RETURN
(
declare #strQuery nvarchar(max)
-- Execute query into a table
SET #strQuery = REPLACE(#query,'FROM', 'INTO '+#tableName+' FROM')
exec sp_executesql #strQuery
)
GO
I need to create a search for a java app I'm building where users can search through a SQL database based on the table they're currently viewing and a search term they provide. At first I was going to do something simple like this:
SELECT * FROM <table name> WHERE CAST((SELECT COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '<table name>')
AS VARCHAR) LIKE '%<search term>%'
but that subquery returns more than one result, so then I tried to make a procedure to loop through all the columns in a given table and put any relevant fields in a results table, like this:
CREATE PROC sp_search
#tblname VARCHAR(4000),
#term VARCHAR(4000)
AS
SET nocount on
SELECT COLUMN_NAME
INTO #tempcolumns
FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = #tblname
ALTER TABLE #tempcolumns
ADD printed BIT,
num SMALLINT IDENTITY
UPDATE #tempcolumns
SET printed = 0
DECLARE #colname VARCHAR(4000),
#num SMALLINT
WHILE EXISTS(SELECT MIN(num) FROM #tempcolumns WHERE printed = 0)
BEGIN
SELECT #num = MIN(num)
FROM #tempcolumns
WHERE printed = 0
SELECT #colname = COLUMN_NAME
FROM #tempcolumns
WHERE num = #num
SELECT * INTO #results FROM #tblname WHERE CAST(#colname AS VARCHAR)
LIKE '%' + #term + '%' --this is where I'm having trouble
UPDATE #tempcolumns
SET printed = 1
WHERE #num = num
END
SELECT * FROM #results
GO
This has two problems: first is that it gets stuck in an infinite loop somehow, and second I can't select anything from #tblname. I tried using dynamic sql as well, but I don't know how to get results from that or if that's even possible.
This is for an assignment I'm doing at college and I've gotten this far after hours of trying to figure it out. Is there any way to do what I want to do?
You need to only search columns that actually contain strings, not all columns in a table (which may include integers, dates, GUIDs, etc).
You shouldn't need a #temp table (and certainly not a ##temp table) at all.
You need to use dynamic SQL (though I'm not sure if this has been part of your curriculum so far).
I find it beneficial to follow a few simple conventions, all of which you've violated:
use PROCEDURE not PROC - it's not a "prock," it's a "stored procedure."
use dbo. (or alternate schema) prefix when referencing any object.
wrap your procedure body in BEGIN/END.
use vowels liberally. Are you saving that many keystrokes, never mind time, saying #tblname instead of #tablename or #table_name? I'm not fighting for a specific convention but saving characters at the cost of readability lost its charm in the 70s.
don't use the sp_ prefix for stored procedures - this prefix has special meaning in SQL Server. Name the procedure for what it does. It doesn't need a prefix, just like we know they're tables even without a tbl prefix. If you really need a prefix there, use another one like usp_ or proc_ but I personally don't feel that prefix gives you any information you don't already have.
since tables are stored using Unicode (and some of your columns might be too), your parameters should be NVARCHAR, not VARCHAR. And identifiers are capped at 128 characters, so there is no reason to support > 257 characters for #tablename.
terminate statements with semi-colons.
use the catalog views instead of INFORMATION_SCHEMA - though the latter is what your professor may have taught and might expect.
CREATE PROCEDURE dbo.SearchTable
#tablename NVARCHAR(257),
#term NVARCHAR(4000)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #sql NVARCHAR(MAX);
SET #sql = N'SELECT * FROM ' + #tablename + ' WHERE 1 = 0';
SELECT #sql = #sql + '
OR ' + c.name + ' LIKE ''%' + REPLACE(#term, '''', '''''') + '%'''
FROM
sys.all_columns AS c
INNER JOIN
sys.types AS t
ON c.system_type_id = t.system_type_id
AND c.user_type_id = t.user_type_id
WHERE
c.[object_id] = OBJECT_ID(#tablename)
AND t.name IN (N'sysname', N'char', N'nchar',
N'varchar', N'nvarchar', N'text', N'ntext');
PRINT #sql;
-- EXEC sp_executesql #sql;
END
GO
When you're happy that it's outputting the SELECT query you're after, comment out the PRINT and uncomment the EXEC.
You get into an infinite loop because EXISTS(SELECT MIN(num) FROM #tempcolumns WHERE printed = 0) will always return a row even if there are no matches - you need to EXISTS (SELECT * .... instead
To use dynamic SQL, you need to build up a string (varchar) of the SQL statement you want to run, then you call it with EXEC
eg:
declare #s varchar(max)
select #s = 'SELECT * FROM mytable '
Exec (#s)
I am trying to reduce fragmentation in all of the indexes for a database running on SQL Server 2005.
Currently I am trying to use ALTER INDEX in conjunction with sp_MSforeachtable, to apply it to all of the indexes for all of the tables:
sp_MSforeachtable "ALTER INDEX ALL ON ? REBUILD;"
But for some reason this doesn’t always seem to work?
If I try it for a single index, or all of the indexes for a single table then the fragmentation is cleaned up, it just seems to be when I apply it to the whole database that I get problems.
Previously I might have used DBCC DBREINDEX but BOL states it will be removed in the next version of SQL Server, so I don’t want to use it.
Can anyone give me any advice on the best way to tackle cleaning up all of the indexes in a database?
Thanks
If you want to fully automate your SQL Server Index maintenance then I seriously recommend that you check out Michelle Ufford's stored procedure for this.
Index Defrag Script V4.1
It is what I consider to be the best index maintenance script I have ever read.
One of the best features about this script are that you can customize the threshold values that you use in order to determine whether or not to REBUILD or REORGANIZE a given index strucutre.
It also provides the option to limit the number of CPU cores that are utilized by the procedure. An excellent option if you intend to run the script on a busy live production database.
Warning: As with all internet available code, be sure you test it thoroughly before using in a production environment. You will also most likely want to incorporate your own customisation and features too.
Check out the article and accompanying sample script to handle this task at SQL Fool (Michelle Ufford's website):
http://sqlfool.com/2009/06/index-defrag-script-v30/
This is quite a nice solution to handle this once and for all!
The best practice recommendation is to reorganize your index if you have 5-30% of fragmentation, and only rebuild it if it has more than 30% fragmentation. You can easily use these thresholds or specify your own using this script.
Marc
Or you can use Microsoft's index rebuilding script found here http://msdn.microsoft.com/en-us/library/ms188917.aspx
-- Ensure a USE <databasename> statement has been executed first.
SET NOCOUNT ON;
DECLARE #objectid int;
DECLARE #indexid int;
DECLARE #partitioncount bigint;
DECLARE #schemaname nvarchar(130);
DECLARE #objectname nvarchar(130);
DECLARE #indexname nvarchar(130);
DECLARE #partitionnum bigint;
DECLARE #partitions bigint;
DECLARE #frag float;
DECLARE #command nvarchar(4000);
-- Conditionally select tables and indexes from the sys.dm_db_index_physical_stats function
-- and convert object and index IDs to names.
SELECT
object_id AS objectid,
index_id AS indexid,
partition_number AS partitionnum,
avg_fragmentation_in_percent AS frag
INTO #work_to_do
FROM sys.dm_db_index_physical_stats (DB_ID(), NULL, NULL , NULL, 'LIMITED')
WHERE avg_fragmentation_in_percent > 10.0 AND index_id > 0;
-- Declare the cursor for the list of partitions to be processed.
DECLARE partitions CURSOR FOR SELECT * FROM #work_to_do;
-- Open the cursor.
OPEN partitions;
-- Loop through the partitions.
WHILE (1=1)
BEGIN;
FETCH NEXT
FROM partitions
INTO #objectid, #indexid, #partitionnum, #frag;
IF ##FETCH_STATUS < 0 BREAK;
SELECT #objectname = QUOTENAME(o.name), #schemaname = QUOTENAME(s.name)
FROM sys.objects AS o
JOIN sys.schemas as s ON s.schema_id = o.schema_id
WHERE o.object_id = #objectid;
SELECT #indexname = QUOTENAME(name)
FROM sys.indexes
WHERE object_id = #objectid AND index_id = #indexid;
SELECT #partitioncount = count (*)
FROM sys.partitions
WHERE object_id = #objectid AND index_id = #indexid;
-- 30 is an arbitrary decision point at which to switch between reorganizing and rebuilding.
IF #frag < 30.0
SET #command = N'ALTER INDEX ' + #indexname + N' ON ' + #schemaname + N'.' + #objectname + N' REORGANIZE';
IF #frag >= 30.0
SET #command = N'ALTER INDEX ' + #indexname + N' ON ' + #schemaname + N'.' + #objectname + N' REBUILD';
IF #partitioncount > 1
SET #command = #command + N' PARTITION=' + CAST(#partitionnum AS nvarchar(10));
EXEC (#command);
PRINT N'Executed: ' + #command;
END;
-- Close and deallocate the cursor.
CLOSE partitions;
DEALLOCATE partitions;
-- Drop the temporary table.
DROP TABLE #work_to_do;
GO
I use this script together with SQL Server Agent to automate the task. Hope this helps.
The safest and most portable way is to drop the indices and to re-add them.