Select Values Based on ItemID OR String Value - sql-server

I am wanting to build a query that looks at an existing Models table.
Table Structure:
ModelID | ManufacturerID | CategoryID | ModelName
What I want to do is pass two things to the query, ModelID and ModelName, so that it returns the specific model and also similar models.
ModelName could be made up of several words e.g iPhone 5s 16GB, so what I would like my query to do is:
SELECT
M.*
FROM
Models AS M
WHERE
(M.ModelID = 1840 OR M.ModelName LIKE '%iPhone%'
OR M.ModelName LIKE '%5s%' OR M.ModelName LIKE '%16GB%')
Is there a way that I can pass the ModelName to the query as a string and then have the query split the string to generate the OR statements?

Do a web search for T-SQL split function. There are loads out there. They take a string (comma-delimited or space delimited or whatever) and return a table of values. Then just do a JOIN against that result set.
SELECT DISTINCT M.*
FROM Models AS M
JOIN dbo.fn_split(#model_name, ' ') AS model_names
ON M.ModelID = #model_id OR m.ModelName LIKE '%' + model_names.value + '%';

OK, so I managed to get this working, following the advice given by Kevin Suchlicki re. fn_Split.
I have made this function even more complex than i intended to, but in order to help others out in a similar situation, here is my final solution:
DECLARE #CategoryID int = 1
DECLARE #ManufacturerID int = 3
DECLARE #ModelName varchar(100) = 'iPhone 5s 16GB'
DECLARE #ModelID int = 1840
DECLARE #Carrier varchar(10) = NULL
DECLARE #Colour varchar(10) = NULL
SELECT
I.*
FROM
(
SELECT
DISTINCT M.*
FROM
Models AS M
JOIN
dbo.fn_Split(#ModelName,' ') AS N
ON M.ModelID = #ModelID OR lower(M.ModelName) LIKE '%'+ Lower(N.value) + '%'
WHERE
M.CategoryID = #CategoryID AND M.ManufacturerID = #ManufacturerID
) AS A
LEFT OUTER JOIN
Items AS I ON A.ModelID = I.ModelID
WHERE
I.Barred <> 1
AND I.Locked <> 1
AND I.Ber <> 1
AND I.Condition = 'Working'
AND (LOWER(I.Colour) = LOWER(ISNULL(#Colour, I.Colour)) OR I.Colour IS NULL)
AND (LOWER(I.Carrier) = LOWER(ISNULL(#Carrier, I.Carrier)) OR I.Carrier IS NULL)
I will now create this as a stored procedure to complete the job.
For reference, HERE is a link to the fn_Split function.

Related

dynamic query with dynamic subquery

I am trying (and failing) to create a dynamic query with a subquery.
I have a table of questions where the possible answers to each question need to be created dynamically from one or several other tables. I have a varchar field in the questions table that I want to use dynamically to query the possible answers. Example:
Questions Table:
-----------------------------------------------------------------------------
id | question | answer_query
-----------------------------------------------------------------------------
1 | Can this be done? | SELECT field1 + ' ' + field2 answers FROM table1 a JOIN table2 b ON b.field1 = a.field2 WHERE b.id = '#id'
Then I want a stored proc that creates a dynamic query like this:
DECLARE #id int
SET #id.......
DECLARE #sql_query varchar(3000);
SET #sql_query =
'SELECT q.id, q.question, (REPLACE(q.answer_query, ''#id'', #id))
FROM Questions q
JOIN Other Table ON ....
WHERE .....';
EXECUTE(#sql_query);
Apologies for the poor formatting!
Is what I am trying to do possible?
Try this:
DECLARE #id int
SET #id.......
DECLARE #sql_query varchar(3000);
SET #sql_query =
'SELECT q.id, q.question, '
+ (REPLACE(q.answer_query, '#id', #id))
+ ' FROM Questions q
JOIN Other Table ON ....
WHERE .....';
EXECUTE(#sql_query);
You want
SET #sql_query = (SELECT REPLACE(q.answer_query, '#id', #id)
FROM Questions q
JOIN Other Table ON ....
WHERE .....)
or
SELECT #sql_query = REPLACE(q.answer_query, '#id', #id)
FROM Questions q
JOIN Other Table ON ....
WHERE .....)
However this seems like a really bad idea for many reasons. Is there any other way to solve this problem.
For example -- instead of having the select statement in the table, reverence a view name. This solves many problems, the compiler can optimize you are not open to sql injection -- so much good.
I am not able to understand the query properly. Assumes that you want to replace the string '#id' from the answer_query with the values in the variable #id.
If so, please try the below query:
DECLARE #id int
SET #id=.....
DECLARE #sql_query varchar(3000);
SET #sql_query =
'SELECT q.id, q.question, (REPLACE(q.answer_query, ''#id'', '+CONVERT(VARCHAR(10),#id)+'))
FROM Questions q
JOIN Other Table ON ....
WHERE .....';
EXECUTE(#sql_query);
Thanks all for your comments. I have come up with the following solution, which is not as generic as I would like.
Table structure
Stored Proc:
CREATE PROCEDURE [dbo].[spQuestionsByTypeAndOther]
#type_id uniqueidentifier,
#other_param_id uniqueidentifier
AS
BEGIN
SET NOCOUNT ON;
SELECT q.id, q.question, q.required, dbo.fnGetAnswers(q.id, #other_param_id)
FROM Questions q
JOIN QuestionsToTypes t on t.question_id = q.id
WHERE t.type_id = #type_id
END
And a function (fnGetAnswers) which uses a case statement to return the answers based on the question id and one other parameter. Each case performs a different query against other tables in the database and flattens/concatenates the results into a csv string.

How to merge several records in one using COALESCE in MS SQL 2008

I'm guering three tables from the DataBase with the idea to extract information for a certain Client so I get single values from all columns except one.
My tables are :
Client :: (ClientId | ClientName)
Notifications :: (NotificationId | NotificiationText)
ClientsNotifications :: (ClientId | NotificationId)
A single client may have multiple notifications related to him, but I want to get them in a single row so after little research I decied that I should use COALESCE.
I made this query :
SELECT c.ClientName, (COALESCE(n.NotificiationText,'') + n.NotificiationText + ';')
FROM [MyDB].[dbo].[Client] AS c
LEFT JOIN [MyDB].[dbo].[ClientsNotifications] AS cn
ON c.ClientId = cn.ClientId
LEFT JOIN [MyDB].[dbo].[Notifications] AS n
ON c.ClientId = cn.ClientId
AND cn.NotificationId = n.NotificationId
WHERE c.ClientId = 1
For this particular user I have two notifications, the result I get is - two rows, on the first row I have the first notification concatenated for itself (I have two times the same string) on the second row I have the second notification concateneated for itself again.
So There are three things that I want but don't know how to do -
Right now for column name I get (No column name) so I want to give it one
I want the two notifications (or as many as they are) concatenated in a single row
I want to determine some delimeter so when I fetch the records I can perform split. In my example I use this - ';') which I think should act as delimeter but the concatenated strings that I have are not separeted by ; or anything.
You can give your column name an alias in the same way you do for a table, e.g.
SELECT <expression> AS ColumnAlias
However, for reasons detailed here I prefer using:
SELECT ColumnAlias = <expression>
Then to get multiple rows into columns you can use SQL Servers XML extensions to achieve this:
SELECT c.ClientName,
Notifications = STUFF(( SELECT ';' + n.NotificationText
FROM [MyDB].[dbo].[ClientsNotifications] AS cn
INNER JOIN [MyDB].[dbo].[Notifications] AS n
ON n.NotificationId = cn.NotificationId
WHERE c.ClientId = cn.ClientId
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 1, '')
FROM [MyDB].[dbo].[Client] AS c
WHERE c.ClientId = 1;
Example on SQL Fiddle
An explanation of how this method works can be found in this answer so I shalln't repeat it here.
There's a trick to doing what you want to do. As it's written right now, you're just grabbing stuff off the same row. Also, multiple conditions on the second left join are unnecessary.
DECLARE #clientName VARCHAR(MAX) = '';
DECLARE #text VARCHAR(MAX) = '';
SELECT #clientName = c.ClientName
, #text = (CASE
WHEN n.NotificationText IS NOT NULL THEN #text + ';' + n.NotificationText
ELSE #text
END)
FROM [MyDB].[dbo].[Client] AS c
LEFT JOIN [MyDB].[dbo].[ClientsNotifications] AS cn
ON c.ClientId = cn.ClientId
LEFT JOIN [MyDB].[dbo].[Notifications] AS n
ON cn.NotificationId = n.NotificationId
WHERE c.ClientId = 1
SELECT #clientName AS ClientName, #text AS Notifications

Output sql query to a variable then create a new query

I'm trying to output the query results from one table and using that same variable as a 'like' in another table.
The first part of the query is supposed to get me everything *. The variable (or array) will be used in the second part of the query as part of 'like'.
This is what I have:
DECLARE #UpnPref nvarchar(100), #WC nvarchar(10)
Set #UpnPref = 0
Set #WC = '*'
Select #upnpref = UpnPrefix from dbo.ADUsers where UPNPrefix = #WC
print #upnpref <-- returns a 0
Select * from dbo.UserMailbox where LinkedAccount like '%' + #UpnPref +'%'
If what you're trying to do is select everything from dbo.UserMailbox that has LinkedAccount matching UpnPrefix of every record in dbo.ADUsers, you can do it so:
SELECT U.*
FROM dbo.UserMailbox U
INNER JOIN dbo.ADUsers A ON U.LinkedAccount = A.UpnPrefix

Query so much slower than stored procedure?

I have created a query which performs with aprox 2 seconds with top 100. If i create a stored procedure of this exact query it takes 12-13 seconds to run.
Why would that be?
Elements table count = 2309015 (with userid specified = 326969)
Matches table count = 1290 (with userid specified = 498)
sites table count = 71 (with userid specified = 9)
code
with search (elementid, siteid, title, description, site, link, addeddate)
as
(
select top(#top)
elementid,
elements.siteid, title, elements.description,
site =
case sites.description
when '' then sites.name
when null then sites.name
else sites.name + ' (' + sites.description + ')'
end,
elements.link,
elements.addeddate
from elements
left join sites on elements.siteid = sites.siteid
where title like #search and sites.userid = #userid
order by addeddate desc
)
select search.*, isnull(matches.elementid,0) as ismatch
from search
left join matches on matches.elementid = search.elementid
When you create SP it is compiled and stored and when the SP has parameters, by which you filter your result, the optimizer don't know which value you will pass on execution, then he treat as 33% selection and by this creates plan. When you execute query, the values are provided and optimizer create the execution plan depended on this values. I sure, the the plans are different.
Without code, I can only guess. When writing a sample query, you first have a constant where clause and second a cache. The stored procedure has no chance of either caching or optimizing the query plan based on a constant in the where clause.
I can suggest two ways to try
First one, write your sp like this:
create procedure sp_search
(
#top int,
#search nvarchar(max),
#userid int
)
as
begin
declare #p_top int, #p_search nvarchar(max), #p_userid int
select #p_top = #top, #p_search = #search, #p_userid = #userid
with search (elementid, siteid, title, description, site, link, addeddate)
as
(
select top(#p_top)
elementid,
elements.siteid, title, elements.description,
site =
case sites.description
when '' then sites.name
when null then sites.name
else sites.name + ' (' + sites.description + ')'
end,
elements.link,
elements.addeddate
from elements
left join sites on elements.siteid = sites.siteid
where title like #p_search and sites.userid = #p_userid
order by addeddate desc
)
select search.*, isnull(matches.elementid,0) as ismatch
from search
left join matches on matches.elementid = search.elementid
end
Second one, use inline table function
create function sf_search
(
#top int,
#search nvarchar(max),
#userid int
)
returns table
as
return
(
with search (elementid, siteid, title, description, site, link, addeddate)
as
(
select top(#top)
elementid,
elements.siteid, title, elements.description,
site =
case sites.description
when '' then sites.name
when null then sites.name
else sites.name + ' (' + sites.description + ')'
end,
elements.link,
elements.addeddate
from elements
left join sites on elements.siteid = sites.siteid
where title like #search and sites.userid = #userid
order by addeddate desc
)
select search.*, isnull(matches.elementid,0) as ismatch
from search
left join matches on matches.elementid = search.elementid
)
There is a similar question here
The problem was the stored proc declaration SET ANSI_NULLS OFF

SQL SP with multiple values for an input

I've inherited a db etc from another developer and need some help.
I have the following stored procedure:
CREATE PROCEDURE [dbo].[TESTgetSearchResults]
(
#ids varchar(100),
#Date DateTime = Null,
#Date2 DATETIME = Null,
#Sort VARCHAR(5), /* ASC or DESC */
#SortBy VARCHAR(10), /* Sorting criteria */
#Location VARCHAR(40)
)
AS
SELECT #Date = GetDate()
SELECT #Date2 = DATEADD(day,-14,GETDATE())
BEGIN
SELECT Aircraft.Id AS AircraftID, AircraftManufacturers.Name, AircraftModels.ModelName,
Aircraft.ModelSuffix, Aircraft.ImageFileName, Aircraft.Year, Aircraft.SerialNo,
Locations.DescriptionForSite, Aircraft.Description, Aircraft.Description2,
Aircraft.InfoWebAddress, Aircraft.ImageDescription, Advertisers.Id AS AdvertisersID,
Advertisers.Name AS AdvertisersName, Aircraft.AircraftDataId, Aircraft.ForSale, Aircraft.ForLease,
Aircraft.TTAF, Aircraft.ReSend, Aircraft.ReSendReason, Aircraft.Registration, Aircraft.AdType,
Aircraft.HasAlternateImage, Aircraft.AlternateImageDescription,
Aircraft.Price, AircraftModels.AircraftType, Advertisers.CurrentEMagLink, Aircraft.CurrentEMagLink,
Aircraft.Email, Aircraft.IsSold, Aircraft.SoldDate, Aircraft.DateAdded, Aircraft.ExtendedDetails,
Aircraft.LastUpdateDate, Aircraft.ImageCount, Aircraft.ContactTelephone, AircraftModels.id, Advertisers.IsPremiumAdvertiser,
Aircraft.Location, Advertisers.ContactTelephone As AdvertisersTelephone, Aircraft.EndDate, Aircraft.VideoLink,
Aircraft.Contact, Advertisers.WASSalesEmail, Advertisers.WASSalesEmail2, Aircraft.PriceNumeric,
Aircraft.PriceQualifier, Aircraft.Currency, Aircraft.AircraftDescription
FROM (((Aircraft
INNER JOIN Advertisers ON Aircraft.AdvertiserId = Advertisers.Id)
INNER JOIN AircraftModels ON Aircraft.AircraftModelId = AircraftModels.Id)
INNER JOIN AircraftManufacturers ON AircraftModels.ManufacturerId = AircraftManufacturers.Id)
INNER JOIN Locations ON Aircraft.LocationId = Locations.Id
JOIN iter$simple_intlist_to_tbles(#ids) i ON AircraftModels.id = i.number
WHERE (Aircraft.IsActive=1 AND Advertisers.IsActive=1 AND Aircraft.IsSold=0 AND (Aircraft.EndDate>=#Date OR Aircraft.EndDate Is Null) AND Locations.Id = #Location)
OR (Aircraft.IsActive=1 AND Advertisers.IsActive=1 AND Aircraft.IsSold=1 AND Aircraft.SoldDate>=#Date2 AND Locations.Id = #Location)
ORDER BY Advertisers.IsPremiumAdvertiser ASC, Aircraft.DateAdded DESC, Aircraft.ListPosition DESC,
Aircraft.LastUpdateDate, AircraftManufacturers.Name, AircraftModels.ModelName, Aircraft.ModelSuffix,
Aircraft.Id DESC
END
iter$simple_intlist_to_tbles(#ids) simple builds a table from the #ids input. This input comes in the form of a strings of id numbers seperated by a ',' eg ,1,,2,,3,,4, etc...
Now I need to replace the #Location with a string of location IDs formatted in this same fashion eg ,1,,2,,3,,4, etc....
So my problem is this... How do I adapt the above sql/ stored procedure so that the two 'WHERE' clauses which filter based on a single location, are now able to take multiple location IDs ??????
Any help would be really appreciated.
Thanks.
To solve your problem, simply retrieve the values from iter$simple_intlist_to_tbles(#Location) in a subquery, and check for them with IN:
AND Locations.Id IN (SELECT * FROM iter$simple_intlist_to_tbles(#Location))
Your where clause is also more complex that it needs to be. There are identical AND requirements in each OR, so you can move them outside the OR. It simplifies to:
WHERE Aircraft.IsActive=1
AND Advertisers.IsActive=1
AND ((Aircraft.IsSold=0 AND (Aircraft.EndDate>=#Date OR Aircraft.EndDate Is Null))
OR (Aircraft.IsSold=1 AND Aircraft.SoldDate>=#Date2))
AND Locations.Id IN (SELECT * FROM iter$simple_intlist_to_tbles(#Location))
By using a subquery like:
FROM blah ... AND Locations.Id IN (SELECT number FROM iter$simple_intlist_to_tbles(#locations))

Resources