Optional Where Clause Based On Another Table - sql

I have a stored proc where a table of integers is passed as a parameter. I'm trying to find a reasonable way of writing "give me all the records, but if the parameter table has values in it then limit my results to those values".
Both approaches in the queries below work, but when I use either approach in my real-world proc (with a substantial number of joins and apply clauses and a ton of data) it's quite a bit slower than I would like even when the number of rows in the variable table is limited to 1 or 2 records.
Is there a better way of doing this?
-- Apprroach1 - Weird WHERE clause
IF OBJECT_ID('tempdb.dbo.#list') IS NOT NULL DROP TABLE #list
create table #list(Id int)
insert into #list(id) values (726), (712), (725)
declare #listCount int
select #listCount = count(*) from #list
select * from SalesLT.Product p
where 1 = 1
AND
(
#listCount > 0 AND p.ProductID in (select Id from #list)
OR
#listCount = 0
)
and
-- approach 2 - goofy looking JOIN
IF OBJECT_ID('tempdb.dbo.#list') IS NOT NULL DROP TABLE #list
create table #list(Id int)
insert into #list(id) values (726), (712), (725)
declare #listCount int
select #listCount = count(*) from #list
select * from SalesLT.Product p
inner join #list l on
case when #listCount > 0 and l.Id = p.ProductID Then 1
else 0
end = 1

Generally, case when in joins (ON clause) are avoided as it will make less performant query.
Use the left join approach as follows:
select * from SalesLT.Product p
Left join #list l on l.Id = p.ProductID
Where ( (#listCount > 0 and l.id is not null)
or #listCount = 0)

Try this
IF #listCount > 0
BEGIN
SELECT
*
FROM SalesLT.Product p
------------------------
INNER JOIN #list l ON
------------------------
l.Id = p.ProductID
------------------------
END
-- I assume you want to output everything if #listCount = 0
ELSE IF #listCount = 0
BEGIN
SELECT
*
FROM SalesLT.Product p
END
If you have a bunch of joins using that table outputs, you can store the output and use it on your real join/query.
Example:
IF #listCount > 0
BEGIN
SELECT
*
INTO #TempSalesTbl
FROM SalesLT.Product p
------------------------
INNER JOIN #list l ON
------------------------
l.Id = p.ProductID
------------------------
END
-- In your query
SELECT
*
FROM Table A
INNER JOIN #TempSalesTbl ON
...

Related

How do you properly query the result of a complex join statement in SQL?

New to advanced SQL!
I'm trying to write a query that returns the COUNT(*) and SUM of the resulting columns from this query:
DECLARE #Id INT = 1000;
SELECT
*,
CASE
WHEN Id1 >= 6 THEN 1
ELSE 0
END AS Tier1,
CASE
WHEN Id1 >= 4 THEN 1
ELSE 0
END AS Tier2,
CASE
WHEN Id1 >= 2 THEN 1
ELSE 0
END AS Tier3
FROM (
SELECT
Org.OrgID,
App.AppID,
App.FirstName,
App.LastName,
MAX(AppSubmitU_Level.Id1) AS Id1
FROM Org
INNER JOIN AppEmployment
ON AppEmployment.OrgID = Org.OrgID
INNER JOIN App
ON App.AppID = AppEmployment.AppID
INNER JOIN AppSubmit
ON App.AppID = AppSubmit.AppID
INNER JOIN AppSubmitU_Level
ON AppSubmit.LevelID = AppSubmitU_Level.Id1
INNER JOIN AppEmpU_VerifyStatus
ON AppEmpU_VerifyStatus.VerifyStatusID = AppEmployment.VerifyStatusID
WHERE AppSubmitU_Level.SubmitTypeID = 1 -- Career
AND AppEmpU_VerifyStatus.StatusIsVerified = 1
AND AppSubmit.[ExpireDate] IS NOT NULL
AND AppSubmit.[ExpireDate] > GETDATE()
AND Org.OrgID = #Id
GROUP BY
Org.OrgID,
App.AppID,
App.FirstName,
App.LastName
) employees
I've tried to do so by moving the #Id outside the original query, and adding a SELECT(*), SUM, and SUM to the top, like so:
DECLARE #OrgID INT = 1000;
SELECT COUNT(*), SUM(employees.Tier1), SUM(employees.Tier2), SUM(employees.Tier3)
FROM
(SELECT *,
...
) AS employees
);
When I run the query, however, I'm getting the errors:
The multi-part identifier employees.Tier1 could not be bound
The same errors appear for the other identifiers in my SUM statements.
I'm assuming this has to do with the fact that the Tier1, Tier2, and Tier3 columns are being returned by the inner join query in my FROM(), and aren't values set by the existing tables that I'm querying. But I can't figure out how to rewrite it to initialize properly.
Thanks in advance for the help!
This is a scope problem: employees is defined in the subquery only, it is not available in the outer scope. You basically want to alias the outer query:
DECLARE #OrgID INT = 1000;
SELECT COUNT(*), SUM(employees.Tier1) TotalTier1, SUM(employees.Tier2) TotalTier2, SUM(employees.Tier3) TotalTier3
FROM (
SELECT *,
...
) AS employees
) AS employees;
--^ here
Note that I added column aliases to the outer query, which is a good practice in SQL.
It might be easier to understand what is going on if you use another alias for the outer query:
SELECT COUNT(*), SUM(e.Tier1), SUM(e.Tier2), SUM(e.Tier3)
FROM (
SELECT *,
...
) AS employees
) AS e;
Note that you don't actually need to qualify the column names in the outer query, since column names are unambigous anyway.
And finally: you don't actually need a subquery. You could write the query as:
SELECT
SUM(CASE WHEN Id1 >= 6 THEN 1 ELSE 0 END) AS TotalTier1,
SUM(CASE WHEN Id1 >= 4 THEN 1 ELSE 0 END) AS TotalTier2,
SUM(CASE WHEN Id1 >= 2 THEN 1 ELSE 0 END) AS TotalTier3
FROM (
SELECT
Org.OrgID,
App.AppID,
App.FirstName,
App.LastName,
MAX(AppSubmitU_Level.Id1) AS Id1
FROM Org
INNER JOIN AppEmployment
ON AppEmployment.OrgID = Org.OrgID
INNER JOIN App
ON App.AppID = AppEmployment.AppID
INNER JOIN AppSubmit
ON App.AppID = AppSubmit.AppID
INNER JOIN AppSubmitU_Level
ON AppSubmit.LevelID = AppSubmitU_Level.Id1
INNER JOIN AppEmpU_VerifyStatus
ON AppEmpU_VerifyStatus.VerifyStatusID = AppEmployment.VerifyStatusID
WHERE AppSubmitU_Level.SubmitTypeID = 1 -- Career
AND AppEmpU_VerifyStatus.StatusIsVerified = 1
AND AppSubmit.[ExpireDate] IS NOT NULL
AND AppSubmit.[ExpireDate] > GETDATE()
AND Org.OrgID = #Id
GROUP BY
Org.OrgID,
App.AppID,
App.FirstName,
App.LastName
) employees

