PostgreSql query becomes slow when adding where condition - sql

This query has so many joins and i have to apply conditions on joined table columns to get the desired results but query becomes slow when i apply condition on a datetime column.
Here is the query
select
distinct v0_.id as id_0,
MIN(v4_.price) as sclr_4
from
venue v0_
left join facility f5_ on
v0_.id = f5_.venue_id
and (f5_.deleted_at is null)
left join sport_facility_types s6_ on
f5_.id = s6_.facility_id
left join taxonomy_term t7_ on
s6_.sport_id = t7_.id
and (t7_.deleted_at is null)
left join term t8_ on
t7_.term_id = t8_.id
left join sport_facility_types_taxonomy_term s10_ on
s6_.id = s10_.sport_facility_types_id
left join taxonomy_term t9_ on
t9_.id = s10_.taxonomy_term_id
and (t9_.deleted_at is null)
left join term t11_ on
t9_.term_id = t11_.id
left join facility_venue_item_price f12_ on
f5_.id = f12_.facility_id
left join venue_item_price v4_ on
f12_.venue_item_price_id = v4_.id
left join calendar_entry c13_ on
v4_.calendar_entry_id = c13_.id
where
(v0_.status = 'active'
and f5_.status = 'active')
and (v0_.deleted_at is null)
and c13_.start_at >= '2022-10-21 19:00:00' --- this slows down the query
group by
v0_.id
And here is the query plan https://explain.dalibo.com/plan/46h0fb3343e246a5.
The query plan is so big that i cannot paste it here
Plain query plan https://explain.depesz.com/s/7qnD
Plain query plan without where condition https://explain.depesz.com/s/3sK3
The query shouldn't take much time as there are not many rows in tables.
calendar_entry table has ~350000 rows
venue_item_price table has also ~320000 rows

Your WHERE condition turns all the outer joins into inner joins (because c13_.start_at cannot be NULL any more), which changes the whole game. Usually that is an advantage, because it gives the optimizer more freedom, but that seems to have worked in the wrong direction in your case. One reason for that may be that you join more than 8 tables, and the default value for join_collapse_limit is 8.
That's pretty much all I can say without a readable execution plan.

When you don't reference c13_ anywhere in the output or WHERE clause, it can just skip doing that join altogether, because it can't change anything. That is, the join is (presumably) on a unique key, so it can't cause multiple rows to be returned; and it is a left join, so it can't cause zero rows to be returned.
Once you reference it in the WHERE, however, the join has to be executed. That join only contributes about half the time, so it isn't like the query was blazingly fast to start with.

Related

SQL Server Query - Weird Behaviour

