Sql LEFT OUTER JOIN with WHERE clause - sql-server

I have two tables:
Request:
RequestID | Msg
----------------
5 | abc
6 | def
7 | ghi
8 | jkl
RequestStatus:
RequestStatusID | RequestID |StatusID
-------------------------------------
1 5 1
2 8 2
Not every request has a record in RequestStatus
I need all the records from table Request except when StatusID = 2. (requestID=8 should be filter-out)
I am using LEFT OUTER JOIN to recieve the records from table Request but when I am adding Where clause (Where StatusID = 1) of course it does not work.

Move the constraint to your on clause.
select *
from request r
left join requestStatus rs
on r.requestID = rs.requestID
--and status_id = 1
and status_id <> 2
What's happening to you is that the outer join is performed first. Any rows coming from the outer join that don't have matches will have nulls in all the columns. Then your where clause is applied, but since 1 <> null, it's not going to work like you want it to.
EDIT: Changed on clause based on Piyush's comment.

In SQL Server 2017 you can put AND after ON in this way
SELECT * FROM Request
LEFT OUTER JOIN RequestStatus ON Request.RequestID = RequestStatus.RequestID
AND RequestStatus.StatusID <> 2
WHERE Request.RequestID IS NOT NULL

Try this
SELECT *
FROM Request R
LEFT JOIN RequestStatus RS ON R.RequestID = RS.RequestID
WHERE RS.StatusID <> 2 OR RS.RequestID IS NULL
SQL FIDDLE

The answer is pretty simple, use left join but filter on the rows that have StatusId different than 2, like so:
select *
from request r
left join requestStatus rs
on r.requestID = rs.requestID
where rs.StatusId <> 2 or rs.StatusId IS NULL
EDIT: added or rs.StatusId IS NULL additional condition to also include rows in requestStatus table that don't have a match in request table.

You need use an NOT IN Query Statement like this
Select *
From Request
Where RequestID not in
(Select RequestID
From RequestStatus
Where StatusID = 2)
or
Select *
From Request r
left join requestStatus rs on r.requestID = rs.requestID
Where r.RequestID not in
(Select RequestID
From RequestStatus
Where StatusID = 2)

SqlFiddle
This answer assumes you just want the RequestId & Msg from the Request Table where there is not a record in the RequestStatus table with that RequestId and a StatusId of 2.
You won't get extra records for Requests with multiple RequestStatus records using this query either (vs Left join).
Using the not exists clause is faster than Except, Not In, Outer Apply, etc in this article by Aaron Bertrand - Should I use NOT IN, OUTER APPLY, LEFT OUTER JOIN, EXCEPT, or NOT EXISTS?
select r.RequestId, r.Msg
from Request r
where not exists (
select 1
from RequestStatus rs
where rs.StatusId = 2
and rs.RequestId = r.RequestId
)

Related

How can I join a column on a table using a variable as the column name without using dynamic sql

I am trying to figure out how I can join on a table column where the join column will sometimes defer. There is dozens of columns in the table. I am trying to do this without dynamic sql.
Here is an example table to demonstrate what I mean:
Table
-------------------------------------------------
ID | ObjectID_6 | ObjectId_9 | ObjectId_10 |
-------------------------------------------------
1 | 1 | 23 | 55 |
-------------------------------------------------
For example lets say I obtain the column name to join on and set it as a paramater
#columnName VARCHAR(200)= CONCAT('ObjectID_',countNr) --Value is now ObjectId_9
SQL Query I have tried:
SELECT tmpName.*
FROM tblName
JOIN tblObjectTable
ON tblOjbectTable.#columnName = tmpName.ObjectID
This is not a great problem to solve and outside of dynamic sql your options are slim. The example below is a SQL Server solution, however, with minor tweaking the same construct can be performed in mysql.
One try would be to left join for each possible combination and flag which join is true. In this case, you also have to have branch logic to pick the correct value among from the proper join. If you do this you certainly want to test it against your largest dataset and read a little about parameter sniffing in SQL Server. I would attempt to avoid these types of things if at all possible as they result in non-performant queries. It would not be a bad idea to compare this to the dynamic sql alternative on larger datasets.
DECLARE #ColumnNumber INT = 5
SELECT
tmpName.*,
ColumnValue =
CASE
WHEN #ColumnNumber = 1 THEN T1.Column1
WHEN #ColumnNumber = 2 THEN T2.Column2
WHEN #ColumnNumber = 3 THEN T3.Column3
WHEN #ColumnNumber = 4 THEN T4.Column4
WHEN #ColumnNumber = 5 THEN T5.Column5
ELSE
NULL
END
FROM
tblName
LEFT JOIN tblObjectTable T1 ON T1.Column1 = tmpName.ObjectID AND #ColumnNUmber = 1
LEFT JOIN tblObjectTable T2 ON T2.Column2 = tmpName.ObjectID AND #ColumnNUmber = 2
LEFT JOIN tblObjectTable T3 ON T3.Column3 = tmpName.ObjectID AND #ColumnNUmber = 3
LEFT JOIN tblObjectTable T4 ON T4.Column4 = tmpName.ObjectID AND #ColumnNUmber = 4
LEFT JOIN tblObjectTable T5 ON T5.Column5 = tmpName.ObjectID AND #ColumnNUmber = 5