More effective way to write following SQL query

I am writing a query to return a list of articles for the news portal homepage.
Requirement is following.
Each category which needs to be on the homepage needs to display 5 articles by following criteria.
Each category needs to have one article which is main news for the category, followed by 4 most popular news at the time being.
If there is no first news for category set, then display 5 most popular insted.
I wrote a SQL Function which has CategoryID parameter and another SQL procedure which calls that function N Times.
Is there more efficient way to write this query?
Function
CREATE FUNCTION [dbo].[Fn_FetchHomepageCategory]
(
-- Add the parameters for the function here
#categoryId int
)
RETURNS #ArticlesToReturn TABLE
( Id int,
Title nvarchar(500),
Slug nvarchar(500),
Summary nvarchar(1500),
IsCategoryFirst bit,
RootCategoryId int,
RootCategory nvarchar(500),
OldFacebookCommentsUrl nvarchar(500),
Icon nvarchar(500),
TopicName nvarchar(500),
MainArticlePhoto nvarchar(500),
FrontPagePhoto nvarchar(500),
PublishDate datetime
)
AS
BEGIN
-- select category first news if any
INSERT INTO #ArticlesToReturn
SELECT TOP 1
ART.Id, ART.Title, ART.InitialTitle, ART.Summary,ART.IsCategoryFirst,
ART.RootCategoryId, CAT.Name, ART.OldFacebookCommentsUrl, ICO.CssClass,
ART.TopicName, ART.MainArticlePhoto, ART.FrontPagePhoto, ART.PublishDate
FROM Articles ART WITH (NOLOCK)
INNER JOIN ArticleViewCountSum AVS WITH (NOLOCK) ON AVS.ArticleId = ART.Id
INNER JOIN Categories CAT WITH (NOLOCK) ON CAT.Id = ART.RootCategoryId
LEFT JOIN ArticleIcons ICO WITH (NOLOCK) ON ICO.Id = ART.IconId
WHERE ART.RootCategoryId = #categoryId
AND ART.PublishDate < GETDATE()
AND ART.Active = 1
AND IsCategoryFirst = 1
-- select 5 most popular by coefficient
INSERT INTO #ArticlesToReturn
SELECT TOP 5
ART.Id, ART.Title, ART.InitialTitle, ART.Summary,ART.IsCategoryFirst,
ART.RootCategoryId, CAT.Name, ART.OldFacebookCommentsUrl, ICO.CssClass,
ART.TopicName, ART.MainArticlePhoto, ART.FrontPagePhoto, ART.PublishDate
FROM Articles ART WITH (NOLOCK)
INNER JOIN ArticleViewCountSum AVS WITH (NOLOCK) ON AVS.ArticleId = ART.Id
INNER JOIN Categories CAT WITH (NOLOCK) ON CAT.Id = ART.RootCategoryId
LEFT JOIN ArticleIcons ICO WITH (NOLOCK) ON ICO.Id = ART.IconId
WHERE ART.RootCategoryId = #categoryId
AND ART.PublishDate < GETDATE()
AND ART.Active = 1
ORDER BY ART.Coefficient DESC
RETURN
END
Stored procedure:
CREATE PROCEDURE [dbo].[Fetch_HomePageArticles]
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #dateNow datetime = GETDATE();
-- first main news
SELECT TOP 1 * FROM Articles
WHERE IsFirst = 1 AND PublishDate < #dateNow
--TODO: featured
SELECT TOP 10 * From Featured
WHERE PublishDate < #dateNow AND Active = 1
ORDER BY PublishDate DESC
SELECT TOP 5 * FROM Fn_FetchHomepageCategory(3)
SELECT TOP 5 * FROM Fn_FetchHomepageCategory(150)
SELECT TOP 5 * FROM Fn_FetchHomepageCategory(1523)
SELECT TOP 5 * FROM Fn_FetchHomepageCategory(1509)
SELECT TOP 5 * FROM Fn_FetchHomepageCategory(1569)
SELECT TOP 5 * FROM Fn_FetchHomepageCategory(1545)
SELECT TOP 5 * FROM Fn_FetchHomepageCategory(1548)
SELECT TOP 5 * FROM Fn_FetchHomepageCategory(67)
END
I tried to modify function to have only one SELECT and included Order BY IsFirstCategory DESC, but query ran much slower then.
One potential improvement would be merging two SELECT clauses in the Fn_FetchHomepageCategory function into one single query by adding a new made-up Coefficient parameter:
SELECT
TOP 5 ART.Id,
ART.Title,
ART.InitialTitle,
ART.Summary,
ART.IsCategoryFirst,
ART.RootCategoryId,
CAT.Name,
ART.OldFacebookCommentsUrl,
ICO.CssClass,
ART.TopicName,
ART.MainArticlePhoto,
ART.FrontPagePhoto,
ART.PublishDate
FROM
Articles ART WITH (NOLOCK)
INNER JOIN ArticleViewCountSum AVS WITH (NOLOCK) ON AVS.ArticleId = ART.Id
INNER JOIN Categories CAT WITH (NOLOCK) ON CAT.Id = ART.RootCategoryId
LEFT JOIN ArticleIcons ICO WITH (NOLOCK) ON ICO.Id = ART.IconId
WHERE
ART.RootCategoryId = #categoryId
AND ART.PublishDate < GETDATE()
AND ART.Active = 1
ORDER BY
CASE IsCategoryFirst
WHEN 1 THEN 1000000
ELSE ART.Coefficient
END DESC
You can replace 1000000 with another big number. Its only point is assigning the highest co-efficiency score possible to the post that have IsCategoryFirst = 1.
Please note that it works fine only if you have only one post with IsCategoryFirst = 1.