Consider below SQL.
SELECT DISTINCT bvc_Order.ID,
bvc_OrderItem.ProductID,
bvc_OrderItem_BundleItem.ProductID
FROM dbo.bvc_OrderItem WITH (nolock)
RIGHT OUTER JOIN dbo.bvc_Order WITH (nolock)
LEFT OUTER JOIN dbo.bvc_User WITH (nolock) ON dbo.bvc_Order.UserID = dbo.bvc_User.ID
LEFT OUTER JOIN dbo.Amazon_Merchants WITH (nolock) ON dbo.bvc_Order.CompanyID = dbo.Amazon_Merchants.ID ON dbo.bvc_OrderItem.OrderID = dbo.bvc_Order.ID
LEFT OUTER JOIN dbo.bvc_OrderItem_BundleItem WITH (nolock) ON dbo.bvc_OrderItem.ID = dbo.bvc_OrderItem_BundleItem.OrderItemID
LEFT OUTER JOIN dbo.bvc_Product WITH (nolock) ON dbo.bvc_OrderItem.ProductID = dbo.bvc_Product.ID
WHERE 1=1
AND (bvc_Order.StatusCode <> 1
AND bvc_Order.StatusCode <> 999)
AND ( bvc_OrderItem.ProductID IN ('28046_00')
OR bvc_OrderItem_BundleItem.ProductID IN ('28046_00'))
AND bvc_Order.OrderSource = 56;
The query when I execute against my database, it returns 85 rows. Well, that is not correct.
If I just remove the part "AND bvc_Order.OrderSource = 56" it returns back 5 rows which is really correct.
Strange.....
Another thing, if I remove the part
OR bvc_OrderItem_BundleItem.ProductID IN ('28046_00')
it will also return the 5 rows as expected even with bvc_Order.OrderSource filter.
I am not sure why it is adding more rows while I am trying to reduce rows by using filters.
the table bvc_OrderItem_BundleItem doesn't contain any rows for the result order ids or OrderItemIDs
[edit]
Thanks guys, I tried to remove the LEFT/RIGHT Join Mix but Query manager doesn't allows only LEFT, it does add at least one RIGHT join. I updated the SQL to remove extra tables and now we have only three. But same result
SELECT DISTINCT dbo.bvc_Order.ID, dbo.bvc_OrderItem.ProductID, dbo.bvc_OrderItem_BundleItem.ProductID AS Expr1
FROM dbo.bvc_OrderItem
LEFT OUTER JOIN dbo.bvc_OrderItem_BundleItem ON dbo.bvc_OrderItem.ID = dbo.bvc_OrderItem_BundleItem.OrderItemId
RIGHT OUTER JOIN dbo.bvc_Order ON dbo.bvc_OrderItem.OrderID = dbo.bvc_Order.ID
WHERE 1=1
AND (bvc_Order.StatusCode <> 1 AND bvc_Order.StatusCode <> 999)
AND (
bvc_OrderItem.ProductID IN ('28046_00')
OR bvc_OrderItem_BundleItem.ProductID IN ('28046_00')
)
AND bvc_Order.OrderSource = 56;
[edit]So far, there is no solution for this. I previously pasted a link in my comment with example data outout for both valid/invalid results with queries. here it is again.
http://sameers.me/SQLIssue.xlsx
One thing to remember here is that ALL left join is not possible. Let me explain further
bvc_Order contains main order record
bvc_ORderItem contains Order Items/Products
bvc_ORderItem_BundleItem contains child products of the product which are available in bvC_OrderItem table.
Now NOT Every product has child products, so bvc_OrderItem_BundleItem may not have any record (and in current scenario, there is really no valid row for the orders in bvC_OrderItem_BundleItem).
In short, in current scenario, there is NO matching row available in bvc_OrderItem_BundleItem table. If I remove that join for now, it is all okay, but in real world, I can't remove that BundleItem table join ofcourse.
thank you
When you say
WHERE bvc_Order.OrderSource = 56
that evaluates to false when bvc_Order.OrderSource is NULL. If the LEFT/RIGHT join failed then it will be NULL. This effectively turns the LEFT/RIGHT join into an inner join.
You probably should write the predicate into the ON clause. An alternative approach, which might not deliver the same results, is:
WHERE (bvc_Order.OrderSource IS NULL OR bvc_Order.OrderSource = 56)
The other predicates have the same problem:
Another thing, if I remove the part OR bvc_OrderItem_BundleItem.ProductID IN ('28046_00') it will also return the 5 rows as expected
When the join fails bvc_OrderItem_BundleItem.ProductID is NULL.
I also would recommend writing queries manually. If I understand you right this query comes from a designer. It's structure is quite confusing. I'm pulling up the most important comment:
Mixing left and right outer joins in a query is just confusing. You should start by rewriting the from clause to only use one type (and I strongly recommend left outer join). – Gordon Linoff
When you have eliminated the impossible, whatever remains, however
improbable, must be the truth? S.H.
It is impossible that an extra AND condition appended to a WHERE clause can ever result in extra rows. That would imply a database engine defect, which I hope I can assume is "impossible". (If not, then I guess it's back to square one).
That fact makes it easier to concentrate on possible reasons:
When you comment out
AND bvc_Order.OrderSource = 56;
then you also comment out the semicolon terminator. Is it possible
that there is text following this query that is affecting it? Try
putting a semicolon at the end of the previous line to make sure.
Depending on the tool you are using to run queries, sometimes when a
query fails to execute, the tool mistakenly shows an old result set.
Make sure your query is executing correctly by adding a dummy column
to the SELECT statement to absolutely prove you are seeing live
results. Which tool are you using?
when you use LEFT outer join it will give all the rows from left table (dbo.bvc_OrderItem) once the your and, or conditions satisfies,
the same thing happens with Right outer join too,
Those conditions (Left join, right join ) may not restrict the rows since rows from one table can be all, another table with some rows only.
check with your join condition
Then check you condition :
(bvc_Order.StatusCode <> 1 AND bvc_Order.StatusCode <> 999)
if any rows satisfying this condition
next check with another condition
[bvc_OrderItem.ProductID IN ('28046_00')
OR bvc_OrderItem_BundleItem.ProductID IN ('28046_00')]
Then bvc_Order.OrderSource = 56
compare the result of three queries and check the data in with the conditions and then write your complete query, so that you will understand where the mistake you have done.
Few points to remember
1.And is applied during Virtual join phases
2.Where clause is applied after the final result
3.Left join followed by right join is effectively an inner join in some cases
Lets break your query step by step..
dbo.bvc_OrderItem a1
LEFT OUTER JOIN
dbo.bvc_OrderItem_BundleItem b1
Above output will be a single virtual table (logically) which contains all rows from b1 with matching rows from a1
now below predicates from your and clause will be applied
bvc_OrderItem.ProductID IN ('28046_00')
OR bvc_OrderItem_BundleItem.ProductID IN ('28046_00')
which effectively eliminates all rows from bvc_OrderItem_BundleItem even if they have matches and gives result some thing like below if bvc_OrderItem_BundleItem.ProductID IN ('28046_00') is true
bvc_OrderItem bvc_OrderItem_BundleItem
28046 28046
null 1
null 2
null 3
if this condition(bvc_OrderItem.ProductID IN ('28046_00')) is true,then you are asking sql to ignore all rows in bvc_OrderItem ,which effectively means the same result set as above
bvc_OrderItem bvc_OrderItem_BundleItem other columns
28046 28046
null 1
null 2
null 3
next you are doing right outer join with dbo.bvc_Order which may qualifies for the join point I mentioned above
Assume ,you got below result set as output which preserves all of bvc_order table(rough output only for understanding due to lack of actual data)
bvc_OrderItem bvc_OrderItem_BundleItem statuscode ordersource
28046 28046 999 56
null 1 1 57
null 2 100 58
null 3 11 59
Next below AND predicates will be applied
status code <>1 and statuscode<> 999
which means ignore rows which match with bvc_order and has status of 1 ,999 even if they found matching rows
Next you are asking bvc_Order.OrderSource = 56; which means I don't care about other rows,preserve matching rows only for 56 and keep the rest as null
Hope this clarifies on what is happening step by step.A more better way can be provide some test data and show the expected output.
you also can control physical order of joins,you can try below to see if this is what you are trying to do..
SELECT DISTINCT dbo.bvc_Order.ID, dbo.bvc_OrderItem.ProductID, dbo.bvc_OrderItem_BundleItem.ProductID AS Expr1
dbo.bvc_OrderItem
LEFT OUTER JOIN
(
dbo.bvc_OrderItem_BundleItem
RIGHT OUTER JOIN
dbo.bvc_Order
ON dbo.bvc_OrderItem.OrderID = dbo.bvc_OrderItem_BundleItem.OrderItemId
)c
on
dbo.bvc_OrderItem.ID = c.bvc_OrderItem_BundleItem.OrderItemId
WHERE 1=1
AND (bvc_Order.StatusCode <> 1 AND bvc_Order.StatusCode <> 999)
AND (
bvc_OrderItem.ProductID IN ('28046_00')
OR bvc_OrderItem_BundleItem.ProductID IN ('28046_00')
)
AND bvc_Order.OrderSource = 56;
It looks like you are using the Query Designer. I would avoid using this as this can make your queries extremely confusing. Your queries will be much more concise if you are designing them by hand. If you don't completely understand how inner/outer joins work, a great textbook that I used to teach myself SQL is Murach's SQL Server for Developers.
https://www.murach.com/shop/murach-s-sql-server-2012-for-developers-detail
Now, onto the answer.
I've been thinking about how to resolve your problem, and if you are trying to reduce the result set to 5 rows, why are you using multiple outer joins in the first place? I would consider switching the joins to inner joins instead of outer joins if you are looking for a very specific result set. I can't really provide you with a really comprehensive answer without looking at exactly what results you are trying to achieve, but here's a general idea based on what you've provided to all of us:
SELECT DISTINCT dbo.bvc_Order.ID, dbo.bvc_OrderItem.ProductID, dbo.bvc_OrderItem_BundleItem.ProductID AS 'bvc_OrderItem_BundleItem_ProductID'
FROM dbo.bvc_OrderItem
INNER JOIN dbo.bvc_OrderItem_BundleItem ON dbo.bvc_OrderItem.ID = dbo.bvc_OrderItem_BundleItem.OrderItemId
INNER JOIN dbo.bvc_Order ON dbo.bvc_OrderItem.OrderID = dbo.bvc_Order.ID
Start here and then based upon what you are searching for, add where clauses to filter criteria.
Also, your where clause must be rewritten if you use an inner join instead of an outer join:
WHERE 1=1 --not really sure why this is here. This will always be true. Omit this statement to avoid a bad result set.
AND (bvc_Order.StatusCode <> 1 AND bvc_Order.StatusCode <> 999) --this is saying, if the StatusCode is not equal to 1 and not equal to 999, don't include it.
--Revised: Look for Status codes with 1 or 999
--bvc_Order.StatusCode = 1 OR bvc_Order.StatusCode = 999
AND (bvc_OrderItem.ProductID IN ('28046_00') --I would eliminate this unless you are looking to see if this exists in Product ID. You could also accomplish this if you are trying to see if this value is in both tables, change this to:
bvc_OrderItem.ProductID = '28046_00' AND bvc_OrderItem_BundleItem.ProductID = '28046_00')
--if you are trying to see if the order source is 56, use this.
AND bvc_Order.OrderSource = 56;
If you are trying to find out rows that are not included in this result set, then I would use OUTER JOIN as necessary (LEFT preferred). Without more information about what you're looking for in your database, that's the best all of us can do.
bLike #usr writ, the reason of this unexpected (for you) result is, you build query with outer joins, and filter rows after join. If you need filter rows of outer joined tables, you should do this before join.
but probably you try build this:
SELECT DISTINCT o.ID, oi.ProductID, bi.ProductID AS Expr1
FROM dbo.bvc_Order as o
LEFT JOIN dbo.bvc_OrderItem as oi on oi.OrderID = o.ID
LEFT JOIN dbo.bvc_OrderItem_BundleItem as bi ON oi.ID = bi.OrderItemId
WHERE 1=1
AND o.OrderSource = 56;
AND o.StatusCode not in (1, 999)
AND '28046_00' in (oi.ProductID, isnull(bi.ProductID,'_') )
Is this query give results what you need?
if not, try change last condition, for example:
and (bi.ProductID = '28046_00' or bi.ProductID is null and oi.ProductID = '28046_00')
you can also put additional condition in to join conditions, for example:
SELECT DISTINCT o.ID, oi.ProductID, bi.ProductID AS Expr1
FROM dbo.bvc_Order as o
LEFT JOIN dbo.bvc_OrderItem as oi on oi.OrderID = o.ID
LEFT JOIN dbo.bvc_OrderItem_BundleItem as bi ON oi.ID = bi.OrderItemId
and bi.ProductID in ('28046_00') --this join BundleItem only if ...
WHERE 1=1
AND o.OrderSource = 56;
AND o.StatusCode not in (1, 999)
AND (oi.ProductID in ('28046_00') or bi.ProductID is not null)
ah, and if you always need join bvc_Order with bvc_OrderItem then use inner join

LEFT JOIN WHERE RIGHT IS NULL for same table in Teradata SQL

I have a table with 51 records . The table structure looks something like below :
ack_extract_id query_id cnst_giftran_key field1 value1
Now ack_extract_ids can be 8,9.
I want to check for giftran keys which are there for extract_id 9 and not there in 8.
What I tried was
SELECT *
FROM ddcoe_tbls.ack_flextable ack_flextable1
INNER JOIN ddcoe_tbls.ack_main_config config
ON ack_flextable1.ack_extract_id = config.ack_extract_id
LEFT JOIN ddcoe_tbls.ack_flextable ack_flextable2
ON ack_flextable1.cnst_giftran_key = ack_flextable2.cnst_giftran_key
WHERE ack_flextable2.cnst_giftran_key IS NULL
AND config.ack_extract_file_nm LIKE '%Dtl%'
AND ack_flextable2.ack_extract_id = 8
AND ack_flextable1.ack_extract_id = 9
But it is returning me 0 records. Ideally the left join where right is null should have returned the record for which the cnst_giftran_key is not present in the right hand side table, right ?
What am I missing here ?
When you test columns from the left-joined table in the where clause (ack_flextable2.ack_extract_id in your case), you force that join to behave as if it were an inner join. Instead, move that test to be part of the join condition.
Then to find records where that value is missing, test for a NULL key in the where clause.
SELECT *
FROM ddcoe_tbls.ack_flextable ack_flextable1
INNER JOIN ddcoe_tbls.ack_main_config config
ON ack_flextable1.ack_extract_id = config.ack_extract_id
LEFT JOIN ddcoe_tbls.ack_flextable ack_flextable2
ON ack_flextable1.cnst_giftran_key = ack_flextable2.cnst_giftran_key
AND ack_flextable2.ack_extract_id = 8
WHERE ack_flextable2.cnst_giftran_key IS NULL
AND config.ack_extract_file_nm LIKE '%Dtl%'
AND ack_flextable1.ack_extract_id = 9
AND ack_flextable2.cnst_giftran_key IS NULL
THIS IS NO ANSWER, JUST AN EXPLANATION
From your comment to Joe Stefanelli's answer I gather that you don't fully understand the issue with WHERE and ON in an outer join. So let's look at an example.
We are looking for all supplier's last orders, i.e. the order records where there is no newer order for the supplier.
select *
from order
where not exists
(
select *
from order newer
where newer.supplier = order.supplier
and newer.orderdate > order.orderdate
);
This is straight-forward; the query matches what we just put in words: Find orders for which NOT EXISTS a newer order for the same supplier.
The same query with the anti-join pattern:
select order.*
from order
left join order newer on newer.supplier = order.supplier
and newer.orderdate > order.orderdate
where newer.id is null;
Here we join every order with all their newer orders, thus probably creating a huge intermediate result. With the left outer join we make sure we get a dummy record attached when there is no newer order for the supplier. Then at last we scan the intermediate result with the WHERE clause, keeping only records where the attached record has an ID null. Well, the ID is obviously the table's primary key and can never be null, so what we keep here is only the outer-joined results where the newer data is just a dummy record containing nulls. Thus we get exactly the orders for which no newer order exists.
Talking about a huge intermediate result: How can this be faster than the first query? Well, it shouldn't. The first query should actually either run equally fast or faster. A good DBMS will see through this and make the same execution plan for both queries. A rather young DBMS however may really execute the anti join quicker. That is because the developers put so much effort into join techniques, as these are needed in about every query, and didn't yet care about IN and EXISTS that much. In such a case one may run into performance issues with NOT IN or NOT EXISTS and use the anti-join pattern instead.
Now as to the WHERE / ON problem:
select order.*
from order
left join order newer on newer.orderdate > order.orderdate
where newer.supplier = order.supplier
and newer.id is null;
This looks almost the same as before, but some criteria has moved from ON to WHERE. This means the outer join gets different criteria. Here is what happens: for every order find all newer orders &dash; no matter which supplier! So it is all orders of the last order date that get an outer-join dummy record. But then in the WHERE clause we remove all pairs where the supplier doesn't match. Notice that the outer-joined records contain NULL for newer.supplier, so newer.supplier = order.supplier is never true for them; they get removed. But then, if we remove all outer-joined records we get exactly the same result as with a vanilla inner join. When we put outer join criteria in the WHERE clause we turn the outer join into an inner join. So the query can be re-written as
select order.*
from order
inner join order newer on newer.orderdate > order.orderdate
where newer.supplier = order.supplier
and newer.id is null;
And with tables in FROM and INNER JOIN it doesn't matter whether the criteria is in ON or WHERE; it's rather a matter of readability, because both criteria will equally get applied.
Now we see that newer.id is null can never be true. The final result will be empty &dash; which is exactly what happened with your query.
You can try with this query:
select * from ddcoe_tbls.ack_main_config
where cnst_giftran_key not in
(
select cnst_giftran_key from ddcoe_tbls.ack_main_config
where ack_extract_id = 8
)
and ack_extract_id = 9;

Join ON order in SQL

I have a question which is similar but not quite the same to others I've seen in on stackoverflow
Let's say I have the following query
select *
from BobsFriends bf
left outer join JennysFriends jf on bf.friendID = jf.friendID
does bf.friendID = jf.friendID and jf.friendID = bf.friendID return different results? I would think that these would return the same result but
select *
from JennysFriends bf
left outer join BobsFriends jf on bf.friendID = jf.friendID
would return different ones.
The order of the columns when comparing for the JOIN doesn't matter, but when doing an OUTER JOIN the order of the tables definitely matters.
For a LEFT OUTER JOIN you are going to always get the results from the first table, with data from the second table, if available.
For a RIGHT OUTER JOIN you are always going to get the results from the second table, with data from the first table, if available.
Left outer join produces a complete set of records from Table A (the "left side" table), with the matching records (where available) in Table B (the "right side" one). If there is no match, the right side will contain null.

sql left join criteria in join vs where clause

I have an existing query to select some payments
I want to filter out any payments that are for clients that have an active alert in another table called ClientAlert
So I figured I would do a left join and check if the ClientAlertId is null.
select *
from payments p
left join client c on c.clientid = p.clientid
left join ClientAlert ca on ca.CRMId = c.CRMId and ca.ClientAlertSubjectId = 1 and ca.IsActive = 1 and (ca.ExpiryDate is null or ca.ExpiryDate > GetDate())
where
ca.clientalertid is null and
p.PaymentStatusId = 2 and
p.PaymentDate <= GetDate() and
p.PaymentCategoryId = 1
This seems to work I think
But I have two questions:
Could there ever be a scenario that would cause multiple payments to be returned instead of one by adding this join?
when I specified the following in the where clause instead of the join, it did not give the same results and I dont understand why
and ca.ClientAlertSubjectId = 1 and ca.IsActive = 1 and (ca.ExpiryDate is null or ExpiryDate > GetDate())
I thought having that criteria in the where clause woiuld be equivelent to having it in the join
You could get multiples per payment record if it links to more than one Client record. Based on the WHERE clause though, I don't see how multiple ClientAlert records could cause duplication.
LEFT JOIN records return NULLs across all their columns when there is no match. Adding ca.ClientAlertSubjectId = 1 and ca.IsActive = 1 to the WHERE clause basically forces that join to behave like an INNER JOIN because it would HAVE to find a matching record, but I'm guessing it would never return data because ClientAlertId is a non-nullable column. So basically you created a query where you need a NULL row (indicating there are no alerts), but the row must contain data.
If they can have multiple alerts, theoretically. However since you are excluding payments with alerts, this should not be a problem. If you were including them it could be. If this was a problem, you should use a "not in" subquery instead of left outer join since that can cause duplicate records if it's not 1:1.
Having criteria in the where clause excludes the entire row if it doesn't match the criteria. Having it in the join clause means the joined record is not shown but the "parent" is.
select *
from payments p
left join client c on c.clientid = p.clientid
left join ClientAlert ca on ca.CRMId = c.CRMId
and ca.ClientAlertSubjectId = 1 and ca.IsActive = 1
and (ca.ExpiryDate is null or ca.ExpiryDate > GetDate())
where
ca.CRMId is null and
p.PaymentStatusId = 2 and
p.PaymentDate <= GetDate() and
p.PaymentCategoryId = 1
This tiny change will assure you will never get any duplicates IF clientid is unique in table client
Moving conditions from left join to where means you are moving the left join condition into a condition for the row. So if the condition is not met, the row is not returned.
Left join condition will always include the row from left side of the join
First, unless there are payments that aren't associated with a client, then the first join should be an inner join.
Second, once a client has the proper alert, you filter out all payments made by that client, even those made months or years before the alert took effect. Is that what you want?
Third and last (though this is a long one): an outer join has this format:
select ...
from InnerTable i
left [outer] join OuterTable o
on <join criteria>
where <filter criteria>;
For outer joins only, it is important that all checks involving the outer table should be consider as join criteria. At a bear minimum, there will be
on o.JoinField = i.OtherJoinField
or in your case
on ca.CRMId = c.CRMId
To keep the output that is expected of an outer join, checks of other outer table fields should also go in the ON clause as additional join criteria:
on ca.CRMId = c.CRMId
and ca.ClientAlertSubjectId = 1
and ca.IsActive = 1
and (ca.ExpiryDate is null or ca.ExpiryDate > GetDate())
Adding those addition checks to the WHERE clause will, as you discovered, completely filter out all the benefits of the outer join to deliver the same result set as an inner join.
However, you could get the output to return to the outer join result set by making one little change:
where (ca.clientalertid is null
or (ca.ClientAlertSubjectId = 1
and ca.IsActive = 1
and (ca.ExpiryDate is null or ca.ExpiryDate > GetDate()))
and p.PaymentStatusId = 2
and ...
But this doesn't quite get back to a normal outer join result set. In your case, the regular outer join will show payment and client data with NULLs for alert data for:
Payments by clients with no alerts at all.
Payments by clients with alerts but not fully qualified by the additional criteria. For example, if IsActive contains 0.
Then there would be alert data for those payments by clients who have a fully qualified alert.
Moving the additional criteria to the WHERE clause but in the format shown above will show payment and client data with NULLs for alert data only for payments with no alerts at all, along with alert data for fully qualified alerts. The payments and clients with alerts not fully qualified would not show up at all.
Remember that. There may be times when that is exactly what you want.
But that does not appear to be what you want at this time. If you want to see no alerts and partial alerts but filter out the fully qualified alerts, you can't place the other criteria in the WHERE clause. Your original query is the only way to do that.
There really is no reason to be placing the additional criteria in the WHERE clause. It gets you nothing. There is no performance benefit to be gained from it even for the inner join where it doesn't effect the output. Compare the execution plan for the inner joins with the additional criteria in the ON clause and in the WHERE clause. They are identical. The same comparison for outer joins are different. In fact, with the addition criteria in the WHERE clause, the execution plan is identical to those of the inner joins.
So just use the query you have and be satisfied that you are getting the output you want.

LEFT INNER JOIN vs. LEFT OUTER JOIN - Why does the OUTER take longer?

We have the query below. Using a LEFT OUTER join takes 9 seconds to execute. Changing the LEFT OUTER to an LEFT INNER reduces the execution time to 2 seconds, and the same number of rows are returned. Since the same number of rows from the dbo.Accepts table are being processed, regardless of the join type, why would the outer take 3x longer?
SELECT CONVERT(varchar, a.ReadTime, 101) as ReadDate,
a.SubID,
a.PlantID,
a.Unit as UnitID,
a.SubAssembly,
m.Lot
FROM dbo.Accepts a WITH (NOLOCK)
LEFT OUTER Join dbo.Marker m WITH (NOLOCK) ON m.SubID = a.SubID
WHERE a.LastModifiedTime BETWEEN #LastModifiedTimeStart AND #LastModifiedTimeEnd
AND a.SubAssembly = '400'
The fact that the same number of rows is returned is an after fact, the query optimizer cannot know in advance that every row in Accepts has a matching row in Marker, can it?
If you join two tables A and B, say A has 1 million rows and B has 1 row. If you say A LEFT INNER JOIN B it means only rows that match both A and B can result, so the query plan is free to scan B first, then use an index to do a range scan in A, and perhaps return 10 rows. But if you say A LEFT OUTER JOIN B then at least all rows in A have to be returned, so the plan must scan everything in A no matter what it finds in B. By using an OUTER join you are eliminating one possible optimization.
If you do know that every row in Accepts will have a match in Marker, then why not declare a foreign key to enforce this? The optimizer will see the constraint, and if is trusted, will take it into account in the plan.
1) in a query window in SQL Server Management Studio, run the command:
SET SHOWPLAN_ALL ON
2) run your slow query
3) your query will not run, but the execution plan will be returned. store this output
4) run your fast version of the query
5) your query will not run, but the execution plan will be returned. store this output
6) compare the slow query version output to the fast query version output.
7) if you still don't know why one is slower, post both outputs in your question (edit it) and someone here can help from there.
This is because the LEFT OUTER Join is doing more work than an INNER Join BEFORE sending the results back.
The Inner Join looks for all records where the ON statement is true (So when it creates a new table, it only puts in records that match the m.SubID = a.SubID). Then it compares those results to your WHERE statement (Your last modified time).
The Left Outer Join...Takes all of the records in your first table. If the ON statement is not true (m.SubID does not equal a.SubID), it simply NULLS the values in the second table's column for that recordset.
The reason you get the same number of results at the end is probably coincidence due to the WHERE clause that happens AFTER all of the copying of records.
Join (SQL) Wikipedia
Wait -- did you actually mean that "the same number of rows ... are being processed" or that "the same number of rows are being returned"? In general, the outer join would process many more rows, including those for which there is no match, even if it returns the same number of records.