My users are trying to find records in my SQL db by providing simple text strings like this:
SCRAP 000000152 TMB-0000000025
These values can be in any order and any may be excluded. For example, they may enter:
SCRAP
TMB-0000000025 SCRAP
000000152 SCRAP
SCRAP 000000152
TMB-0000000025 000000152
All should work and include the same record as the original search, but they may also contain additional records because fewer columns are used in the match.
Here is a sample table to use for the results:
DECLARE #search1 varchar(50) = 'SCRAP 000000152 TMB-0000000025'
DECLARE #search2 varchar(50) = 'SCRAP'
DECLARE #search3 varchar(50) = 'TMB-0000000025 SCRAP'
DECLARE #search4 varchar(50) = '000000152 SCRAP'
DECLARE #search5 varchar(50) = 'SCRAP 000000152'
DECLARE #search6 varchar(50) = 'TMB-0000000025 000000152'
DECLARE #table TABLE (WC varchar(20),WO varchar(20),PN varchar(20))
INSERT INTO #table
SELECT 'SCRAP','000000152','TMB-0000000025' UNION
SELECT 'SCRAP','000012312','121-0000121515' UNION
SELECT 'SM01','000000152','121-0000155' UNION
SELECT 'TH01','000123151','TMB-0000000025'
SELECT * FROM #table
One additional wrinkle, the user does not have to enter 000000152, they can enter 152 and it should find the same results.
I can use patindex, but it requires the users to enter the search terms in a specific order, or for me to have an exponentially larger string to compare as I try to put them in all possible arrangements.
What is the best way to do this in SQL? Or, is this outside the capabilities of SQL? It is quite possible that the table will have well over 10,000 records (for some instances even over 100,000), so the query has to be efficient.
Agree with #MitchWheat (as usual). This database is not designed for queries like that, nor would any kind of "basic query" help. Best way would be to build a list of strings appearing in any column of the database, mapped back to the source column and row, and search that lookup table for your strings. This is pretty much what Lucene and any other full-text search library will do for you. SQL has a native implementation, but if the pros say go with a third party implementation, I'd say it's worth a look-see.
You can try this SP:
USE master
GO
CREATE PROCEDURE 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.'
IF CURSOR_STATUS('variable', 'col_cursor') <> -3
BEGIN
CLOSE col_cursor
DEALLOCATE col_cursor
END
END CATCH
This will have results as follow:
USE AdventureWorks
GO
EXEC sp_FindStringInTable 'Irv%', 'Person', 'Address'
USE AdventureWorks
GO
EXEC sp_FindStringInTable '%land%', 'Person', 'Address'
That's all there is to it. Once this has been created you can use this against any table and any database on your server.(Read More)
Related
I'm using a linked server and finding it very painful to write queries like this:
select * from [10.150.10.109].lhf.[dbo].[TABLE_NAME]
Is it possible to use a synonym for something like this:
CREATE SYNONYM [DataRelay] FOR [10.150.10.109].[lhf].[dbo]
in order to be able to query like this:
select * from DataRelay.TABLE_NAME
Without the capabilities of Intellisense, this is just painful...
No, there is no short hand for linked servers, however, you can alias tables in your queries to make it a bit easier.
select * from [10.150.10.109].lhf.[dbo].[TABLE_NAME] T
WHERE
T.FieldName=1
OR
T.FieldName=2
Now that I had a minute what I was saying in my comment is that you cannot create a synonym for just part of an object path as you desire. But you can dynamically script the drop and creation of synonyms for any object in your remote database pretty easily. here is an example of how to do if for user tables. For other objects you can use the sys.objects instead of sys.table system view.
Technique key words for more learning. Dynamic SQL, cursor, schema views.
DECLARE #ServerAndDB SYSNAME = '[10.150.10.109].[lhf]'
DECLARE #SynonymSchema SYSNAME = '[syn]'
DECLARE #ObjectPath NVARCHAR(1000)
DECLARE #SynonymName NVARCHAR(1000)
DECLARE CursorName CURSOR FOR
SELECT
#ServerAndDB + QUOTENAME(SCHEMA_NAME(t.schema_id)) + '.' + QUOTENAME(t.name) as ObjectPath
,#SynonymSchema + '.' + QUOTENAME(t.name) as SynonymName
FROM
[10.150.10.109].[lhf].sys.tables t
WHERE
t.type = 'U'
OPEN CursorName
FETCH NEXT FROM CursorName
INTO #ObjectPath, #SynonymName
WHILE ##FETCH_STATUS = 0
BEGIN
BEGIN TRY
DECLARE #SQL NVARCHAR(MAX)
IF EXISTS (SELECT * FROM sys.synonyms WHERE object_id = OBJECT_ID(#SynonymName))
BEGIN
SET #SQL = 'DROP SYNONYM ' + #SynonymName
EXECUTE sp_executesql #SQLString
SET #SQL = ''
END
SET #SQL = 'CREATE SYNONYM ' + #SynonymName + ' FOR ' + #ObjectPath
EXECUTE sp_executesql #SQLString
SET #SQL = ''
END TRY
BEGIN CATCH
--Can do error handling here
END CATCH
FETCH NEXT FROM CursorName
INTO #ObjectPath, #SynonymName
END
CLOSE CursorName
DEALLOCATE CursorName
create procedure sp_First
#columnname varchar
AS
begin
select #columnname from Table_1
end
exec sp_First 'sname'
My requirement is to pass column names as input parameters.
I tried like that but it gave wrong output.
So Help me
You can do this in a couple of ways.
One, is to build up the query yourself and execute it.
SET #sql = 'SELECT ' + #columnName + ' FROM yourTable'
sp_executesql #sql
If you opt for that method, be very certain to santise your input. Even if you know your application will only give 'real' column names, what if some-one finds a crack in your security and is able to execute the SP directly? Then they can execute just about anything they like. With dynamic SQL, always, always, validate the parameters.
Alternatively, you can write a CASE statement...
SELECT
CASE #columnName
WHEN 'Col1' THEN Col1
WHEN 'Col2' THEN Col2
ELSE NULL
END as selectedColumn
FROM
yourTable
This is a bit more long winded, but a whole lot more secure.
No. That would just select the parameter value. You would need to use dynamic sql.
In your procedure you would have the following:
DECLARE #sql nvarchar(max) = 'SELECT ' + #columnname + ' FROM Table_1';
exec sp_executesql #sql, N''
Try using dynamic SQL:
create procedure sp_First #columnname varchar
AS
begin
declare #sql nvarchar(4000);
set #sql='select ['+#columnname+'] from Table_1';
exec sp_executesql #sql
end
go
exec sp_First 'sname'
go
This is not possible. Either use dynamic SQL (dangerous) or a gigantic case expression (slow).
Create PROCEDURE USP_S_NameAvilability
(#Value VARCHAR(50)=null,
#TableName VARCHAR(50)=null,
#ColumnName VARCHAR(50)=null)
AS
BEGIN
DECLARE #cmd AS NVARCHAR(max)
SET #Value = ''''+#Value+ ''''
SET #cmd = N'SELECT * FROM ' + #TableName + ' WHERE ' + #ColumnName + ' = ' + #Value
EXEC(#cmd)
END
As i have tried one the answer, it is getting executed successfully but while running its not giving correct output, the above works well
You can pass the column name but you cannot use it in a sql statemnt like
Select #Columnname From Table
One could build a dynamic sql string and execute it like EXEC (#SQL)
For more information see this answer on dynamic sql.
Dynamic SQL Pros and Cons
As mentioned by MatBailie
This is much more safe since it is not a dynamic query and ther are lesser chances of sql injection . I Added one situation where you even want the where clause to be dynamic . XX YY are Columns names
CREATE PROCEDURE [dbo].[DASH_getTP_under_TP]
(
#fromColumnName varchar(10) ,
#toColumnName varchar(10) ,
#ID varchar(10)
)
as
begin
-- this is the column required for where clause
declare #colname varchar(50)
set #colname=case #fromUserType
when 'XX' then 'XX'
when 'YY' then 'YY'
end
select SelectedColumnId from (
select
case #toColumnName
when 'XX' then tablename.XX
when 'YY' then tablename.YY
end as SelectedColumnId,
From tablename
where
(case #fromUserType
when 'XX' then XX
when 'YY' then YY
end)= ISNULL(#ID , #colname)
) as tbl1 group by SelectedColumnId
end
First Run;
CREATE PROCEDURE sp_First #columnname NVARCHAR(128)--128 = SQL Server Maximum Column Name Length
AS
BEGIN
DECLARE #query NVARCHAR(MAX)
SET #query = 'SELECT ' + #columnname + ' FROM Table_1'
EXEC(#query)
END
Second Run;
EXEC sp_First 'COLUMN_Name'
Please Try with this.
I hope it will work for you.
Create Procedure Test
(
#Table VARCHAR(500),
#Column VARCHAR(100),
#Value VARCHAR(300)
)
AS
BEGIN
DECLARE #sql nvarchar(1000)
SET #sql = 'SELECT * FROM ' + #Table + ' WHERE ' + #Column + ' = ' + #Value
--SELECT #sql
exec (#sql)
END
-----execution----
/** Exec Test Products,IsDeposit,1 **/
We suffered some kind of invasion in our SQL Server.
I'm trying to find in every database, in every table, every column the word abortion and cheat.
I can do this with this query, but in a single database.
-- Store results in a local temp table so that. I'm using a
-- local temp table so that I can access it in SP_EXECUTESQL.
create table #tmp
(
db varchar(max),
tbl nvarchar(max),
col nvarchar(max),
val nvarchar(max),
);
declare #db nvarchar(max);
declare #tbl nvarchar(max);
declare #col nvarchar(max);
declare #q nvarchar(max);
declare #search nvarchar(max) = 'abortion';
-- Create a cursor on all columns in the database
declare c cursor for
SELECT
DB_NAME(DB_ID()) as DBName, tbls.TABLE_NAME, cols.COLUMN_NAME
FROM INFORMATION_SCHEMA.TABLES AS tbls
JOIN INFORMATION_SCHEMA.COLUMNS AS cols ON tbls.TABLE_NAME = cols.TABLE_NAME
-- For each table and column pair, see if the search value exists.
open c
fetch next from c into #db, #tbl, #col
while ##FETCH_STATUS = 0
begin
-- Look for the search key in current table column and if found add it to the results.
SET #q = 'INSERT INTO #tmp SELECT ''' +#db+''',''' + #tbl + ''', ''' + #col + ''', ' + #col + ' FROM ' + #tbl + ' WHERE ' + #col + ' LIKE ''%' + #search + '%'''
EXEC SP_EXECUTESQL #q
fetch next from c into #db, #tbl, #col
end
close c
deallocate c
-- Get results
select distinct db,tbl,col from #tmp
-- Remove local temp table.
drop table #tmp
How can I find these strings? The result set should be:
DATABASE | TABLE | COLUMN
I don't need the result ( text field ), and I need to select distinct for tables and columns, because it will be a lot of abortion in the same table/column.
While the use of the undocumented sp_msforeachdb is generally not encouraged, my instinct would be to send your existing code to this procedure like this:
exec sp_MSforeachdb 'USE [?];
-- Store results in a local temp table so that. I'm using a
-- local temp table so that I can access it in SP_EXECUTESQL.
create table #tmp (
db varchar(max) ,
tbl nvarchar(max),
col nvarchar(max),
val nvarchar(max),
);
declare #db nvarchar(max);
declare #tbl nvarchar(max);
declare #col nvarchar(max);
declare #q nvarchar(max);
--------------------------------------------------------------------------------------------
declare #search nvarchar(max) = ''abortion'';
--------------------------------------------------------------------------------------------
-- Create a cursor on all columns in the database
declare c cursor for
SELECT DB_NAME(DB_ID()) as DBName,tbls.TABLE_NAME, cols.COLUMN_NAME FROM INFORMATION_SCHEMA.TABLES AS tbls
JOIN INFORMATION_SCHEMA.COLUMNS AS cols
ON tbls.TABLE_NAME = cols.TABLE_NAME
-- For each table and column pair, see if the search value exists.
open c
fetch next from c into #db, #tbl, #col
while ##FETCH_STATUS = 0
begin
-- Look for the search key in current table column and if found add it to the results.
SET #q = ''INSERT INTO #tmp SELECT '''''' +#db+'''''','''''' + #tbl + '''''', '''''' + #col + '''''', '' + #col + '' FROM '' + #tbl + '' WHERE '' + #col + '' LIKE ''''%'' + #search + ''%''''''
EXEC SP_EXECUTESQL #q
fetch next from c into #db, #tbl, #col
end
close c
deallocate c;'
The only added code here is the first line, for the rest of the code just make sure to replace ' with ''. The ? in USE [?] is a special character meaning the currently active database in the loop sp_MSforeachdb executes.
I have 6 table with different fields. I want to access table name dynamically. is there any idea to do it?
My code is below this is simple procedure which I want to make dynamic to use in c#. how to do it?
Create procedure [dbo].[Insert_Data] (#Id int,#FeesHead nchar(20),#Fees int,#Remarks nchar(20))
as
begin
Insert into FeesHead(ID,FeesHead,Fees,Remarks)values(#Id,#FeesHead,#Fees,#Remarks)
End
Don't go there.
It's a bad idea since you will end up with a long, inefficient stored procedure that will be vulnerable to SQL injection attacks and have performance issues.
Writing an insert stored procedure for each table is the way to go.
You wrote you have six different tables with different columns, so writing a stored procedure to handle inserts for all of them will require you to send all the parameters for all columns as well as a parameter for the table name, and a nested if...else with 6 possible paths, one for each table.
This will end up as a long, messy, poorly written code at best, bad in each parameter: security, performance, code readability and maintainability.
The only way that makes some sense to achieve such a goal is to write individual insert stored procedures for each table, and then write a stored procedure that will take all of the possible parameters and the table name and inside of it decide what insert stored procedure to execute based on the value of the table name parameter. However, you will be better off leaving conditions like these to the SQL client (your c# code in this case) then to SQL Server.
Its very easy to do.........
Just call the sql query using Data Adapter.
select TABLE_NAME from INFORMATION_SCHEMA.TABLES
As you said you need dynamic SQL like this:
Create procedure [dbo].[Insert_Data]
(
#TableName nvarchar(512),
#Values nvarchar(max)
)
BEGIN
DECLARE #SQL nvarchar(max)
SELECT #SQL = 'INSERT INTO ' + #TableName + ' VALUES (' + #Values + ')'
EXEC(#SQL)
END
Note that #Values will be like this '1, ''name'', 10.2' and with the same order of columns.
or
Create procedure [dbo].[Insert_Data]
(
#TableName nvarchar(512),
#Fields nvarchar(max),
#Values nvarchar(max)
)
BEGIN
DECLARE #SQL nvarchar(max)
SELECT #SQL = 'INSERT INTO ' + #TableName + ' (' + #Fields + ') VALUES (' + #Values + ')'
EXEC(#SQL)
END
To more ability to handle column order and remove identity columns.
As Robert Harvey mentioned it is a bad idea, anyway if you want to you can do something like....
CREATE PROCEDURE Insert_Data
#TableName SYSNAME
,#Column1 SYSNAME = NULL
,#Column2 SYSNAME = NULL
,#Column3 SYSNAME = NULL
,#Value1 NVARCHAR(100) = NULL
,#Value2 NVARCHAR(100) = NULL
,#Value3 NVARCHAR(100) = NULL
AS
BEGIN
SET NOCOUNT ON;
DECLARE #Sql NVARCHAR(MAX);
SET #Sql = N' INSERT INTO ' + QUOTENAME(#TableName)
+ N' ( '
+ STUFF(
CASE WHEN #Column1 IS NOT NULL
THEN N',' + QUOTENAME(#Column1) ELSE N'' END
+ CASE WHEN #Column2 IS NOT NULL
THEN N',' + QUOTENAME(#Column2) ELSE N'' END
+ CASE WHEN #Column3 IS NOT NULL
THEN N',' + QUOTENAME(#Column3) ELSE N'' END
,1,1,'')
+ N' ) '
+ N' VALUES ( '
+ STUFF(
CASE WHEN #Value1 IS NOT NULL
THEN N', #Value1' ELSE N'' END
+ CASE WHEN #Value2 IS NOT NULL
THEN N', #Value2' ELSE N'' END
+ CASE WHEN #Value3 IS NOT NULL
THEN N', #Value3' ELSE N'' END
,1,1,'')
+ N' ) '
Exec sp_executesql #Sql
,N'#Value1 NVARCHAR(100),#Value2 NVARCHAR(100),#Value3 NVARCHAR(100)'
,#Value1
,#Value2
,#Value3
END
I'm securing the DB by only allowing interaction with the DB through a series of Sprocs; pretty common fare.
I've dug up and modified a script which loops through and assigns the user EXECUTE permission for all non-system SProcs. It works a treat except that I'd ideally like to add it to the Master DB so that I can easily use it for any subsequent projects. Yes, I could save simple as a .sql file but I'd prefer it this way.
The problem is that I don't know how to dynamically refer to objects in another DB. For example, I can easily query on MyDB.dbo.INFORMATION_SCHEMA.ROUTINES, but if the DB name is dynamic (e.g. #MyDBName), how can I query the objects in this DB?
Edit: Thanks to the posters below, I now have a working solution:
USE [master]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[spGrantExec]
#User sysname,
#DB varchar(50),
#Target varchar(50)
AS
/*---------------------------- SQL 2005 + -------------------------------*/
SET NOCOUNT ON
-- 1 - Variable declarations
DECLARE #SQL varchar(8000)
-- 2 - Create temporary table
Set #SQL =
'USE #DB
DECLARE #MAXOID int
DECLARE #OwnerName varchar(128)
DECLARE #ObjectName varchar(128)
DECLARE #CMD1 varchar(8000)
CREATE TABLE #StoredProcedures
(OID int IDENTITY (1,1),
StoredProcOwner varchar(128) NOT NULL,
StoredProcName varchar(128) NOT NULL)
-- 3 - Populate temporary table
INSERT INTO #StoredProcedures (StoredProcOwner, StoredProcName)
SELECT ROUTINE_SCHEMA, ROUTINE_NAME
FROM INFORMATION_SCHEMA.ROUTINES
WHERE ROUTINE_NAME LIKE ''' + #Target + '%''
AND ROUTINE_TYPE = ''PROCEDURE''
-- 4 - Capture the #MAXOID value
SELECT #MAXOID = MAX(OID) FROM #StoredProcedures
-- 5 - WHILE loop
WHILE #MAXOID > 0
BEGIN
-- 6 - Initialize the variables
SELECT #OwnerName = StoredProcOwner,
#ObjectName = StoredProcName
FROM #StoredProcedures
WHERE OID = #MAXOID
-- 7 - Build the string
SELECT #CMD1 = ''GRANT EXEC ON '' + ''['' + #OwnerName + '']'' + ''.'' + ''['' + #ObjectName + '']'' + '' TO #user''
-- 8 - Execute the string
Print #CMD1
EXEC(#CMD1)
-- 9 - Decrement #MAXOID
SET #MAXOID = #MAXOID - 1
END
-- 10 - Drop the temporary table
DROP TABLE #StoredProcedures'
Set #SQL = REPLACE(REPLACE(REPLACE(#SQL, '#DB', #DB), '#User', #User), '#Target', #Target)
--Select #SQL
--Print #SQL
Exec (#SQL)
SET NOCOUNT OFF
Similiar to #Cade's answer, the way to do this is to use dynamic sql. Before each call to a database table, add '#DbName.' Then replace the #DbName with the actual database name (the database name can't be passed as a variable in SQL, so you have to do the replace).
Also Cursors are normally considered evil for performance reasons, however using one in this case makes sense. For one, it would greatly simplify the procedure, plus since you're only going to run this once during application updates, you probably won't notice a performance hit, even if it added an extra second or two (which I doubt it would add anywhere near that much).
ALTER PROCEDURE [dbo].[spGrantExec]
#User SysName,
#DbName VarChar(512)
AS
BEGIN
DECLARE #Sql VarChar(1024)
SET #Sql = 'DECLARE #OwnerName varchar(128)
DECLARE #ObjectName varchar(128)
DECLARE #Cmd1 VarChar(128)
DECLARE ProcCursor CURSOR FOR
SELECT ROUTINE SCHEMA, ROUTINE NAME
FROM #DbName.INFORMATION SCHEMA.ROUTINES
WHERE ROUTINENAME NOT LIKE ''dt %'' AND ROUTINE TYPE = ''PROCEDURE''
OPEN ProcCursor
FETCH NEXT FROM ProcCursor INTO #OwnerName, #ObjectName
WHILE ##FETCH STATUS = 0
BEGIN
SET #CMD1 = ''GRANT EXEC ON '' + ''['' + #OwnerName + '']'' + ''.'' + ''['' + #ObjectName + '']'' + '' TO '' + ''#user''
EXEC (#CMD1)
FETCH NEXT FROM ProcCursor INTO #OwnerName, #ObjectName
END
CLOSE ProcCursor
DEALLOCATE ProcCursor
'
SET #Sql = Replace(Replace(#Sql, '#DbName', #DbName), '#user', #User)
EXEC (#Sql)
END
You can call this using: EXEC [spGrantExec] 'bob', 'Northwind'
Sorry the spacing is a little off in the sp. Developed using Sql 2005.
I found another technique, which I think is cleaner:
SELECT #sql = 'CREATE VIEW ...'
SELECT #sp_executesql = quotename(#dbname) + '..sp_executesql'
EXEC #sp_executesql #sql
This relies on setting the database context by calling sp_executesql in the other database (just like one could call an SP in any database).
In your case it would be equivalent to:
SELECT #sp_executesql = quotename(#dbname) + '..sp_executesql'
EXEC #sp_executesql #CMD1
You can use the double exec technique.
In your case, instead of just:
EXEC(#CMD1)
You would have:
SET #CMD1 =
'USE OtherDatabase;
EXEC (''' + REPLACE(#CMD1, '''', '''''') + ''')'
EXEC(#CMD1)