SQL server join issue

i have the following statement in a stored procedure that is returning strange results. Given two columns where one (RL) is less than 0 e.g -2, it should add 2 to another column (HD). If the negative value was -8 it should add 8 to the HD column.
In a test ive just done, the RL column had 0 and HD was 2. I changed the RL to -2 and run the code. I was EXPECTING : RL = 0 and HD = 4. INSTEAD the RESULT was RL = 0 and HD = 5.
I think the problem is due to the presence of the join. How would i write this to replace the join with a WHERE clause please.
UPDATE P
SET P.HD = P.HD + P.RL
,P.RL = 0
FROM Products P
INNER JOIN (
SELECT id
,RL
FROM Products
WHERE id IN (
SELECT ProductID
FROM OrderDetails
WHERE OrderID = #OrderId
)
AND RL < 0
) Q ON P.ID = Q.id
cheers
Try this one -
UPDATE Products
SET HD = HD + RL,
RL = 0
FROM P
WHERE RL < 0
AND ID IN (
SELECT ProductID
FROM dbo.OrderDetails
WHERE OrderID = #OrderId
)
Small check -
DECLARE #t TABLE (a INT, b INT)
INSERT INTO #t (a, b)
VALUES (1, 2)
UPDATE #t
SET a = b, b = 0
SELECT * FROM #t