SQL Join that only returns records that are missing in other table

Consider two tables in a SQL Server database:
Order
OrderId | OrderType | etc...
Shipment
ShipmentId | OrderId | ShipType | etc..
I'm trying to write a select statement that returns OrderIds :
where Order.OrderType = 'EXCHANGE'
and there is no corresponding record in the Shipment table that has
Shipment.OrderId = Order.OrderId and Shipment.ShipType = 'BOX'
The reason for this is that depending on the OrderType, a BOX may or may not to be shipped to the customer. I want to grab all the OrderIds that are of OrderType = 'EXCHANGE' but have no corresponding BOX shipment so they can be forwarded to be shipped.
You can use not exists:
select o.*
from order o
where o.OrderType = 'EXCHANGE' and
not exists (select 1
from shipment s
where s.OrderId = o.OrderId and s.ShipType = 'BOX'
);
Note: As written this won't work because order is a SQL keyword and reserved word -- that makes it a very poor choice of table name (you can escape the name, although you do not in the question). I would suggest you call it something else, such as orders.
You could use LEFT JOIN.
SELECT
o.*
FROM Order o
LEFT JOIN Shipment s ON s.OrderId = o.OrderId AND s.ShipType = 'BOX'
WHERE
o.OrderType = 'EXCHANGE'
AND s.ShipmentId IS NULL

Update records SQL?

