I have a query which uses a IN Filter and works fine. I am wondering if there
is something like a wildcard char which will not filter anything
Select *
FROM [tbl_Leads]
where p_contact_first_name in ('Tom')
the above works as desired but what happens if i don't want to filter by anything and return all. I know i can create a second query and removing the IN clause but from the logic if possible it would be nicer if i can check for existence of filter value and if none present replace it with wildcard char
The IN operator doesn't allow wildcards or partial values to match. In fact it's just a syntactic sugar of a chaining of OR logical operators.
This query:
SELECT 1 FROM SomeTable AS T
WHERE T.Column IN (1, 2, 3, 4)
Is exactly the same as:
SELECT 1 FROM SomeTable AS T
WHERE
T.Column = 1 OR
T.Column = 2 OR
T.Column = 3 OR
T.COlumn = 4
And this is why having a NULL value with a NOT IN list will make all the logic result be UNKNOWN (hence interpreted as false and never return any record):
SELECT 1 FROM SomeTable AS T
WHERE T.Column NOT IN (1, 2, NULL, 4)
Will be:
SELECT 1 FROM SomeTable AS T
WHERE
NOT(
T.Column = 1 OR
T.Column = 2 OR
T.Column = NULL OR -- Always resolve to UNKNOWN (handled as false for the whole condition)
T.COlumn = 4
)
You have a few options to conditionally apply a filter like IN:
Use OR against another condition:
DECLARE #ApplyInFilter BIT = 0
SELECT 1 FROM SomeTable AS T
WHERE
(#ApplyInFilter = 1 AND T.Column IN (1, 2, 3, 4)) OR
#ApplyInFilter = 0
Avoid the query altogether (have to repeat whole statement):
DECLARE #ApplyInFilter BIT = 0
IF #ApplyInFilter = 1
BEGIN
SELECT 1 FROM SomeTable AS T
WHERE
T.Column IN (1, 2, 3, 4)
END
ELSE
BEGIN
SELECT 1 FROM SomeTable AS T
END
Use Dynamic SQL to conditionally omit the filter:
DECLARE #ApplyInFilter BIT = 0
DECLARE #DynamicSQL VARCHAR(MAX) = '
SELECT 1 FROM SomeTable AS T '
IF #ApplyInFilter = 1
SET #DynamicSQL += ' WHERE T.Column IN (1, 2, 3, 4) '
EXEC (#DynamicSQL)
Unfortunately, the best approach if you plan to have multiple conditional filters is the Dynamic SQL one. It will be the hardest to code but best for performance (with some caveats). Please read George's Menoutis link to fully understand pros and cons of each approach.
You can make use of not exists to get the desired results. From my understanding if you have a name like Tom you want only that row and if it does not you want all other rows to be displayed.
select 1 as ID, 'Tom' as Name into #temp
union all
select 2 as ID, 'Ben' as Name union all
select 3 as ID, 'Kim' as Name
union all
select 4 as ID, 'Jim' as Name
This query will check if Tom exists then display only that row if not display all.
select * from #temp
where name = 'TOm' or not exists (select 1 from #temp where name = 'Tom')
Result from above query:
ID Name
1 Tom
Lets test it, by deleting the row where Tom record is.
Delete from #temp
where name = 'Tom'
If you run the same query you get the following result.
select * from #temp
where name = 'TOm' or not exists (select 1 from #temp where name = 'Tom')
ID Name
2 Ben
3 Kim
4 Jim
As said by Dale Burrell, the fast way to implement dynamic search conditions (exactly what your problem is) is to put code like:
....and field=values or #searchThisField=0
The other solution would be dynamic sql.
I consider Erland Sommarskog's article to be the epitome of analyzing this specific subject.
Make two requests. The performance of these two queries will be better than that of a single universal query. You can compare the execution plan for these queries.
Related
I want to write a query that selects rows by condition, but if there is no response for that condition, the code should select the same columns, but without the condition.
What the right way to do this? Thanks!
This is my example - select this:
select top 1 *
from tbl
where isActive = 1
but if there is no response, select this instead:
select top 1 *
from tbl
Note that the query is big and complex, so I prefer not to select one and then select the second one, if the first one is null. Also because I have a union after this and it throws an error with this syntax.
Assuming your query is a select top x ... query I would simply use the order by as suggested by Peter's answer. Assuming it's not involving top x, since you wrote your actual query is big and complicated, you can use a common table expression.
The idea is that you encapsulate the big and complex query inside the cte, but instead of writing the where clause to filter out records, you use a case expression to return 1 or 0 if the condition is true or false for each record.
Then you select from that cte where either the case expression results with 1 or there are no records in the cte where the case expression results with 1.
Create and populate sample table (Please save us this step in your future questions)
DECLARE #T AS TABLE
(
Id int identity(1,1),
IsActive bit
)
INSERT INTO #T VALUES
(1),(1),(NULL),(1),(1),(NULL),
(1),(1),(NULL),(1),(1),(NULL),
(1),(1),(NULL),(1),(1),(NULL),
(1),(1),(NULL),(1),(1),(NULL)
The common table expression:
;WITH CTE AS
(
SELECT Id, IsActive,
CASE WHEN IsActive = 1 THEN 1
ELSE 0
END As FoundRecords
FROM #T
)
The query:
SELECT Id, IsActive
FROM CTE
WHERE FoundRecords = 1
OR NOT EXISTS
(
SELECT 1
FROM CTE
WHERE FoundRecords = 1
)
Results:
Id IsActive
1 True
2 True
4 True
5 True
7 True
8 True
10 True
11 True
13 True
14 True
16 True
17 True
19 True
20 True
22 True
23 True
You can see a live demo on rextester
The simplest way is to not use where isActive = 1 but instead order by isActive in descending order:
select top 1 *
from tbl
order by isActive desc
You might even want to consider adding additional ordering fields (e.g. id, name, date), because without that the resulting item could be unpredictable.
You can use the IF-Else condition, right?
DECLARE #result INT
SET #result = CONVERT(INT, (SELECT COUNT(*) FROM tbl WHERE isActive = 1))
IF (#result > 0)
BEGIN
select top 1 * from tbl where isActive = 1;
END
ELSE
BEGIN
select top 1 * from tbl;
END
I need to select related Ids from a table based on a list of provided Ids - effectively an Adjacency List problem. I have a working query for a single Id, but it is frankly inelegant at best even though it works! I would welcome suggestions for improvements and for ways to move the single Id solution to a multiple Id solution.
I have a database table like so:
CREATE TABLE [BookingLines]
(
[BookingLineId] BIGINT NOT NULL IDENTITY (138, 1),
[BookingId] BIGINT NOT NULL,
---- Additional Columns Redacted for brevity
[ContractNumber] INT NOT NULL DEFAULT 0,
[ContractSubNumber] DECIMAL NOT NULL DEFAULT 0,
---- Additional Columns Redacted for brevity
);
There will be records in this table, and in some cases there will be 1 or more pairs of records relating to the same Booking Id. The differentiation is in the ContractSubNumber column, where one value in the pair will be n.0 and the other n.1. So if there were three consecutive pairs, the Contract SubNumbers would be:
LineId BookingId SubNumber
1 1 0.0
2 1 0.1
3 1 1.0
4 1 1.1
5 1 2.0
6 1 2.1
I may need to start from the Line Id representing either of the sub numbers, and collect the opposing one. So, if I am starting from LineId 1, I need to retrieve LineId 2 being the related row. I can do this on a single Id using multiple sub selects, like this:
SELECT BookingLineId
FROM
(
SELECT BookingLineId
FROM BookingLines
WHERE BookingId = 1
AND FLOOR(ContractSubNumber) =
(
SELECT FLOOR(ContractSubNumber)
FROM BookingLines
WHERE BookingId = 1 AND BookingLineId = (1)
)
)
WHERE BookingLineId <> 1;
This works correctly, returning the value 2 in this case.
How can I make this more elegant and efficient?
How can I rewrite this to return the opposing values of all Ids in a specified list e.g.
WHERE BookingId = 1 AND BookingLineId IN (1,3,5))
and have it return the result 2,4,6?
All suggestions gratefully received.
EDIT
I have corrected the typo in the SQL provided in the original question, and using the framework proposed by #McNets this is the solution I went for:
SELECT BL.BookingLineId
FROM BookingLines BL
INNER JOIN BookingLines ABL ON ABL.BookingId = BL.BookingId
AND ABL.BookingLineId IN (22, 24, 26)
AND FLOOR(BL.ContractSubNumber) = FLOOR(ABL.ContractSubNumber)
WHERE BL.BookingId = 3 AND BL.BookingLineId NOT IN (22,24,26);
I am very grateful for the contributions and for the final answer. Thanks guys!
As far as there is no information about AgencyBookingLines and no sample data I cannot set up a fiddle example, but I think you can move the AgencyBookingLines subquery to the ON clause.
SELECT BL.BookingLineId
FROM BookingLines BL
INNER JOIN AgencyBookingLines ABL
ON ABL.BookingId = BL.BookinId
AND ABL.BookingLineId = 1
AND FLOOR(BL.ContractSubNumber) = FLOOR(ABL.ContractSubNumber
WHERE BL.BookingId = 1
AND BL.BookingLineId <> 1;
--
-- AND BL.BookingLineId IN (2,4,6);
Will it sub numbers always *.0 & *.1. Then you could try the below
SELECT oppo.*
FROM AgencyBookingLines AS main
INNER JOIN AgencyBookingLines AS oppo ON
oppo.BookingId = main.BookingId
AND oppo.SubNumber <> main.SubNumber
AND FLOOR(oppo.SubNumber) = FLOOR(main.SubNumber)
WHERE main.BookingId = 1
AND main.LineId IN (1,3,5)
While using case when in where clause in sql query it's not working.
Problem :
I have two tables named TblEmployee and TblAssociate.Both tables contains common columns PeriodId, EmpId and AssociateId. My requirement is to fetch values from
TblEmployee with combination of EmpId and AssociateId from TblAssociate should be excluded.And the exclusion should be based on PeriodId condition.`
If(#PeriodID<50)
BEGIN
SELECT *
FROM TblEmployee
WHERE (EmpId+AssociateId) NOT IN (SELECT EmpId+AssociateId FROM TblAssociate)
END
ELSE
BEGIN
SELECT *
FROM TblEmployee
WHERE (EmpId) NOT IN (SELECT EmpId FROM TblAssociate)
END
The above code is working, but I need to avoid that IF-ELSE condition and I wish to use 'case when' in where clause.Please help
Try this:
SELECT *
FROM TblEmployee
WHERE (EmpId + CASE WHEN #PeriodID<50 THEN AssociateId ELSE 0 END) NOT IN
(SELECT EmpId + CASE WHEN #PeriodID<50 THEN AssociateId ELSE 0 END FROM TblAssociate)
You say your code is working but this is rather odd, since it doesn't make much sense to add together id values. In any case, the above statement produces a result that is equivalent to the code originally posted.
You could use AND-OR combination in the WHERE clause. Additionally, you should not be using + as it may lead to incorrect result. You can rewrite your query as:
SELECT e.*
FROM TblEmployee e
WHERE
(
#PeriodID < 50
AND NOT EXISTS(
SELECT 1
FROM TblAssociate a
WHERE
a.EmpId = e.EmpId
AND a.AssociateId = e.AssociateId
)
)
OR
(
#PeriodID >= 50
AND NOT EXISTS(
SELECT 1
FROM TblAssociate a
WHERE a.EmpId = e.EmpId
)
)
The addition of IDs do not guarantee uniqueness. For instance, if EmpId is 5 and AssociateId is 6, then EmpId + AssociateId = 11, while EmpId + AssociateId = 11 even if EmpId is 6 and AssociateId is 5. In the query below, I made sure that the subquery will stop searching when the first record is found and will return a single record, having the value of 1. We select the employee if and only if 1 is among the results. In the subquery we check the operand we are sure of first and then check if we are not in a period where AssociateId must be checked, or it matches.
select *
from TblEmployee
where 1 in (select top 1 1
from TblAssociate
where TblEmployee.EmpId = TblAssociate.EmpId and
(#PeriodID >= 50 or TblEmployee.AssociateId = TblAssociate.AssociateId))
The two tables I am using are
Contacts (Contact_id, Contact_name,...)
Contacts_group_options (Contact_id, Group_id, Status)
I want to get all the contacts that are not part of a specific group from the Contacts_group_options table .
The problem is, if first table has Contact_ids 1, 2, 3, 4 and second table has Contact_ids 2, 3 for a Group_id = 1, I want result as 1 and 4. But I am getting 1, 3, 4, 1, 2, 4.
First 1 is compared with 2. Both are not same. Output 1, then 2 with 2 same so don't output, 3 with 2 output 3, 4 with 2 output 4,1 with 3 output 1,2 with 3, output 2,3 with 3 dont output,4 with 3 output 4.
stored procedure is:
Select
m.Contact_id,
m.Contact_code,
m.Contact_name,
m.Phone_number,
m.Mail_id,
m.Designation,
m.Department,
m.User_id,
m.User_type,
m.Status
from
Contacts as m,
Contact_group_options as b
,#tblType_contacts2group as i
where
b.Group_id = i.Group_id
and m.Contact_id != b.Contact_id
I think you can do this with a simple NOT EXISTS:
SELECT c.*
FROM Contacts AS c
WHERE
NOT EXISTS(
SELECT 1
FROM Contact_group_options
WHERE
Group_id = 1
AND Contact_id = c.Contact_id
)
You can try this:
-- NOT EXISTS
SELECT C.*
FROM Contacts C
WHERE NOT EXISTS (SELECT 1 FROM Contacts_group_options WHERE Group_id = 1 AND C.Contact_id)
-- NOT IN
SELECT *
FROM Contacts
WHERE Contact_id NOT IN (SELECT Contact_id FROM Contacts_group_options WHERE Group_id = 1)
Your result set includes so many rows because you actually have three tables there: Contacts, Contact_group_options and #tblType_contacts2group.
Maybe you wanted to write something like this:
-- compacted for to make it more readable
Select
m.Contact_id, m.Contact_code, m.Contact_name, m.Phone_number,
m.Mail_id, m.Designation, m.Department, m.User_id, m.User_type, m.Status
from Contacts as m
where not exists (select 1 from #tblType_contacts2group as i where i.Group_id = m.Group_id)
NOTE: Since #tblType_contacts2group is not clearly defined in your example, above query might not get the correct results.
As already Felix mentioned, you should always use proper joins. E.g. the above query could be like this:
Select
m.Contact_id, m.Contact_code, m.Contact_name, m.Phone_number,
m.Mail_id, m.Designation, m.Department, m.User_id, m.User_type, m.Status
from Contacts as m
left join #tblType_contacts2group i on i.Group_id = m.Group_id
where i.Group_id IS NULL -- any non-null column from i can be used here
However, keep in mind that, usually, LEFT JOIN with IS NULL is slower that NOT EXISTS equivalent.
I want to update rows of a table in a specific order, like one would expect if including an ORDER BY clause, but SQL Server does not support the ORDER BY clause in UPDATE queries.
I have checked out this question which supplied a nice solution, but my query is a bit more complicated than the one specified there.
UPDATE TableA AS Parent
SET Parent.ColA = Parent.ColA + (SELECT TOP 1 Child.ColA
FROM TableA AS Child
WHERE Child.ParentColB = Parent.ColB
ORDER BY Child.Priority)
ORDER BY Parent.Depth DESC;
So, what I'm hoping that you'll notice is that a single table (TableA) contains a hierarchy of rows, wherein one row can be the parent or child of any other row. The rows need to be updated in order from the deepest child up to the root parent. This is because TableA.ColA must contain an up-to-date concatenation of its own current value with the values of its children (I realize this query only concats with one child, but that is for the sake of simplicity - the purpose of the example in this question does not necessitate any more verbosity), therefore the query must update from the bottom up.
The solution suggested in the question I noted above is as follows:
UPDATE messages
SET status=10
WHERE ID in (SELECT TOP (10) Id
FROM Table
WHERE status=0
ORDER BY priority DESC
);
The reason that I don't think I can use this solution is because I am referencing column values from the parent table inside my subquery (see WHERE Child.ParentColB = Parent.ColB), and I don't think two sibling subqueries would have access to each others' data.
So far I have only determined one way to merge that suggested solution with my current problem, and I don't think it works.
UPDATE TableA AS Parent
SET Parent.ColA = Parent.ColA + (SELECT TOP 1 Child.ColA
FROM TableA AS Child
WHERE Child.ParentColB = Parent.ColB
ORDER BY Child.Priority)
WHERE Parent.Id IN (SELECT Id
FROM TableA
ORDER BY Parent.Depth DESC);
The WHERE..IN subquery will not actually return a subset of the rows, it will just return the full list of IDs in the order that I want. However (I don't know for sure - please tell me if I'm wrong) I think that the WHERE..IN clause will not care about the order of IDs within the parentheses - it will just check the ID of the row it currently wants to update to see if it's in that list (which, they all are) in whatever order it is already trying to update... Which would just be a total waste of cycles, because it wouldn't change anything.
So, in conclusion, I have looked around and can't seem to figure out a way to update in a specified order (and included the reason I need to update in that order, because I am sure I would otherwise get the ever-so-useful "why?" answers) and I am now hitting up Stack Overflow to see if any of you gurus out there who know more about SQL than I do (which isn't saying much) know of an efficient way to do this. It's particularly important that I only use a single query to complete this action.
A long question, but I wanted to cover my bases and give you guys as much info to feed off of as possible. :)
Any thoughts?
You cannot succeed this in one query, because your updates are correlated (ie. level N depends on the updated value of level N+1). Relational engines frown on this very explicitly because of the Halloween Problem. The query plan will go out of its way to ensure that the updates occur as if they had two stages: one in which the current state was read, and then one in which the updated state was applied. If necessary, they'll spool intermediate tables just to preserve this apparent execution order (read all->write all). Since your query, if I understand correctly, tries to break this very premise I don't see any way you'll succeed.
UPDATE statements will be executed as a single query, not as a step by step result.
You need to either use a while loop/cursor (uhhgg) or maybe make use of a CTE expression view to achieve what you are trying, which gives you the recursice possibility.
Have a look at
Using Common Table Expressions
Recursive Queries Using Common Table
Expressions
Here is a one line SQL solution. If you ever relax the requirement that it need be one update statement you can factor out some of the complexity
CREATE TABLE [TableA](
[ID] [int] NOT NULL,
[ParentID] [int] NULL,
[ColA] [varchar](max) NOT NULL,
[Priority] [varchar](50) NOT NULL,
[Depth] [int] NOT NULL)
go
INSERT TableA
SELECT 1, NULL, 'p', 'Favorite', 0 UNION ALL
SELECT 2, 1, 'm', 'Favorite', 1 UNION ALL
SELECT 3, 1, 'o', 'Likeable', 1 UNION ALL
SELECT 4, 2, 'v', 'Favorite', 2 UNION ALL
SELECT 5, 2, 'v', 'Likeable', 2 UNION ALL
SELECT 6, 2, 'd', 'Likeable', 2 UNION ALL
SELECT 7, 6, 'c', 'Red-headed Stepchild', 3 UNION ALL
SELECT 8, 6, 's', 'Likeable', 3 UNION ALL
SELECT 9, 8, 'n', 'Favorite', 4 UNION ALL
SELECT 10, 6, 'c', 'Favorite', 3 UNION ALL
SELECT 11, 5, 'c', 'Favorite', 3 UNION ALL
SELECT 12, NULL, 'z', 'Favorite', 0 UNION ALL
SELECT 13, 3, 'e', 'Favorite', 2 UNION ALL
SELECT 14, 8, 'k', 'Likeable', 4 UNION ALL
SELECT 15,4, 'd', 'Favorite', 3
;WITH cte AS (
SELECT a.i, a.Depth, a.maxd, a.mind, a.maxc, a.di, a.ci, a.cdi, a.ID, a.y, CAST('' AS varchar(max))z
FROM(
SELECT DISTINCT i = 1
,p.Depth
,maxd = (SELECT MAX(Depth) FROM TableA)
,mind = (SELECT MIN(Depth) FROM TableA)
,maxc = (SELECT MAX(c) FROM (SELECT COUNT(*) OVER(PARTITION BY ParentID) FROM TableA)f(c))
,di = (SELECT MIN(Depth) FROM TableA)
,ci = 1
,cdi = (SELECT MIN(Depth) FROM TableA)
,p.ID
,CAST(p.ID AS varchar(max)) + p.ColA + SPACE(1) + CASE WHEN g IS NULL THEN '' ELSE '(' END
+ ISNULL(g,'') + CASE WHEN g IS NULL THEN '' ELSE ')' END y
FROM TableA p
LEFT JOIN TableA c ON (c.ParentID = p.ID)
CROSS APPLY (SELECT SPACE(1) + CAST(c2.ID AS varchar(max)) + ColA + SPACE(1)
FROM TableA c2 WHERE ParentID = p.ID
ORDER BY Priority
FOR XML PATH(''))f(g)
)a
UNION ALL
SELECT r.i, r.Depth, r.maxd, r.mind, r.maxc, r.di, r.ci, r.cdi, r.ID
,CASE WHEN di = cdi
THEN REPLACE(r.y,LEFT(r.z,CHARINDEX(SPACE(1),r.z,2)), r.z)
ELSE r.y END [y]
,r.z
FROM(
SELECT i = i + 1
,Depth
,[maxd]
,[mind]
,[maxc]
,CASE WHEN ci = maxc AND cdi = maxd
THEN di + 1
ELSE di
END [di]
,CASE WHEN cdi = [maxd]
THEN CASE WHEN ci + 1 > maxc
THEN 1
ELSE ci + 1
END
ELSE ci
END [ci]
,CASE WHEN cdi + 1 > maxd
THEN mind
ELSE cdi + 1
END [cdi]
,id,y
,CAST(ISNULL((SELECT y FROM(
SELECT p.Depth,p.ID
,SPACE(1) + CAST(p.ID AS varchar(max)) + p.ColA + SPACE(1) +
CASE WHEN g IS NULL THEN '' ELSE '(' END + ISNULL(g,'')
+ CASE WHEN g IS NULL THEN '' ELSE ')' END y
,r1 = DENSE_RANK() OVER(ORDER BY p.ID) --child number
,r2 = ROW_NUMBER() OVER(PARTITION BY p.ID ORDER BY p.ID) --DISTINCT not allowed in recursive section
FROM TableA p
JOIN TableA c ON (c.ParentID = p.ID)
CROSS APPLY (SELECT SPACE(1)+CAST(c2.ID AS varchar(max))+ColA+SPACE(1)
FROM TableA c2
WHERE ParentID = p.ID
ORDER BY Priority
FOR XML PATH(''))f(g)
WHERE p.Depth = cdi AND cdi < di AND p.ID <> cte.ID
)v
WHERE r1 = ci
AND r2 = 1
AND cte.y LIKE '%' + LEFT(v.y,CHARINDEX(SPACE(1),v.y,2) ) + '%'),'') AS varchar(max)) z
FROM cte
WHERE [di]<[maxd] or [ci]<[maxc] or [cdi]<[maxd]
)r
)--cte
UPDATE t
SET ColA = REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE
(y,SPACE(1),''),'1',''),'2',''),'3',''),'4',''),'5',''),'6',''),'7',''),'8',''),'9',''),'0','')
FROM cte
JOIN TableA t ON (t.ID = cte.ID)
WHERE di = (SELECT MAX(Depth) FROM TableA)
AND cdi = (SELECT MAX(Depth) FROM TableA)
AND ci = (SELECT MAX(c) FROM (SELECT COUNT(*) OVER(PARTITION BY ParentID) FROM TableA)f(c))
OPTION(maxrecursion 0)
SELECT * FROM TableA
DROP TABLE TableA
JMTyler-
1 What kind of data is in ColA? What does it look like?
2 How is/should that column be originally populated? I ask this because you would only be able to run the update once since the value in that column would be modified from a previous run. Any additional runs would just concatenate more data. Which makes me believe there is another ColC with the original value for ColA (a person's name?)
3 Will a row ever be deleted orphaning it's children? If yes what should their ParentColB then point to? NULL? Does their depth then get set to 0 so they are now at the top of the hierarchy?
If you can answer this I can give you a solution
Thanks