SQL Server passing identifiers to stored procedures/dynamic SQL - sql

Background:
SQL Server Management Studio allows to define own query shortcuts (Tools > Options > Environment > Keyboard > Query Shortcuts):
Image from: http://social.technet.microsoft.com/wiki/contents/articles/3178.how-to-create-query-shortcuts-in-sql-server-management-studio.aspx
my_schema.my_table
-- highlight it
-- press CTRL + 3 and you will get the number of rows in table
It works ok, but it concatenates query in basic form (as far as I know only at the end). Query:
SELECT COUNT(*) FROM my_schema.my_table;
Attempt #1
Now I want to write something more specific, for example pass/concatenate table name to following query (this is just example):
SELECT * FROM sys.columns WHERE [object_id] = OBJECT_ID(...)
So when I write in query shortcuts:
SELECT * FROM sys.columns WHERE [object_id] = OBJECT_ID('
I have to use:
my_schema.my_table')
-- highlight it
-- press CTRL + 3
The additional ') is very ugly and inconvenient.
Attempt #2:
The second trial is to use Dynamic-SQL:
EXEC dbo.sp_executesql
N'SELECT * FROM sys.columns WHERE [object_id] = OBJECT_ID(#obj_name)'
,N'#obj_name SYSNAME'
,
Executing:
my_table
-- highligt it
-- and run
LiveDemo
Works also when table name is quoted [my_table]. As long as object is in dbo(default) schema.
The problem is that when table has schema it won't work:
EXEC dbo.sp_executesql
N'SELECT * FROM sys.columns WHERE [object_id] = OBJECT_ID(#obj_name)'
,N'#obj_name SYSNAME'
,
Executing:
my_schema.my_table
[my_schema].[my_table]
LiveDemo2
Incorrect syntax near '.'.
Of course I could write:
EXEC dbo.sp_executesql
N'SELECT * FROM sys.columns WHERE [object_id] = OBJECT_ID(#obj_name)'
,N'#obj_name SYSNAME'
,'
and call it as:
[my_schema].[my_table]'
But additional ' is also ugly and inconvenient.
Questions:
Is it possible to pass value, to query shortcuts window, in the middle (positional or even more than one value)?
Is it possible to pass do stored_procedure/dynamic-sql qualified identifier without wraping it with ', "?
Remarks:
I do not search for plugins to SSMS
I do not want to wrap object_name as "my_schema.my_table"
I know there is sp_helptext (this is just example, I search for method)
First question is tool specific (I am aware of it), but second is about SQL Server.
EDIT:
To clarify passing identifier to SP without ' or ":
CREATE TABLE dbo.my_table(col INT);
GO
CREATE PROCEDURE dbo.my_proc
#a SYSNAME
AS
SELECT *
FROM sys.columns
WHERE [object_id] = OBJECT_ID(#a)
GO
EXEC dbo.my_proc
#a = my_table;
EXEC dbo.my_proc
#a = dbo.my_table;
-- Incorrect syntax near '.'.
LiveDemo3

1. Is it possible to pass value, to query shortcuts window, in the middle?
To my knowledge, there is no workaround to achieve this.
1-b. Is it possible to pass more than one value?
It can be done for string values using a separator character and then splitting the value on the other side. Sadly, there isn't many special character to fulfill this job because they pretty much all raise a syntax error. However '#' could be a wise choice because it's already a special character for SQL for temp table going in tempDB. Just check if you don't already have identifier that are using it because it's permitted by SQL (tough, it's forbidden as first char).
Here is an example of this :
Create a stored procedure to receive the arguments into one single string and split the string to have each arguments.
CREATE PROCEDURE sp_PassingMultipleStringValues
#Param1 NVARCHAR(MAX)
AS
--Here I'm using a XML split, but feel free to use any string split function you already have.
DECLARE #xml AS XML,
#separator AS VARCHAR(1)
SELECT #separator ='#',
#xml = CAST('<X>'+ (REPLACE(#Param1,#separator ,'</X><X>') +'</X>') AS XML)
SELECT N.value('.', 'VARCHAR(200)') AS value
FROM #xml.nodes('X') as T(N)
--Do whatever is needed with them
Then configure your shortcut as seem on this image. (Note the space at the end)
Result :
2. Is it possible to pass to a stored_procedure/dynamic-sql qualified identifier without wraping it with ', "?
Do you have multiple schema with the same identifier?
Because if not, what about retrieve it on the other side using sys.schemas instead of passing it?
Instead of having an inconvenient character to type at the end, you would have fewer things to type.
With the retrieved schema, you can then do dynamic SQL for whatever is needed with it.
SELECT #Param1 = REPLACE(REPLACE(#Param1, '[', ''), ']', '')
SELECT TOP 1 #Param1 = [Schema].name + '.' + #Param1
FROM sys.objects AS obj
JOIN sys.schemas AS [Schema] ON obj.schema_id = [Schema].schema_id
WHERE obj.name = #Param1
SELECT *
FROM sys.columns
WHERE [object_id] = OBJECT_ID(#Param1)
DECLARE #Query NVARCHAR(MAX) = 'SELECT TOP 1 * FROM ' + #Param1
EXEC sp_sqlexec #Query
If you do want to handle two different schema with the same identifier then it's still feasible by passing the schema and the identifier as two arguments using the method explained in answer 1-b.
Everything in one example
Since here we want to pass multiple identifiers and specify their schema, two separators are needed.
CREATE PROCEDURE sp_MultiArgsWithSchema
#Param1 NVARCHAR(MAX)
AS
SELECT #Param1 = REPLACE(REPLACE(#Param1, '[', ''), ']', '')
--Here I'm using a XML split, but feel free to use any string split function you already have.
DECLARE #xml AS XML,
#ArgSeparator AS VARCHAR(2),
#SchemaSeparor AS VARCHAR(1)
SELECT #ArgSeparator = '##',
#SchemaSeparor = '#',
#xml = CAST('<X>'+ (REPLACE(#Param1,#ArgSeparator, '</X><X>') +'</X>') AS XML)
IF OBJECT_ID('tempdb..#QualifiedIdentifiers') IS NOT NULL
DROP TABLE #QualifiedIdentifiers;
--While splitting, we are putting back the dot instead of '#' between schema and name of object
SELECT QualifiedIdentifier = REPLACE(N.value('.', 'VARCHAR(200)'), #SchemaSeparor, '.')
INTO #QualifiedIdentifiers
FROM #xml.nodes('X') as T(N)
SELECT * FROM #QualifiedIdentifiers
--From here, use what is inside #QualifiedIdentifiers and Dynamic SQL if need to achieve what is needed
DECLARE #QualifiedIdentifier NVARCHAR(500)
WHILE EXISTS(SELECT TOP 1 1 FROM #QualifiedIdentifiers)
BEGIN
SELECT TOP 1 #QualifiedIdentifier = QualifiedIdentifier
FROM #QualifiedIdentifiers
SELECT *
FROM sys.columns
WHERE [object_id] = OBJECT_ID(#QualifiedIdentifier)
DELETE TOP (1)
FROM #QualifiedIdentifiers
WHERE QualifiedIdentifier = #QualifiedIdentifier
END
Usage (note that specifying the schema isn't mandatory) :
So, since it is inconvenient to have to double the splitting character, it would be best if schema could be guessed like stated above.

Here is a long shot to pass multi-part identifier without wrapping it with quotes.
Solution:
The query shortcuts is going to create a synonym in the database with a specific name and a DDLTrigger to intercept this specific synonym creation.
Setup up the following shortcut in Query ShortCuts. (Make sure you include last space)
DECLARE #CreateTriggerSQL NVARCHAR(MAX) = 'CREATE TRIGGER DDLTrigger_QueryShortcutX ON DATABASE FOR CREATE_SYNONYM AS BEGIN DECLARE #EventData XML = EVENTDATA(), #SynonymName NVARCHAR(255), #DbName NVARCHAR(255), #SchemaName NVARCHAR(255), #ObjectName NVARCHAR(255), #Alias NVARCHAR(255) SELECT #SynonymName = #EventData.value(''(/EVENT_INSTANCE/ObjectName)[1]'', ''NVARCHAR(255)'') IF(#SynonymName = ''QueryShortcutX'') BEGIN DROP SYNONYM QueryShortcutX DROP TRIGGER DDLTrigger_QueryShortcutX ON DATABASE SELECT #DbName = #EventData.value(''(/EVENT_INSTANCE/DatabaseName)[1]'', ''NVARCHAR(255)''), #SchemaName = #EventData.value(''(/EVENT_INSTANCE/TargetSchemaName)[1]'', ''NVARCHAR(255)''), #ObjectName = #EventData.value(''(/EVENT_INSTANCE/TargetObjectName)[1]'', ''NVARCHAR(255)''), #Alias = (CASE WHEN LEN(#SchemaName) > 0 THEN #SchemaName + ''.'' ELSE '''' END) + #ObjectName /*EXEC yourStoredProcHere #Param = #Alias*/ SELECT DbName = #DbName, SchemaName = #SchemaName, ObjectName = #ObjectName, Alias = #Alias, ObjectId = OBJECT_ID(#Alias) END END' EXEC sp_executeSQL #CreateTriggerSQL CREATE SYNONYM QueryShortcutX FOR
As #Vladimir suggested, here we use "sp_executesql" to be able to create the trigger and the synonym at the same time.
Here is the code of the trigger without being inlined.
CREATE TRIGGER DDLTrigger_QueryShortcutX ON DATABASE FOR CREATE_SYNONYM
AS
BEGIN
DECLARE #EventData XML = EVENTDATA(),
#SynonymName NVARCHAR(255),
#DbName NVARCHAR(255),
#SchemaName NVARCHAR(255),
#ObjectName NVARCHAR(255),
#Alias NVARCHAR(255)
SELECT #SynonymName = #EventData.value('(/EVENT_INSTANCE/ObjectName)[1]', 'NVARCHAR(255)')
--Safety in case someone else really create a synonym meanwhile.
IF(#SynonymName = 'QueryShortcutX')
BEGIN
--2. Clean up what we created
DROP SYNONYM QueryShortcutX
DROP TRIGGER DDLTrigger_QueryShortcutX ON DATABASE
--3. Parsing identifier code here
SELECT #DbName = #EventData.value('(/EVENT_INSTANCE/DatabaseName)[1]', 'NVARCHAR(255)'),
#SchemaName = #EventData.value('(/EVENT_INSTANCE/TargetSchemaName)[1]', 'NVARCHAR(255)'),
#ObjectName = #EventData.value('(/EVENT_INSTANCE/TargetObjectName)[1]', 'NVARCHAR(255)'),
#Alias = (CASE WHEN LEN(#SchemaName) > 0 THEN #SchemaName + '.' ELSE '' END) + #ObjectName
--4. Here, write any print/select statement you want.
--For maintenance, it would be easier to just call a stored procedure from here with parameter and put the desired print/select there.
--Thus avoiding to redo inlining the whole trigger each time.
--EXEC yourStoredProcHere #Param = #Alias
SELECT DbName = #DbName,
SchemaName = #SchemaName,
ObjectName = #ObjectName,
Alias = #Alias,
ObjectId = OBJECT_ID(#Alias)
END
END
Here is the code of the shortcut without being inlined.
DECLARE #CreateTriggerSQL NVARCHAR(MAX) = 'Trigger creation code here...'
IF EXISTS(SELECT TOP 1 1 FROM sys.triggers WHERE name = 'DDLTrigger_QueryShortcutX')
BEGIN
DROP TRIGGER DDLTrigger_QueryShortcutX ON DATABASE
END
EXEC sp_executeSQL #CreateTriggerSQL
IF EXISTS(SELECT TOP 1 1 FROM sys.synonyms WHERE name = 'QueryShortcutX')
BEGIN
DROP SYNONYM QueryShortcutX
END
CREATE SYNONYM QueryShortcutX FOR
The trigger drop itself and the synonym to avoid schema pollution.
The trigger parse the information to retrieve identifier.
Use the identifier for your needs. (use dynamic SQL if needed)
Results for each test item
1.RealColumnName
2.WhatEverText
3.dbo.tests
4.[No selection]
5.dbo.tests.very.much
DbName SchemaName ObjectName Alias ObjectId
1.TEST RealColumnName RealColumnName NULL --FN OBJECT_ID doesn't return value with only column name
2.TEST WhatEverText WhatEverText NULL
3.TEST dbo tests dbo.tests 245575913
4.Incorrect syntax near 'FOR'.
5.TEST very much very.much NULL
The parsing I've made doesn't handle identifier with more than two multipart properly. If you want to improve it. The following XML show you which tag to use.
<TargetServerName>dbo</TargetServerName>
<TargetDatabaseName>tests</TargetDatabaseName>
<TargetSchemaName>very</TargetSchemaName>
<TargetObjectName>much</TargetObjectName>
Note:
If you prefer, you can let the trigger stay permanently within the database.
Also, if you want to pass multiple identifiers, string parsing like I do in my other answer is still a possibility here.
To use this solution, user will have to have "create synonym permission" and either own the schema or have "ALTER SCHEMA permission".

Related

How to check existence of tables in different databases in a proc - SQLServer

I am working on a proc which is present in one database, it picks table from other source database (passed as parameter) and insert missing values to other destination database (again passed as parameter).
My insert query is a dynamic query and before executing it, I want to check existence of both the source and destination tables. I don't want to execute dynamic queries again just to check existence of tables as they are not advisable to use in bulk,.
Is there any way I can achieve something like below
USE #DbName
GO
IF EXISTS (
SELECT 1
FROM sys.tables
WHERE NAME = #table
AND type = 'U'
)
BEGIN
code here...
END
or
IF EXISTS (
SELECT 1
FROM #fulltableName -- where variable consists 'dbname.sys.tables'
WHERE NAME = #table
AND type = 'U'
)
BEGIN
code here...
END
with the help of only variables and without executing dynamic queries.
Check if the OBJECT_ID returns a value. If you are checking for tables, use U as 2nd parameter.
IF OBJECT_ID('DatabaseName.dbo.TableName', 'U') IS NOT NULL
BEGIN
-- Table Exists
END
With variables:
DECLARE #DatabaseName VARCHAR(100) = 'MyCustomDatabase'
DECLARE #SchemaName VARCHAR(100) = 'dbo'
DECLARE #TableName VARCHAR(100) = 'Countries'
IF OBJECT_ID(QUOTENAME(#DatabaseName) + '.' + QUOTENAME(#SchemaName) + '.' + QUOTENAME(#TableName), 'U') IS NOT NULL
BEGIN
-- Do stuff
END
Make sure to execute with a login with enough privileges.

Send query as parameter to SQL function

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

Stored procedure get parameter list and current values

Not sure how to implement this, but I need a way to get the current list of parameters for a stored procedure as well as their passed in values (this code will be executed in the stored procedure itself).
I know I can use sys.parameters to get the parameter names, but how to get the actual values?
What I need to do with this is to make a char string of the form
#param_name1=#value1,#param_name2=#value2,...,#param_namen=#valuen
I have tried to use dynamic sql, but not having much joy with that.
Any ideas??
Edit:
Currently I am just going through all the parameters one-by-one to build the string. However I want a "better" way to do it, since there are quite a few parameters. And incase parameters are added later on (but the code to generate the string is not updated).
I tried using dynamic sql but gave up, since the sp_executesql sp requires parameters be passed into it...
You state '(this code will be executed in the stored procedure itself).' so assuming you are in the procedure you will already know the parameter names as you have to declare them when creating your procedure. Just do a select and put the names inside text fields
ALTER PROCEDURE procname
(
#param1 NVARCHAR(255)
,#param2 INT
...
)
SELECT [Parameters] = '#param1=' + #param1
+ ',#param2=' + CONVERT(NVARCHAR(MAX),#param2)...
The CONVERT is there as an example for non-char datatypes.
update
You will need to create a linked server that points to itself to use the OPENQUERY function.
USE [master]
GO
/****** Object: LinkedServer [.] Script Date: 04/03/2013 16:22:13 ******/
EXEC master.dbo.sp_addlinkedserver #server = N'.', #srvproduct=N'', #provider=N'SQLNCLI', #datasrc=N'.', #provstr=N'Integrated Security=SSPI'
/* For security reasons the linked server remote logins password is changed with ######## */
EXEC master.dbo.sp_addlinkedsrvlogin #rmtsrvname=N'.',#useself=N'True',#locallogin=NULL,#rmtuser=NULL,#rmtpassword=NULL
GO
Now you can do something like this cursor to get each parameter name and then use dynamic sql in OPENQUERY to get the value:
DECLARE curParms CURSOR FOR
SELECT
name
FROM sys.parameters
WHERE OBJECT_ID = OBJECT_ID('schema.procedurename')
ORDER BY parameter_id
OPEN curParms
FETCH curParms INTO #parmName
WHILE ##FETCH_STATUS <> -1
BEGIN
SELECT #parmName + '=' + (SELECT * FROM OPENQUERY('linkedservername','SELECT ' + #parmName))
FETCH curParms INTO #parmName
END
CLOSE curParms
DEALLOCATE curParms
Since SQL Server 2014 we have sys.dm_exec_input_buffer, it is a table valued function with an output column event_info that gives the full execution statement (including parameters).
We can parse the param values from sys.dm_exec_input_buffer and get the param names from sys.parameters and join them together to get the string you want.
For example:
create procedure [dbo].[get_proc_params_demo]
(
#number1 int,
#string1 varchar(50),
#calendar datetime,
#number2 int,
#string2 nvarchar(max)
)
as
begin
-- get the full execution statement
declare #statement nvarchar(max)
select #statement = event_info
from sys.dm_exec_input_buffer(##spid, current_request_id())
-- parse param values from the statement
declare #proc_name varchar(128) = object_name(##procid)
declare #param_idx int = charindex(#proc_name, #statement) + len(#proc_name)
declare #param_len int = len(#statement) - #param_idx
declare #params nvarchar(max) = right(#statement, #param_len)
-- create param values table
select value, row_number() over (order by current_timestamp) seq
into #params
from string_split(#params, ',')
-- get final string
declare #final nvarchar(max)
select #final = isnull(#final + ',','') + p1.name + '=' + ltrim(p2.value)
from sys.parameters p1
left join #params p2 on p2.seq = parameter_id
where object_id = ##procid
select #final params
end
To test it:
exec get_proc_params_demo 42, 'is the answer', '2019-06-19', 123456789, 'another string'
Returns the string you want:
#number1=42,#string1='is the answer',#calendar='2019-06-19',#number2=123456789,#string2='another string'
I have something similar wrapped as a UDF. I use it for error logging in catch blocks.

Return multiple columns as single comma separated row in SQL Server 2005

I'm curious to see if this is possible.
I have a table or this could be specific to any old table with data. A simple SELECT will return the columns and rows as a result set. What I'm trying to find out if is possible to return rows but rather than columns, the columns concatenated and are comma separated. So expected amount of rows returned but only one varchar column holding comma separated results of all the columns just like a CSV file.
Thanks.
[UPDATE]
Here is a bit more detail why I'm asking. I don't have the option to do this on the client, this is a task I'm trying to do with SSIS.
Scenario: I have a table that is dynamically created in SSIS but the column names change each time it's built. The original package uses BCP to grab the data and put it into a flat file but due to permissions when run as a job BCP can't create the flat file at the required destination. We can't get this changed either.
The other issue is that with SSIS 2005, using the flat files destination, you have to map the column name from the input source which I can't do because the column names keep changing.
I've written a script task to grab all the data from the original tables and then use stream writer to write to the CSV but I have to loop through each row then through each column to produce the string built up of all the columns. I want to measure performance of this concatenation of columns on sql server against a nasty loop with vb.net.
If I can get sql to produce a single column for each row I can just write a single line to the text file instead of iterating though each column to build the row.
I Think You Should try This
SELECT UserName +','+ Password AS ColumnZ
FROM UserTable
Assuming you know what columns the table has, and you don't want to do something dynamic and crazy, you can do this
SELECT CONCAT(ColumnA, ',', ColumnB) AS ColumnZ
FROM Table
There is a fancy way to this using SQL Server's XML functions, but for starters could you just cast the contents of the columns you care about as varchar and concatenate them with commas?
SELECT cast(colA as varchar)+', '+cast(colB as varchar)+', '+cast(colC as varchar)
FROM table
Note, that this will get tripped up if any of your contents have a comma or double quotes in them, in which case you can also use a replace function on each cast to escape them.
This could stand to be cleaned up some, but you can do this by using the metadata stored in sys.objects and sys.columns along with dynamic SQL. Note that I am NOT a fan of dynamic SQL, but for reporting purposes it shouldn't be too much of a problem.
Some SQL to create test data:
if (object_id('test') is not null)
drop table test;
create table test
(
id uniqueidentifier not null default newId()
,col0 nvarchar(255)
,col1 nvarchar(255)
,col2 nvarchar(255)
,col3 nvarchar(255)
,col4 nvarchar(255)
);
insert into test (col0,col1,col2,col3,col4)
select 'alice','bob','charlie','dave','emily'
union
select 'abby','bill','charlotte','daniel','evan'
A stored proc to build CSV rows:
-- emit the contents of a table as a CSV.
-- #table_name: name of a permanent (in sys.objects) table
-- #debug: set to 1 to print the generated query
create procedure emit_csv(#table_name nvarchar(max), #debug bit = 0)
as
declare #object_id int;
set nocount on;
set #object_id = object_id(#table_name);
declare #name nvarchar(max);
declare db_cursor cursor for
select name
from sys.columns
where object_id = #object_id;
open db_cursor;
fetch next from db_cursor into #name
declare #query nvarchar(max);
set #query = '';
while ##FETCH_STATUS = 0
begin
-- TODO: modify appended clause to escape commas in addition to trimming
set #query = #query + 'rtrim(cast('+#name+' as nvarchar(max)))'
fetch next from db_cursor into #name;
-- add concatenation to the end of the query.
-- TODO: Rearrange #query construction order to make this unnecessary
if (##fetch_status = 0)
set #query = #query + ' + '','' +'
end;
close db_cursor;
deallocate db_cursor;
set #query = 'select rtrim('+#query+') as csvrow from '+#table_name;
if #debug != 0
begin
declare #newline nvarchar(2);
set #newline = char(13) + char(10)
print 'Generated SQL:' + #newline + #query + #newline + #newline;
end
exec (#query);
For my test table, this generates the query:
select
rtrim(rtrim(cast(id as nvarchar(max)))
+ ','
+rtrim(cast(col0 as nvarchar(max)))
+ ','
+rtrim(cast(col1 as nvarchar(max)))
+ ','
+rtrim(cast(col2 as nvarchar(max)))
+ ','
+rtrim(cast(col3 as nvarchar(max)))
+ ','
+rtrim(cast(col4 as nvarchar(max))))
as csvrow
from test
and the result set:
csvrow
-------------------------------------------------------------------------------------------
EEE16C3A-036E-4524-A8B8-7CCD2E575519,alice,bob,charlie,dave,emily
F1EE6C84-D6D9-4621-97E6-AA8716C0643B,abby,bill,charlotte,daniel,evan
Suggestions
Modify the cursor loop to escape commas
Make sure that #table_name refers to a valid table (if object_id(#table_name) is null) in the sproc
Some exception handling would be good
Set permissions on this so that only the account that runs the report can execute it. String concatenation in dynamic SQL can be a big security hole, but I don't see another way to do this.
Some error handling to ensure that the cursor gets closed and deallocated might be nice.
This can be used for any table that is not a #temp table. In that case, you'd have to use sys.objects and sys.columns from tempdb...
select STUFF((select ','+ convert(varchar,l.Subject) from tbl_Student B,tbl_StudentMarks L
where B.Id=L.Id FOR XML PATH('')),1,1,'') Subject FROM tbl_Student A where A.Id=10

Dynamically search columns for given table

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)