First when I started this project seemed very simple. Two tables, field tbl1_USERMASTERID in Table 1 should be update from field tbl2_USERMASTERID Table 2. After I looked deeply in Table 2, there is no unique ID that I can use as a key to join these two tables. Only way to match the records from Table 1 and Table 2 is based on FIRST_NAME, LAST_NAME AND DOB. So I have to find records in Table 1 where:
tbl1_FIRST_NAME equals tbl2_FIRST_NAME
AND
tbl1_LAST_NAME equals tbl2_LAST_NAME
AND
tbl1_DOB equals tbl2_DOB
and then update USERMASTERID field. I was afraid that this can cause some duplicates and some users will end up with USERMASTERID that does not belong to them. So if I find more than one record based on first,last name and dob those records would not be updated. I would like just to skip and leave them blank. That way I wouldn't populate invalid USERMASTERID. I'm not sure what is the best way to approach this problem, should I use SQL or ColdFusion (my server side language)? Also how to detect more than one matching record?
Here is what I have so far:
UPDATE Table1 AS tbl1
LEFT OUTER JOIN Table2 AS tbl2
ON tbl1.dob = tbl2.dob
AND tbl1.fname = tbl2.fname
AND tbl1.lname = tbl2.lname
SET tbl1.usermasterid = tbl2.usermasterid
WHERE LTRIM(RTRIM(tbl1.usermasterid)) = ''
Here is query where I tried to detect duplicates:
SELECT DISTINCT
tbl1.FName,
tbl1.LName,
tbl1.dob,
COUNT(*) AS count
FROM Table1 AS tbl1
LEFT OUTER JOIN Table2 AS tbl2
ON tbl1.dob = tbl2.dob
AND tbl1.FName = tbl2.first
AND tbl1.LName = tbl2.last
WHERE LTRIM(RTRIM(tbl1.usermasterid)) = ''
AND LTRIM(RTRIM(tbl1.first)) <> ''
AND LTRIM(RTRIM(tbl1.last)) <> ''
AND LTRIM(RTRIM(tbl1.dob)) <> ''
GROUP BY tbl1.FName,tbl1.LName,tbl1.dob
Some data after I tested query above:
First Last DOB Count
John Cook 2008-07-11 2
Kate Witt 2013-06-05 1
Deb Ruis 2016-01-22 1
Mike Bennet 2007-01-15 1
Kristy Cruz 1997-10-20 1
Colin Jones 2011-10-13 1
Kevin Smith 2010-02-24 1
Corey Bruce 2008-04-11 1
Shawn Maiers 2016-08-28 1
Alenn Fitchner 1998-05-17 1
If anyone have idea how I can prevent/skip updating duplicate records or how to improve this query please let me know. Thank you.
You could check for and avoid duplicate matches using with common_table_expression (Transact-SQL)
along with row_number()., like so:
with cte as (
select
t.fname
, t.lname
, t.dob
, t.usermasterid
, NewUserMasterId = t2.usermasterid
, rn = row_number() over (partition by t.fname, t.lname, t.dob order by t2.usermasterid)
from table1 as t
inner join table2 as t2 on t.dob = t2.dob
and t.fname = t2.fname
and t.lname = t2.lname
and ltrim(rtrim(t.usermasterid)) = ''
)
--/* confirm these are the rows you want updated
select *
from cte as t
where t.NewUserMasterId != ''
and not exists (
select 1
from cte as i
where t.dob = i.dob
and t.fname = i.fname
and t.lname = i.lname
and i.rn>1
);
--*/
/* update those where only 1 usermasterid matches this record
update t
set t.usermasterid = t.NewUserMasterId
from cte as t
where t.NewUserMasterId != ''
and not exists (
select 1
from cte as i
where t.dob = i.dob
and t.fname = i.fname
and t.lname = i.lname
and i.rn>1
);
--*/
I use the cte to extract out the sub query for readability. Per the documentation, a common table expression (cte):
Specifies a temporary named result set, known as a common table expression (CTE). This is derived from a simple query and defined within the execution scope of a single SELECT, INSERT, UPDATE, or DELETE statement.
Using row_number() to assign a number for each row, starting at 1 for each partition of t.fname, t.lname, t.dob. Having those numbered allows us to check for the existence of duplicates with the not exists() clause with ... and i.rn>1
You could use a CTE to filter out the duplicates from Table1 before joining:
; with CTE as (select *
, count(ID) over (partition by LastName, FirstName, DoB) as IDs
from Table1)
update a
set a.ID = b.ID
from Table2 a
left join CTE b
on a.FirstName = b.FirstName
and a.LastName = b.LastName
and a.Dob = b.Dob
and b.IDs = 1
This will work provided there are no exact duplicates (same demographics and same ID) in table 1. If there are exact duplicates, they will also be excluded from the join, but you can filter them out before the CTE to avoid this.
Please try below SQL:
UPDATE Table1 AS tbl1
INNER JOIN Table2 AS tbl2
ON tbl1.dob = tbl2.dob
AND tbl1.fname = tbl2.fname
AND tbl1.lname = tbl2.lname
LEFT JOIN Table2 AS tbl3
ON tbl3.dob = tbl2.dob
AND tbl3.fname = tbl2.fname
AND tbl3.lname = tbl2.lname
AND tbl3.usermasterid <> tbl2.usermasterid
SET tbl1.usermasterid = tbl2.usermasterid
WHERE LTRIM(RTRIM(tbl1.usermasterid)) = ''
AND tbl3.usermasterid is null

T-SQL stored procedure with joins

