Need help in stored procedure for select query - sql-server

I am trying to fetch data on the basis of first name and last name. I have two DB replica of one another except for few extra data in one DB. Below is the stored procedure for the same. The problem is that I want data fetched first part of the(ie is if I search for ar it should show names starting with ar like arron but not in the middle like Sharon). The query is working fine with one db & not with the other one.
ALTER PROCEDURE [dbo].[SEARCH]
(
#firstName nvarchar(50),
#lastName nvarchar(50),
#state nvarchar(50),
#county nvarchar(50),
#searchspan nvarchar(5)
)
AS
BEGIN
declare #queryString nvarchar(max)
declare #countyId nvarchar(50)
SET QUOTED_IDENTIFIER OFF
Create table #temp1(
county nvarchar(50) NULL,
ID nvarchar(50) NULL,
name nvarchar(200) NULL,
state nvarchar(50) NULL,
FirstName nvarchar(50) NULL,
LastName nvarchar(50) NULL,
county nvarchar(50) NULL,
)
set #queryString = 'insert into #temp1 select distinct a.Source as county, a.ID as ID, a.FirstName +'' ''+ a.LastName as name, ''' +
#state +''' as state,a.FirstName,a.LastName,b.county, from Person a, CountySite b where 1=1 and a.Source=b.sourcecounty '
if(#searchspan<>'')
BEGIN
set #queryString = #queryString = 1'
END
EXECUTE sp_executesql #queryString
set #queryString ='select county, ID, name, state,col_FirstName,col_LastName,col_county, from #temp1 where 1=1 '
if(#firstName <> '')
BEGIN
set #queryString = #queryString+ ' and UPPER(col_FirstName) like ''' +#firstName +'%'''
END
if(#lastName <> '')
BEGIN
set #queryString = #queryString + ' and UPPER(col_LastName) like ''' +#lastName +'%'''
drop table #temp1
END

TSQL is not case-sensitive and you don't need to use UPPER
Also change your query to
set #queryString = #queryString + ' and col_LastName like #lastName +'%'
Try this
SET #queryString = #queryString + ' AND col_LastName LIKE '''+#lastName +'%'''

Do you have profiler so that you can see the query that is being executed on the second DB? Just to ensure that it's the same. Just for grins you may want to try dropping and re-executing the SP on the DB's.

Related

Dynamic WHERE condition SQL SERVER

I have the below requirement. When FirstName present I should return the only condtionFirstName when LastName present I should return the only condtionlastName when First and email both are present I should return condtionFirstName + condtionEmail.
Basically whichever value is present I should return that condition, If 1 value 1 condition, if 1 and 2 then condition 1 and 2, if 1 and 3 then condition 1 and 3, if only 3 then condition 3.
Kindly help me to get this logic.
DECLARE #FirstName NVARCHAR(100)
DECLARE #LastName NVARCHAR(100)
DECLARE #Email NVARCHAR(200)
DECLARE #condtionFirstName NVARCHAR(200)
DECLARE #condtionlastName NVARCHAR(200)
DECLARE #condtionEmail NVARCHAR(200)
SET #FirstName = 'JOhn'
SET #LastName = 'David'
SET #Email = 'john.david#abc.com'
SET #condtionFirstName = ' AND FirstName = ' + '''' + #FirstName + ''''
SET #condtionlastName = ' AND LastName = ' + '''' + #LastName + ''''
SET #condtionEmail = ' AND Email = ' + '''' + #Email + ''''
This is what I've long called "the kitchen sink" - you want a single query that can support any combination of search criteria. Some thoughts:
Stop trying to concatenate user input with executable strings - this is dangerous and error-prone. These should always be passed in as explicitly-typed parameters.
You can build a single string containing all the conditions instead of trying to create a new condition string for every possible condition.
You can declare and pass parameters to sp_executesql even if they don't all end up in the dynamic SQL statement. This is just like declaring a local variable and not using it.
e-mail addresses can be 320 characters long. If you only support 200, that could bite you.
Sample:
DECLARE #FirstName nvarchar(100),
#LastName nvarchar(100),
#Email nvarchar(320),
#conditions nvarchar(max) = N'';
SET #FirstName = N'John';
SET #LastName = N'David';
SET #Email = N'john.david#abc.com';
SET #conditions += CASE WHEN #FirstName IS NOT NULL THEN
N' AND FirstName = #FirstName' ELSE N'' END
+ CASE WHEN #LastName IS NOT NULL THEN
N' AND LastName = #LastName' ELSE N'' END
+ CASE WHEN #Email IS NOT NULL THEN
N' AND Email = #Email' ELSE N'' END;
DECLARE #sql nvarchar(max) = N'SELECT ... FROM dbo.table
WHERE 1 = 1' + #conditions;
PRINT #sql; -- try this when populating or not populating each of
-- #FirstName, #LastName, #Email
EXEC sys.sp_executesql #sql,
N'#FirstName nvarchar(100), #LastName nvarchar(100), #Email nvarchar(320)',
#FirstName, #LastName, #Email;
More details:
#BackToBasics: An Updated Kitchen Sink Example
Protecting Yourself from SQL Injection in SQL Server - Part 1
Protecting Yourself from SQL Injection in SQL Server - Part 2

Use null result of exec sp_execute

I have some dynamic SQL that I use to see if a certain property for a certain client is in a table. If so, it will return the value, if not, it will return null. However, I would like it to return a default value if the property is not found.
The table:
CREATE TABLE [test].[customers](
[customer] [nvarchar](256) NULL,
[key_name] [nvarchar](256) NULL,
[key_value] [nvarchar](256) NULL
)
GO
INSERT INTO [test].[customers]
([customer]
,[key_name]
,[key_value])
VALUES
('JohnDoe'
,'periodlength'
,'3')
GO
The Dynamic SQL:
declare #customer as nvarchar(256) = 'JohnDoe'
declare #table as nvarchar(256) = 'test.customers'
declare #sql as nvarchar(4000)
set #sql = 'select [key_value] from ' + #table + ' where [key_name]= ''periodlength'' and customer= ''' + #customer + ''''
exec sp_executesql #sql
So when Qqerying for John Doe you get result 3 which is perfect. However, I would like to reurn 1 for Jane Doe. So I was thinking along the lines
IF exec sp_executesql #sql IS NULL 1 ELSE exec sp_executesql #sql
but that doesn't work.
How to change my dynamic query so that is returns a default value if the property is not found?
Could you try the following query but this usage only return a single value;
declare #customer as nvarchar(256) = 'JohnDoe'
declare #table as nvarchar(256) = 'test.customers'
DECLARE #ValReturn nvarchar(50)
declare #sql as nvarchar(4000)
set #sql = 'select #ValOutPut=[key_value] from ' + #table + ' where [key_name]= ''periodlength'' and customer= ''' + #customer + ''''
exec sp_executesql #sql , N'#ValOutPut nvarchar(25) OUTPUT' ,#ValOutPut = #ValReturn OUTPUT
BEGIN
IF #ValReturn IS NULL
SELECT NULL
ELSE
SELECT #ValReturn
END

How to search with parameter colums in a stored procedure?

I'm trying to write a stored procedure in SQL Server that gets the columns as parameters. The user will select the column name from a combo box and will write the searched value for that column on a textbox.
I've been searching how to do this and so far i have this:
ALTER PROCEDURE [dbo].[SP_Select_TBL_Folio]
#cant int,
#Column1 nvarchar(50),
#Value1 nvarchar(50),
#Column2 nvarchar(50),
#Value2 nvarchar(50),
#Column3 nvarchar(50),
#Value3 nvarchar(50)
AS
BEGIN
declare #query nvarchar (max)
SET NOCOUNT ON;
if #cant = 1
BEGIN
set #query = 'SELECT * FROM TBL_Folio WHERE ' + #Column1 + ' LIKE '+ #Value1 + ' ORDER BY 1 DESC';
exec sp_executesql #query, N' '
END
else
BEGIN
if #cant = 2
BEGIN
set #query = 'SELECT * FROM TBL_Folio WHERE ' + #Column1 + ' LIKE '+ #Value1 + ' AND ' + #Column2 + ' LIKE '+ #Value2 + ' ORDER BY 1 DESC';
exec sp_executesql #query, N' '
END
ELSE
if #cant = 3
BEGIN
set #query = 'SELECT * FROM TBL_Folio WHERE ' + #Column1 + ' LIKE '+ #Value1 + ' AND ' + #Column2 + ' LIKE '+ #Value2 + ' AND ' + #Column3 + ' LIKE '+ #Value3 + ' ORDER BY 1 DESC';
exec sp_executesql #query, N' '
END
END
END
The user can send 1 to 3 values, for that I have the parameter #cant, this code works but I want to know if there is a better way to do this or how can I improve this stored procedure.
I think what you have is fine if you need to do it in an SP rather than client side. I would probabably initialize the query to the 'select * from TBL_Folio" and then append the LIKES after each if. I would also caution against using SELECT * so your client side doesn't blow up if a field gets added to the table.
If you have a need to check a variable number of fields rather than just up to 3, you can do a table-valued parameter and build up your query by looping through. Here is an example:
ALTER PROCEDURE [dbo].[GetFilteredInvoices]
#FilterColumns ColumnValueType READONLY
AS
BEGIN
SET NOCOUNT ON;
declare #columnName varchar(50), #columnValue varchar(MAX), #query nvarchar(MAX), #count int
set #query='SELECT InvoiceNumber, InvoiceDate, Customer from Invoices '
set #count=0
set #columnName=''
while exists(select * from #FilterColumns where ColumnName>#ColumnName)
begin
set #columnName=(select min(ColumnName) from #FilterColumns where ColumnName>#columnName)
if #count=0
set #query=#query+'WHERE '
else
set #query=#query+'AND '
set #query=#query+ (select ColumnName+' Like ''%'+ColumnValue+'%'' ' from #filterColumns where ColumnName=#columnName)
set #count=#count+1
end
exec sp_executesql #query
END
Here is the table valued type I used:
CREATE TYPE [dbo].[ColumnValueType] AS TABLE(
[ColumnName] [varchar](50) NULL,
[ColumnValue] [varchar](max) NULL
)
GO
Now this will take any number of columns and values to apply the filter.
Here is an example call to the procedure:
DECLARE #RC int
DECLARE #FilterColumns [dbo].[ColumnValueType]
insert into #filterColumns
Values('InvoiceNumber','345')
,('Customer','67')
EXECUTE #RC = [dbo].[GetFilteredInvoices]
#FilterColumns
I think you can perhaps improve the way that you handle your input parameters by getting rid of the #cant parameter. You can also improve the way that you build up the conditions, at the moment you are not handling the situations where only #Column2 and #Value2 or only #Column3 and #Value3 is set (perhaps it is not needed in your case, but it is still good practice to handle these types of scenarios)
CREATE PROCEDURE SP_Select_TBL_Folio
#Column1 NVARCHAR(50) = NULL,
#Value1 NVARCHAR(50) = NULL,
#Column2 NVARCHAR(50) = NULL,
#Value2 NVARCHAR(50) = NULL,
#Column3 NVARCHAR(50) = NULL,
#Value3 NVARCHAR(50) = NULL
AS
BEGIN
SET NOCOUNT ON;
DECLARE
#P1 NVARCHAR(500),
#P2 NVARCHAR(500),
#P3 NVARCHAR(500),
#SQL NVARCHAR(MAX)
IF (#Column1 IS NULL OR #Column1 = '') AND (#Value1 IS NULL OR #Value1 = '')
BEGIN
-- This will build up dynamic SQL to always select records even if #Column1
-- and #Value1 is not set. This obvisously all depends on your requirements
-- and if you still need to select records if the parameters are not set, otherwise
-- it can be changed to ' WHERE ThePrimaryKeyColumn = 0'
SET #P1 = ' WHERE ThePrimaryKeyColumn > 0'
END
ELSE
BEGIN
SET #P1 = 'WHERE ' + #Column1 + ' LIKE ' + '''' + #Value1 + ''''
END
IF (#Column2 IS NULL OR #Column2 = '') AND (#Value2 IS NULL OR #Value2 = '')
BEGIN
SET #P2 = ''
END
ELSE
BEGIN
SET #P2 = ' AND ' + #Column2 + ' LIKE ' + '''' + #Value2 + ''''
END
IF (#Column3 IS NULL OR #Column3 = '') AND (#Value3 IS NULL OR #Value3 = '')
BEGIN
SET #P3 = ''
END
ELSE
BEGIN
SET #P3 = ' AND ' + #Column3 + ' LIKE ' + '''' + #Value3 + ''''
END
SET #SQL = 'SELECT * FROM TBL_Folio
[P1]
[P2]
[P3]'
-- Here we set all the conditions
SET #SQL = REPLACE(#SQL, '[P1]', #P1);
SET #SQL = REPLACE(#SQL, '[P2]', #P2);
SET #SQL = REPLACE(#SQL, '[P3]', #P3);
-- This will be replaced by EXEC(#SQL)
PRINT #SQL
END
So now you can for instance execute
EXEC SP_Select_TBL_Folio
which will give you
SELECT * FROM TBL_Folio
WHERE ThePrimaryKeyColumn > 0
or you can execute
EXEC SP_Select_TBL_Folio 'Column1','Value1'
which will give you
SELECT * FROM TBL_Folio
WHERE Column1 LIKE 'Value1'
or you can execute
EXEC SP_Select_TBL_Folio NULL,NULL,'Column2','Value2'
which will give you
SELECT * FROM TBL_Folio
WHERE ThePrimaryKeyColumn > 0
AND Column2 LIKE 'Value2'
I'm not going to list all the permutations, I'm sure you get my point.

Query to select next column if the current column returns no rows

The question is quite extensive, please bear with me. I have a single mapping table with the following structure:
This particular table is used in the process of generating a hierarchy. The order and position of the columns in the table indicate the order of hierarchy (Organization, Category, Continent, Country.. etc.) Each entity in this hierarchy has a related table with associated Id and Name. For example, there is a Country table with CountryId and CountryName. Note that since the MappingTable's values are all nullable there are no foreign key constraints.
I want to generate a procedure that will do the following:
Based on conditions provided, retrieve values of the next entity in the hierarchy. For example, if the OrganizationId and CategoryId are given, the values of ContinentId that satisfy said condition need to be retrieved.
Also, if the value of ContinentId is NULL, then the values of CountryId need to be retrieved. Here, given the condition OrganizationId = 1 and CategoryId = 1 the procedure should return the list of RegionId.
In addition to retrieving the RegionId, the corresponding RegionName should be retrieved from the Region Table.
So far, the procedure looks something like this - just a few things to explain here.
ALTER PROCEDURE [dbo].[GetHierarchy]
(
#MappingTableName VARCHAR(30),
#Position VARCHAR(5),
-- Given in the form of Key-value pairs 'OrganizationId:1,CategoryId:1'
#InputData VARCHAR(MAX),
#Separator CHAR(1),
#KeyValueSeperator CHAR(1)
)
AS
BEGIN
DECLARE #Sql NVARCHAR(MAX)
DECLARE #Result NVARCHAR(MAX)
DECLARE #Sql1 NVARCHAR(MAX);
DECLARE #TableName NVARCHAR(30)
DECLARE #Exists bit
SELECT #TableName = COLUMN_NAME from INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = #MappingTableName AND ORDINAL_POSITION = #position
SET #TableName = SUBSTRING(#TableName,0,LEN(#TableName) - 1)
-- Returns a dynamic query like "SELECT ContinentId from Continent WHERE OrganizationId = 1 and CategoryId = 1".
SELECT #Sql = [dbo].[KeyValuePairs](#TableName, #InputData, #Separator, #KeyValueSeperator)
SET #Sql1 = N'SET #Exists = CASE WHEN EXISTS(' + #Sql + N' AND ' + #TableName + N'Id IS NOT NULL) THEN 1 ELSE 0 END'
PRINT #Sql
EXEC sp_executesql #Sql1,
N'#Exists bit OUTPUT',
#Exists = #Exists OUTPUT
IF(#Exists = 1)
BEGIN
SET #Sql1 = 'SELECT ' + #TableName + 'Id, ' + #TableName + 'Name FROM '+ #TableName+' WHERE ' + #TableName +'Id IN (' + #Sql + ')';
PRINT #Sql1
EXECUTE sp_executesql #SQL1
END
ELSE
BEGIN
--PRINT 'NOT EXISTS'
DECLARE #nextPosition INT
SELECT #nextPosition = CAST(#position AS INT)
SET #nextPosition = #nextPosition + 1
SET #Position = CONVERT(VARCHAR(5), CAST(#position AS INT))
EXEC [dbo].[GetHierarchy] #MappingTableName, #Position, #InputData, #Separator, #KeyValueSeperator
END
END
The logic of this procedure is such that, I get the name of the column at a particular position (based on the conditions here, it is Continent) and generate the dynamic query to retrieve the next column's values based on the condition of the input condition (I am using a separate function to do this for me).
Once retrieved, I run the query to check if it returns any rows. If the query returns rows, then I retrieve the corresponding ContinentName from the Continent table. If no rows are returns, I recursively call the procedure again with the next position as the input.
On the business side of things, it seems like a two step process. But, as a procedure it is quite complex, extensive and - not to mention, recursive. Is there an easier way to do this? I am not familiar with CTEs - can the same logic be implemented using CTEs?
This is quite similar to what is asked here: Working with a dynamic hierarchy SQL Server
Might be the little lengthy approach. Try this
DECLARE #T TABLE
(
SeqNo INT IDENTITY(1,1),
CatId INT,
Country INT,
StateId INT,
DistId INT
)
DECLARE #State TABLE
(
StateId INT,
StateNm VARCHAR(20)
)
DECLARE #Country TABLE
(
CountryId INT,
CountryNm VARCHAR(20)
)
INSERT INTO #State
VALUES(3,'FL')
INSERT INTO #Country
VALUES(2,'USA')
INSERT INTO #T(CatId)
VALUES(1)
INSERT INTO #T(CatId,Country)
VALUES(1,2)
INSERT INTO #T(CatId,StateId)
VALUES(1,3)
;WITH CTE
AS
(
SELECT
*,
IdVal = COALESCE(Country,StateId,DistId),
IdCol = COALESCE('Country '+CAST(Country AS VARCHAR(50)),'StateId '+CAST(StateId AS VARCHAR(50)),'DistId '+CAST(DistId AS VARCHAR(50)))
FROM #T
WHERE CatId = 1
),C2
AS
(
SELECT
SeqNo,
CatId,
Country,
StateId,
DistId,
IdVal,
IdCol = LTRIM(RTRIM(SUBSTRING(IdCol,1,CHARINDEX(' ',IdCol))))
FROM CTE
)
SELECT
C2.SeqNo,
C2.CatId,
S.StateNm,
C.CountryNm
FROM C2
LEFT JOIN #State S
ON C2.IdCol ='StateId'
AND C2.IdVal = S.StateId
LEFT JOIN #Country C
ON C2.IdCol ='Country '
AND C2.IdVal = c.CountryId

Creating view from multiple databases linked by columns with db name

Hi I need to create a view or stored procedure that combines data and returns a result set from 3 different databases on the same server using a column that holds a schema (db) name.
For Example on the first DB I have this table:
CREATE TABLE [dbo].[CloudUsers](
ID int IDENTITY(1,1) NOT NULL,
Username nvarchar(50) NULL,
MainDB nvarchar(100) NULL
) ON [PRIMARY]
Each CloudUser has a separate DB so next now I need to fetch the data from the User database using the MainDB name. The data I need is always 1 row cause I'm using aggregate functions / query.
So in the User MainDB let's say I have this table.
CREATE TABLE [dbo].[CLIENT](
ID int NOT NULL,
Name nvarchar(50) NULL,
ProjectDBName [nvarchar](100) NULL
CreationDate datetime NULL
) ON [PRIMARY]
And I query like:
select min(CreationDate) from MainDB.Client
The same Idea for the Client I need to fetch even more data from a 3rd database that points to the Client ProjectDBName. Again it's aggregate data:
select Count(id) as TotalTransactions from ProjectDBName.Journal
My final result should have records from all databases. It's readonly data that I need for statistics.
Final result set example:
CloudUsers.Username, MainDB->CreationDate, ProjectDBName->TotalTransaction
How can I achieve that ?
This is not easy - and without a schema and sample data, I can't give you a precise answer.
You need to iterate through each client, and use dynamic SQL to execute a the query against the mainDB and projectDB join. You can either do that in one gigantic "union" query, or by creating a temporary table and inserting the data into that temporary table, and then selecting from the temp table at the end of the query.
For you who are curious of how to solve this issue I have found my own solution using some cursors + dynamic and a simple table variable, enjoy.
ALTER PROCEDURE CloudAnalysis as
DECLARE #objcursor cursor
DECLARE #innercursor cursor
DECLARE #userid int
,#maindb nvarchar(100)
,#clientid int
,#name nvarchar(50)
,#projdb nvarchar(100)
,#stat nvarchar(50)
,#sql nvarchar(max)
,#vsql nvarchar(max)
,#rowcount int
DECLARE #result table(userid int,clientid int,maindb nvarchar(100),name nvarchar(50),projdb nvarchar(100),stat nvarchar(50))
SET #objcursor = CURSOR FORWARD_ONLY STATIC FOR SELECT c.id,c.maindb,u.client_id FROM dbo.ClientUsers c join dbo.UserClients u on c.id = u.user_id open #objcursor
FETCH NEXT FROM #objcursor INTO #userid,#maindb,#clientid
WHILE (##FETCH_STATUS=0)
BEGIN
IF (EXISTS (SELECT name
FROM master.dbo.sysdatabases
WHERE ('[' + name + ']' = #maindb
OR name = #maindb)))
BEGIN
set #sql = N'SELECT #name = c.name,#projdb=c.ProjectDBName FROM ' + #maindb + '.dbo.CLIENT c WHERE c.id = ' + cast(#clientid as nvarchar)
EXECUTE sp_executesql #sql, N'#name NVARCHAR(50) OUTPUT,#projdb NVARCHAR(100) OUTPUT',
#name = #name OUTPUT
,#projdb = #projdb OUTPUT
SELECT #rowcount = ##ROWCOUNT
IF #rowcount > 0
BEGIN
--print ' client: ' + cast(#clientid as nvarchar)+
--':' + #name + ' projdb: ' + #projdb
IF (EXISTS (SELECT name
FROM master.dbo.sysdatabases
WHERE ('[' + name + ']' = #projdb
OR name = #projdb)))
BEGIN
SET #sql = N'SELECT #stat = j.stat FROM ' + #projdb + '.dbo.JournalTransaction j'
EXECUTE sp_executesql #sql
,N'#stat NVARCHAR(50) OUTPUT'
,#stat = #stat OUTPUT
END
INSERT INTO #result (userid,clientid,maindb,name,projdb,stat)
VALUES (#userid,#clientid,#maindb,#name,#projdb,#stat)
END
END
FETCH NEXT FROM #objcursor INTO #userid,#maindb,#clientid
END
CLOSE #objcursor
DEALLOCATE #objcursor
SELECT * FROM #result

Resources