SQL Text Matching Query Tuning

I'm trying to do some free text search matching, and wondering if I can improve this query (using MSSQL 2008):
#FreeText is a table, where each row is a search word
DECLARE #WordCount = (SELECT COUNT(*) from #FreeText)
SELECT p.ID
FROM Product p
OUTER APPLY
(
SELECT COUNT(ID) as MatchCount
FROM Product pm
INNER JOIN #FreeText ft
ON pm.txt like '%'+ft.text+'%'
WHERE pm.ID = p.ID
AND (SELECT TOP 1 [text] FROM #FreeText) IS NOT NULL
)MC
WHERE MatchCount = #WordCount
So I'm wondering if there is any way to avoid the "FROM Product pm" in the outer apply?
I cannot always INNER JOIN #FreeText because sometimes we don't use free text searching.
Any thoughts or tips would be greatly appreciated, also let me know if I can clarify anything. Thanks in advance.
P.S. I do know that MS SQL has a FREETEXT() search, but I unfortunately cannot use that at the moment.
Here's a query without OUTER APPLY, that returns all results when there are no search critera.
DECLARE #FreeText TABLE
(
[text] varchar(200)
)
INSERT INTO #FreeText SELECT 'a'
INSERT INTO #FreeText SELECT 'c'
-- what, null? No.
DELETE FROM #FreeText WHERE [text] is null
DECLARE #WordCount int
SET #WordCount = (SELECT Count(*) FROM #FreeText)
SELECT p.ID
FROM Product p
LEFT JOIN #FreeText ft
ON p.txt like '%' + ft.text + '%'
WHERE ft.text is not null OR #WordCount = 0
GROUP BY p.ID
HAVING COUNT(*) = #WordCount OR #WordCount = 0
Note: it would be my preference to not use the "freetext" query when there is not any freetext criteria - instead use another query (simpler). If you choose to go that route - go back to an INNER JOIN and drop the OR #WordCount = 0 x2.

How to check intersection of subqueries in query?

I have the next query:
SELECT c.client_code, a.account_num, m.account_close_date, u.uso, m.product_name
FROM accounts a INNER JOIN Clients c ON c.id = a.client_id INNER JOIN
Uso u ON c.uso_id = u.uso_id INNER JOIN Magazine m ON a.account_id = m.account_id
and I need to compare product_name with input parameter.
product_name and input parameter #s are comma-delimited strings.
I use next split function:
ALTER FUNCTION [dbo].[Split]
(
#s VARCHAR(max),
#split CHAR(1)
)
RETURNS #temptable TABLE (items VARCHAR(MAX))
AS
BEGIN
DECLARE #x XML
SELECT #x = CONVERT(xml,'<root><s>' + REPLACE(#s,#split,'</s><s>') + '</s></root>');
INSERT INTO #temptable
SELECT [Value] = T.c.value('.','varchar(20)')
FROM #X.nodes('/root/s') T(c);
RETURN
END;
I think that I need to check the intersection of tables, which I will receive after split of product_name and after split of input parameter. I trid to do this:
WHERE (select * from dbo.Split(m.product_name, ';')
INTERSECT select * from dbo.Split('product1;product2',';'))
is not null
But it does not work quite right. Please, help me.
INTERSECT requires the same column output and is used like UNION or EXCEPT: not in the WHERE clause
Just JOIN onto the udf
...
INNER JOIN
Magazine m ON a.account_id = m.account_id
INNER JOIN
dbo.Split(#parameter, ';') CSV ON m.productname = CSV.items
If you need to split m.productname, if you can't fix the design, use CROSS APPLY
...
INNER JOIN
Magazine m ON a.account_id = m.account_id
CROSS APPLY
dbo.Split(m.productname, ';') WTF
INNER JOIN
dbo.Split(#parameter, ';') CSV ON WTF.items = CSV.items
However, JOIN and INTERSECT give different results if #parameter has duplicated values. Add a DISTINCT to the UDF for example to get around this. Or change the udf JOIN into EXISTS