I have a problem with a stored procedure. I have 3 tables for a mass mailing service and I want to know how many tasks (table - MMProcessItem) I still need to do...
I have these 3 tables:
Here is my select:
SELECT
MMAddress.AddressID, MMProcess.ProcessID
FROM
MMProcess, MMAddress
LEFT OUTER JOIN
(SELECT *
FROM MMProcessItem) Items ON Items.AddressID = MMAddress.AddressID
WHERE
Items.ResultID IS NULL
ORDER BY
ProcessID, AddressID
And my SQL Code is working fine if there is nothing in MMProcessItem table, this is what I get:
But if I send 1 email, like the one with AddressID = 1 and ProcessID = 1, I don't get anymore the 1 record with AddressID = 1 and ProcessID = 2, I should get a total of 3 records, but what i get is a total of 2 records...
Sorry if this is an amateur mistake, im not used to work with t-sql and do these type of things...
Your join to MMProcessItem requires two predicates, one to join to MMProcess, and one to join to MMAddress. You are currently only joining to MMAddress. That means that when you add a record with AddressID = 1 and ProcessID = 1 it removes both records where AddressID = 1, not just the one record where AddressID is 1 and ProcessID is 1.
You could rewrite your query as:
SELECT a.AddressID, p.ProcessID
FROM MMProcess AS p
CROSS JOIN MMAddress AS a
LEFT OUTER JOIN MMProcessItem AS i
ON i.AddressID = a.AddressID
AND i.ProcessID = p.ProcessID
WHERE i.ResultID IS NULL
ORDER BY p.ProcessID, a.AddressID;
Note the use of explicit join syntax, and also aliases for brevity
Since you are using the LEFT JOIN to MMProcessItem solely for the reason of removing records, then you might find that using NOT EXISTS conveys intention better, but more importantly, it can also perform better.
SELECT a.AddressID, p.ProcessID
FROM MMProcess AS p
CROSS JOIN MMAddress AS a
WHERE NOT EXISTS
( SELECT 1
FROM MMProcessItem AS i
WHERE i.AddressID = a.AddressID
AND i.ProcessID = p.ProcessID
)
ORDER BY p.ProcessID, a.AddressID;

How to select distinct values after left outer join operation

I want to select some values from three tables with aggregate function but without duplicates in one of the columns, for example:
select t3.ValueDesc as FeatureType,
count(t2.Strategic) as TotalCount
,t2.RequestID,t1.StoryID --these are not needed, but put for better vision
from tblRequests t2
left outer join (select * from tblAgileMultiDD where Type=18) t3
on t3.FormulaValue = t2.Strategic
left outer join tblAgileStory t1
on t1.Feature = t2.RequestID
where t2.RequestID > 0
and t1.DemoStatus = 1
group by t3.ValueDesc
,t2.RequestID, t1.StoryID --these are not needed but put for better vision
order by t3.ValueDesc
And then it returns me something like this:
FeatureType TotalCount RequestID StoryID
Protect Base 1 311 1629
Protect Base 1 311 1630
Protect Base 1 312 1631
Protect Base 1 312 1637
New Market 1 313 1640
New Market 1 313 1645
And if I comment out lines with ",t2.RequestID, t1.StoryID", it gives me the result:
FeatureType TotalCount
Protect Base 4
New Market 2
So, for each unique combination of RequestID and StoryID it returns new row. How to make it return new row only for each unique RequestID regardless to StoryID?
So I want this query to result like this:
FeatureType TotalCount
Protect Base 2 (for RequestID = 311, 312)
New Market 1 (for RequestID = 313)
Putting word "distinct" at the beginning doesn't take effect on it.
Can you help me with this?
select distinct FeatureType,TotalCount from (
select t3.ValueDesc as FeatureType,
count(t2.Strategic) as TotalCount
,t2.RequestID
-- ,t1.StoryID --these are not needed, but put for better vision
from tblRequests t2
left outer join (select * from tblAgileMultiDD where Type=18) t3
on t3.FormulaValue = t2.Strategic
left outer join tblAgileStory t1
on t1.Feature = t2.RequestID
where t2.RequestID > 0
and t1.DemoStatus = 1
group by t3.ValueDesc
,t2.RequestID
-- , t1.StoryID --these are not needed but put for better vision
) as T
order by t3.ValueDesc
could you try this.

Resources