Group,
I am trying to create a stored procedure using one variable #Customer. What I want to do is put something in my WHERE clause that says if it is a number search the CustomerID field where the number entered is LIKE CustomerID... If a char is entered search the CustomerName field where the text entered is LIKE CustomerName. Below is an example of what I am trying to do:
CREATE PROCEDURE [dbo].[uspGetCustomer] (#Customer VARCHAR(100))
AS
SET NOCOUNT ON
SELECT *
FROM dbo.Customers
WHERE CASE WHEN ISNUMERIC(#Customer) THEN CustomerID LIKE #Customer + '%'
ELSE CustomerName LIKE #Customer + '%' END
Any suggestions?
I'd do it using an IF statement, since putting that logic in the WHERE makes it kind of hard to read later on:
DECLARE #match = #CustomerID + '%'
IF ISNUMERIC(#CustomerID) = 1
BEGIN
SELECT * FROM CUSTOMERS WHERE CustomerID LIKE #match
END ELSE BEGIN
SELECT * FROM CUSTOMERS WHERE CustomerNAME LIKE #match
END
update:
I'm wondering if the CustomerID field is an INT. If so then I'd change the query like so (and get rid of the #match variable):
... WHERE CustomerID = Cast(#CustomerID as INT) --if numeric
... WHERE CustomerNAME = #CustomerID + '%' --if not numeric
However, if it's some weird VARCHAR field that starts with a number and ends with other data, like '11_blah', then the LIKE plus wildcard works fine
Doing a single SQL statement that tries to solve both conditions will result in worst execution plan. Remeber that SQL has to generate one single plan to satisfy any value of the #variable. In your case when #customerID is numeric the proper plan would be to use an index on CustomerID. But when #customerID is a name the proper access would be an index on CustomerName. Given this dillema the optimizer will likely pick a plan that does a full scan, ie. not optimized in neither case.
The proper thing to do is to determine in your application if is an ID or a name and call two separate stored procedures, uspGetCustomerByID and uspGetCustomerByName, according to the value entered. If you must do this via one 'magic' API entry point (the all-powerful uspGetCustomer), then you already got seveal good answers.
It could just be me, but using a single variable to represent two different fields gives me the bad-practice willies. I would rewrite this stored procedure to take in two different, nullable variables (one int, CustomerID, and one varchar, CustomerName). It would look like this:
CREATE PROCEDURE [dbo].[uspGetCustomer] (
#CustomerID int = null,
#CustomerName VARCHAR(100) = null)
AS
IF #CustomerID IS NOT NULL BEGIN
SELECT * FROM Customers WHERE CustomerID = #CustomerID
END ELSE IF #CustomerName IS NOT NULL BEGIN
SELECT * FROM Customers WHERE CustomerName LIKE #CustomerName
END ELSE
--error handling, return empty set maybe?
END
If this simply isn't an option, then you could still use:
CREATE PROCEDURE [dbo].[uspGetCustomer] (#Customer VARCHAR(100))
AS
DECLARE #NameMatch;
IF ISNUMERIC(#Customer) = 1 BEGIN
SELECT * FROM Customers WHERE CustomerID = CAST (#Customer AS int)
END ELSE BEGIN
SET #NameMatch = '%' + #Customer + '%'
SELECT * FROM Customers WHERE CustomerName LIKE #NameMatch
END
Use:
CREATE PROCEDURE [dbo].[uspGetCustomer] (#Customer VARCHAR(100))
AS
BEGIN
IF ISNUMERIC(#Customer) = 1
BEGIN
SELECT *
FROM dbo.CUSTOMERS
WHERE customerid LIKE #Customer + '%'
END
ELSE
BEGIN
SELECT *
FROM dbo.CUSTOMERS
WHERE customername LIKE #Customer + '%'
END
END
Do something like this:
CREATE PROCEDURE [dbo].[uspGetCustomer] (
#CustomerID INT = NULL
#Customer VARCHAR(100) = NULL
)
AS
SET NOCOUNT ON
IF #CustomerID is not null
BEGIN
SELECT * FROM dbo.Customers
WHERE CustomerID = #CustomerID
END
ELSE
BEGIN
SELECT * FROM dbo.Customers
WHERE CustomerName LIKE #CustomerID + '%'
END
SET NOCOUNT OFF
I'd keep it simple and assume that customer names are never numeric:
SELECT *
FROM dbo.Customers
WHERE CustomerID LIKE #Customer + '%'
OR CustomerName LIKE #Customer + '%'
Alternatively, if you really don't want to match a numeric customer against its name, you can check like:
WHERE (IsNumeric(#Customer) = 1 AND CustomerID LIKE #Customer + '%')
OR (IsNumeric(#Customer) = 0 AND CustomerName LIKE #Customer + '%')
But then, how would you search for a customer with a numeric name? And by the way... a search like this will find customer 121 if you search for 12.
SELECT *
FROM dbo.Customers
WHERE ISNUMERIC(#Customer) = 1
AND CustomerID = CAST(#Customer AS INTEGER)
UNION ALL
SELECT *
FROM dbo.Customers
WHERE NOT ISNUMERIC(#Customer) = 1
AND CustomerName LIKE #Customer + '%'
This will use the approproate indexes on CustomerID and CustomerName
Oh just do it this way....
IF ISNUMERIC(#Customer) THEN
SELECT * FROM .... CustomerID = #Customer
ELSE
SELECT * FROM ... CustomerName LIKE #Customer
But you would want it more maintainable I suppose...
declare #basequery NVARCHAR(4000)
declare #params NVARCHAR(4000)
set #base_query = 'select * from dbo.Customers where '
IF ISNUMERIC(#Customer) THEN
SET #base_query = #base_query + 'customerid = #xCustomer'
SET #params = '#xCustomer int'
END
ELSE
SET #base_query = #base_query + 'customerName LIKE #xCustomer + ''%'' '
SET #params = '#xCustomer nvarchar(1000)'
END
exec sp_execuresql #base_query, #params, #Customer
Of course I would only recommend this kind of dynamic sql for more complex kinds of filtering.
I believe this will do it:
WHERE CustomerID LIKE
CASE WHEN IsNumeric(#Customer) = 1 THEN
CustomerID
ELSE
CustomerName + '%'
END
Keep it simple, have an index on each, determine on the front-end which one it is and set the correct parameter.
CREATE PROCEDURE dbo.getCustomer
(#CustomerID int = null,
#CustomerName VARCHAR(100) = null)
AS
SET NOCOUNT ON
SELECT *
FROM Customers
WHERE CustomerID = #CustomerID
or CustomerName like #CustomerName + '%'
Related
I am trying to sanitize a database I have for testing purposes, and want to update a column (name) to something like Name <rownumber>.
I have given it a go myself, but it cycles through all the rows and ends up naming them all the same at the end Name 323; 323 being the total number of rows.
Some direction would be very helpful.
This is what I have at the moment
Should I not be doing a while for each row in the table?
DECLARE #counter INT = 2
DECLARE #rows int
DECLARE #CustCode varchar(20)
DECLARE #CustName varchar(128)
DECLARE #CustID varchar(10)
SELECT #rows = COUNT(*) FROM Customer
WHILE #counter <= #rows
BEGIN
SELECT #CustID = CustomerID FROM Customer
SET #CustCode = 'CustCode' + #CustID
SET #CustName = 'Customer Description ' + #CustID
SET #CustName = 'Customer Description ' + #CustID
UPDATE Customer
SET CustomerCode = #CustCode,
CustomerDescription = #CustName
SET #counter = #counter + 1
END
Really, it would be much better to use ONE, set-based statement for this. You're not using any specific additional information (like a row number - as mentioned in your post) - you're just concatenating fixed text like Cust Code and Customer Description) with the already existing CustomerId - so a simple UPDATE statement like this would do (and would update everything in one go):
UPDATE dbo.Customer
SET CustomerCode = CONCAT('Cust Code ', CustomerId),
CustomerDescription = CONCAT('Customer Description ', CustomerId);
I'm trying to create a searchstring that is a bit dynamic, and i'm trying to work around a large CASE WHEN scenario, but before i resort to doing a CASE WHEN i'm trying my luck here.
I've tried to execute it in string format "exec('code')" where it works, but then i get another issue with getdate() that i wont go into details with.
DECLARE #ProductLines nvarchar(50) = 'usr_author'
DECLARE #searchProductlines nvarchar(50) = 'hc'
SELECT TOP 20
Productid as Produktid,
usr_Author AS Author,
Header AS Title,
usr_Publisher AS Publisher,
CustomerId AS Customerid, FROM Products
WHERE
(#ProductLines Like '%' + #searchProductlines + '%')
I've scraped away all other code that isn't relevant here. What i want to do is declare #ProductLines as the column 'usr_author' so i in the WHERE clause can use #ProductLines as an dynamic column picker in a drop down menu later.
however, this doesnt work. if i write usr_Author instead of #ProductLines, i get the results i need but then it's a static solution, rather than a dynamic solution. what is best practice in this situation?
You can only replace constant values using parameters. You cannot replace identifiers -- including column names, table names, and so on.
You can do this dynamically as:
DECLARE #col nvarchar(50) = 'usr_author'
DECLARE #search nvarchar(50) = 'hc'
DECLARE #sql NVARCHAR(MAX);
SET #sql = '
SELECT TOP 20
Productid as Produktid,
usr_Author AS Author,
Header AS Title,
usr_Publisher AS Publisher,
CustomerId AS Customerid
FROM Products
WHERE #col Like ''%'' + #search + ''%''
';
SET #sql = REPLACE(#sql, '#col', #col);
EXEC sp_executesql #sql,
N'#search nvarchar(50)',
#search=#search;
By declaring this string, you're now comparing it as a string, not a column. Try a more dynamic SQL approach:
DECLARE #ProductLines nvarchar(50) = 'usr_author'
DECLARE #searchProductlines nvarchar(50) = 'hc'
DECLARE #sql1 nvarchar(500);
select #sql1 = 'SELECT TOP 20 Productid as Produktid, usr_Author AS Author, Header AS Title, usr_Publisher AS Publisher, CustomerId AS Customerid into #temptab1 FROM Products WHERE (' + #ProductLines + 'Like ''%' + #searchProductlines + '%'')'
exec( #sql1 )
select *
from #temptab1
Disclaimer: Not injection proof in the slightest, just a concept
create procedure dbo.uspSearch(
#searchProductId int = 0,
#searchAuthor nvarchar(50) = '',
#searchHeader nvarchar(50) = '',
#searchPublisher nvarchar(50) = '',
#searchCustomerId int = 0
) as
begin
set nocount on;
select top 20
Productid as Produktid,
usr_Author AS Author,
Header AS Title,
usr_Publisher AS Publisher,
CustomerId AS Customerid
from Products
where Productid = case when #searchProductId > 0 then #searchProductId else Productid end
and usr_Author like case when #searchAuthor <> '' then '%' + #searchAuthor + '%' else usr_Author end
and Header like case when #searchHeader <> '' then '%' + #searchHeader + '%' else Header end
and usr_Publisher like case when #searchPublisher <> '' then '%' + #searchPublisher + '%' else usr_Publisher end
and CustomerId = case when #searchCustomerId > 0 then #searchCustomerId else CustomerId end;
end;
go;
declare #searchProductId int = '1'
declare #searchAuthor nvarchar(50) = 'hc'
declare #searchHeader nvarchar(50) = 'test'
declare #searchPublisher nvarchar(50) = 'test'
declare #searchCustomerId int = '1';
exec dbo.uspSearch #searchProductId, #searchAuthor, #searchHeader, #searchPublisher, #searchCustomerId;
Besides the fact, that the whole approach has a certain smell, the best answer will be: Use dynamically created SQL (if you really have / want to stick to this). There are answers already...
Just for fun, there is a fully generic approach using XML like here:
I want to use a statement like this:
SELECT *
FROM sys.objects
WHERE [name]='sysrowsets';
...With generically defined parameters
DECLARE #ColumnName VARCHAR(100)='name';
DECLARE #SearchValue VARCHAR(100)='sysrowsets';
SELECT
(
SELECT *
FROM sys.objects
FOR XML PATH('o'),TYPE
).query('/*[*[local-name()=sql:variable("#ColumnName") and text()[1]=sql:variable("#SearchValue")]]')
The result is the same row as above, but as XML (which can be transformed into a tabular set again).
<o>
<name>sysrowsets</name>
<object_id>5</object_id>
<schema_id>4</schema_id>
<parent_object_id>0</parent_object_id>
<type>S </type>
<type_desc>SYSTEM_TABLE</type_desc>
<create_date>2012-09-02T23:08:12.370</create_date>
<modify_date>2012-09-02T23:08:15.340</modify_date>
<is_ms_shipped>1</is_ms_shipped>
<is_published>0</is_published>
<is_schema_published>0</is_schema_published>
</o>
As mentioned above, this is not the recommended approach, it will be very slow. I just felt the need to say something against the impossible statements :-)
UPDATE: Tabular result and still generic...
You can use something like this, just to articulate a predicate against a unique value (in this case object_id)
SELECT o.*
FROM sys.objects o
WHERE o.object_id=
(
SELECT *
FROM sys.objects
FOR XML PATH('o'),TYPE
).query('/*[*[local-name()=sql:variable("#ColumnName")
and text()[1]=sql:variable("#SearchValue")]]')
.value('(/o/object_id/text())[1]','bigint');
I have a parameterized query shown below that is supposed to return info about items that have a name like the parameter. However, it only works for exact matches
BEGIN
SET NOCOUNT ON
DECLARE #name varchar(50)
SET #name = 'bananas'
SELECT category, sum(netweight) AS NetWeight from PieChart group by
category, name HAVING name LIKE #name
END
Since 'bananas' is in the database, it returns that info. If it put 'banan', it returns nothing.
Thanks for your help!
You can also put the wildcards in the WHERE clause:
BEGIN
SET NOCOUNT ON;
DECLARE #name VARCHAR(50) = 'bananas';
SELECT category ,
SUM(netweight) AS NetWeight
FROM PieChart
WHERE [name] LIKE '%' + #name + '%'
GROUP BY category ,
[name]
END
You need wildcards. In addition, you should use where, not having. So:
BEGIN
SET NOCOUNT ON
DECLARE #name varchar(50);
SET #name = 'banan%';
SELECT category, sum(netweight) AS NetWeight
FROM PieChart
WHERE name LIKE #name
GROUP BY category, name;
END;
I have a parameter based on which I need to include or exclude one condition in a where clause.
If the CustomerType parameter is blank, then I need to include a condition CustomerType is null, otherwise I need to exclude that CustomerType is null condition.
The SQL query is:
IF(#CustomerType = Blank)
BEGIN
Select * From Customer
Where
(CustomerType IN(#Type) Or CustomerType is null)
END
Else
Begin
Select * From Customer
Where
CustomerType IN(#Type)
End
Is there a better way to include or exclude this condition without using an IF ELSE condition like above?
Try
Select * From Customer
Where
(CustomerType is null AND #customertype ='')
OR
CustomerType IN (#Type)
You need change your "IN" part , however.
Edit:
Select * From Customer
Where (#customertype ='') OR CustomerType IN (...)
To handle all those input in one stored procedure:
CREATE PROCEDURE FIND_CUSTOMER_BY_TYPE #TYPE VARCHAR(50)
AS
BEGIN
-- a lazy check on your parameters
IF #TYPE LIKE '%[^0-9, ]%' BEGIN
RAISERROR('Invalid parameter',16,1);
return;
End;
SET NOCOUNT ON;
DECLARE #SQL VARCHAR(500);
SET #SQL='SELECT * FROM CUSTOMERS';
IF #TYPE>''
SET #SQL = #SQL + ' WHERE CUSTOMER_TYPE IN ('+#TYPE+')';
EXEC(#SQL);
SET NOCOUNT OFF;
END;
Your program can all this procedure with the parameter.
My query returns 26 table names.
select name from sys.tables where name like '%JPro_VP_Service%'
Now I'm trying to write a query to check in every table return from the above query.
--consider this is my first table
select * from JPro_VP_Service
where row_id like '%1-101%' or row_id like '%1-102%'
-- likewise I want to search in 26 tables return from above query
I think I need to write for or cursor to accomplish this.
Can anyone help me how to achieve this?
The easiest way to do this is
Try this:
SELECT 'select * from ' + name
+ ' where row_id like ''%1-101%'' or row_id like ''%1-102%'''
FROM sys.tables
WHERE name LIKE '%JPro_VP_Service%'
you will get all tables together with the same conditions. You could execute them together.
Yes, you would have to use a cursor for this, and probably also dynamic sql
Also see
Generate dynamic SQL statements in SQL Server
Dynamic SQL PROs & CONs
DECLARE #mn INT
DECLARE #mx INT
DECLARE #tblname VARCHAR(100);
WITH cte
AS (SELECT Row_number()
OVER (
ORDER BY (SELECT 0)) AS rn,
name
FROM sys.tables
WHERE name LIKE '%JPro_VP_Service%')
SELECT #mn = Min(rn),
#mx = Max(rn)
FROM cte
WHILE( #mn >= #mx )
BEGIN
SELECT #tblname = name
FROM cte
WHERE rn = #mn
SELECT *
FROM #tblname
WHERE row_id LIKE '%1-101%'
OR row_id LIKE '%1-102%'
--Do something else
SET #mn=#mn + 1
END
This route may work, though you might want the results saved to a table:
DECLARE #tables TABLE(
ID INT IDENTITY(1,1),
Name VARCHAR(100)
)
INSERT INTO #tables (Name)
SELECT name
FROM sys.tables
WHERE name like '%JPro_VP_Service%'
DECLARE #b INT = 1, #m INT, #table VARCHAR(100), #cmd NVARCHAR(MAX)
SELECT #m = MAX(ID) FROM #tables
WHILE #b <= #m
BEGIN
SELECT #table = Name FROM #tables WHERE ID = #b
SET #cmd = 'select * from ' + #table + '
where row_id like ''%1-101%'' or row_id like ''%1-102%''
'
EXECUTE sp_executesql #cmd
SET #b = #b + 1
SET #cmd = ''
END