Find First Match - sql

I have two tables that look roughly like this (only including relevant info):
------------ Master --------------
ID | Master_Number | First_Arrival
---------- Vehicle ------------
ID | Master_ID | Name | Arrived
In the above, Vehicle.Master_ID is a FK to Master.ID
I've created a table variable like this:
declare #foo table (Master_Number varchar(20), First_Arrival datetime, Name varchar(20), Arrived datetime)
insert into
#foo
select
M.Master_Number,
M.First_Arrival,
V.Name,
V.Arrived
from
Master as M
inner join
Vehicle as V
on
V.Master_ID = I.ID
order by M.Master_Number, V.Arrived
This gives me a nice list of all vehicles related to the same Master_Number, and the order they arrived in.
What I'm attempting to do is get something like this:
--------------------------------------------- Result --------------------------------------------------
Master_Number | First_Arrival | Vehicle_1_Name | Vehicle_1_Arrived | Vehicle_2_Name | Vehicle_2_Arrived
sorted where Vehicle_1_Arrived < Vehicle_2_Arrived, Vehicle_1_Name like '[ETL]%', and 'Vehicle_2_Name like '[TL]%'
The problem I'm running into is that there may be zero or more vehicles associated with a Master_ID, and I only want each row to be the first results that match this exactly, disregarding any other vehicles related to that Master_ID, and any null values.
I'm new to SQL, and can't wrap my head around this....any help would be greatly appreciated.

This is a very good candidate for apply:
select M.Master_Number, M.First_Arrival, v1.Name, v1.Arrived,
v1.Name, v2.Arrived
from Master m cross apply
(select top (1) v1.*
from Vehicle v1
where v.Master_ID = I.ID and v1.Name like '[ETL]%'
order by v1.arrived
) v1 cross apply
(select top (1) v2.*
from Vehicle v2
where v2.Master_ID = I.ID and v2.Name like '[TL]%' and
v2.arrived > v1.arrived
order by v2.arrived asc
) v2
order by M.Master_Number;
apply implements a lateral join, which is a lot like a correlated subquery, except it can return multiple columns and/or multiple rows.

Related

Postgres join and count multiple relational tables

I want to join the 2 tables to the first table and group by a vendor name. I have three tables listed below.
Vendors Table
| id | name
|:-----------|------------:|
| test-id | Vendor Name |
VendorOrders Table
| id | VendorId | Details | isActive(Boolean)| price |
|:-----------|------------:|:------------:| -----------------| --------
| random-id | test-id | Sample test | TRUE | 5000
OrdersIssues Table
| id | VendorOrderId| Details. |
|:-----------|--------------:-----------:|
| order-id | random-id | Sample test|
The expected output is to count how many orders belong to a vendor and how many issues belongs to a vendor order.
I have the below code but it's not giving the right output.
SELECT "vendors"."name" as "vendorName",
COUNT("vendorOrders".id) as allOrders,
COUNT("orderIssues".id) as allIssues
FROM "vendors"
LEFT OUTER JOIN "vendorOrders" ON "vendors".id = "vendorOrders"."vendorId"
LEFT OUTER JOIN "orderIssues" ON "orderIssues"."vendorOrderId" = "vendorOrders"."id"
GROUP BY "vendors".id;```
You need the keyword DISTINCT, at least for allOrders:
SELECT v.name vendorName,
COUNT(DISTINCT vo.id) allOrders,
COUNT(DISTINCT oi.id) allIssues
FROM vendors v
LEFT OUTER JOIN vendorOrders vo ON v.id = vo.vendorId
LEFT OUTER JOIN orderIssues oi ON oi.vendorOrderId = vo.id
GROUP BY v.id, v.name;
Consider using aliases instead of full table names to make the code shorter and more readable.
You are joining along two related dimensions. The overall number of rows is the number of issues. But to get the number of orders, you need a distinct count:
SELECT v.*, count(distinct vo.id) as num_orders,
COUNT(oi.vendororderid) as num_issues
FROM vendors v LEFT JOIN
vendorOrders vo
ON v.id = vo.vendorId LEFT JOIN
orderIssues oi
ON oi.vendorOrderId = vo.id
GROUP BY v.id;
Notes:
Table aliases make the query easier to write and to read.
Quoting column and table names makes the query harder to write and read. Don't quote identifiers (you may need to recreate the tables).
Postgres support SELECT v.* . . . GROUP BY v.id assuming that the id is the primary key (actually, it only needs to be unique). This seems like a reasonable assumption.

PL/SQL Oracle Sql - Filter query results by max of column/attribute

i haven't really touched much of PL/SQL before this project and my current knowledge of DBs is limited to SQLite, MySQL, PostgreSQL and other non sql DB technologies so bear with me on this.
First i started with a query that would get me the max difference in hours between two dates for each ID i had on that table. It goes something like this:
SELECT
id_trip as ID,
MAX(24 * (Trip.actual_arrival_date- (Trip.programmed_arrival_date))) as MAX_DELAY_HOURS
FROM Trip
GROUP BY ID
And that returns me something like this:
Results of first query
So i can say that i successfully went into the all the trips that exist for each ID and got the ones that have the maximum delay.
Now what i want to do after that is to join other types of information to that table, namely the actual date of the trip, the name of the departure spot and the name of the arrival spot. So i did something like this:
SELECT
internal_ID as external_ID,
programmed_date,
starting_airport.name as starting_airport,
destination_airport.name as destination_airport,
24 * (Trip.actual_arrival_date- (Trip.programmed_arrival_date)) AS external_delay
FROM Regular_flight
INNER JOIN Trip ON Regular_flight.ID = Trip.ID_Regular_flight
INNER JOIN Flight ON Regular_flight.ID_Flight = Flight.ID
INNER JOIN Airport starting_airport ON starting_airport.ID_IATA = Flight.ID_STARTING_AIRPORT
INNER JOIN Airport destination_airport ON destination_airport.ID_IATA = Voo.ID_DESTINATION_AIRPORT
INNER JOIN (
--this is the query that i built before--
SELECT
id_trip as internal_ID,
MAX(24 * (Trip.actual_arrival_date- (Trip.programmed_arrival_date))) as MAX_DELAY_HOURS
FROM Trip
GROUP BY ID
) ON internal_ID = external_ID
Order by external_ID;
And this actually returns something like this:
Results of second query
Now my problem is that while i have all the info i need there... i wanted to filter it out so that it only shows me the highest EXTERNAL_DELAY for each EXTERNAL_ID.
Usually i'd do a GROUP BY EXTERNAL_ID but since i'm selecting many things not just EXTERNAL_ID it won't actually execute the code. I've tried to do a GROUP BY with all the columns i'm selecting in the external query but then i have all the "combinations" between the external_ID and Programmed_date which is not what i'm looking for.
Basically from the 2nd query i want to reach something like this:
| EXTERNAL_ID | PROGRAMMED_DATE | STARTING_AIRPORT | DESTINATION_AIRPORT | EXTERNAL_DELAY |
| 1 | 16.07.08 | Aeroporto de Gatwick | Aeroporto Francisco Sa Carneiro | 744 |
| 2 | 16.08.08 | Aeroporto de Gatwick | Aeroporto Francisco Sa Carneiro | 0 |
| 3 | 16.08.09 | Aeroporto Francisco Sa Carneiro | Aeroporto Francisco Sa Carneiro | 744 |
And so on for each ID, so basically for each ID the the MAXIMUM delay found no matter what the date is.
I've been scratching my head for a few hours now and i'd like to have someone point me out in the right direction.
Appreciate any help i can get.
SELECT *
FROM
(SELECT x.*,
ROW_NUMBER() OVER (PARTITION BY external_ID ORDER BY external_delay DESC NULLS LAST) r
FROM (
SELECT internal_ID AS external_ID,
programmed_date,
starting_airport.name AS starting_airport,
destination_airport.name AS destination_airport,
24 * (Trip.actual_arrival_date- (Trip.programmed_arrival_date)) AS external_delay
FROM Regular_flight
INNER JOIN Trip
ON Regular_flight.ID = Trip.ID_Regular_flight
INNER JOIN Flight
ON Regular_flight.ID_Flight = Flight.ID
INNER JOIN Airport starting_airport
ON starting_airport.ID_IATA = Flight.ID_STARTING_AIRPORT
INNER JOIN Airport destination_airport
ON destination_airport.ID_IATA = Voo.ID_DESTINATION_AIRPORT
INNER JOIN
(
--this is the query that i built before--
SELECT id_trip AS internal_ID,
MAX(24 * (Trip.actual_arrival_date- (Trip.programmed_arrival_date))) AS MAX_DELAY_HOURS
FROM Trip
GROUP BY ID
)
ON internal_ID = external_ID
) x) WHERE r =1
Order by external_ID;

Why does my self join return double rows? And how would I query the error in this table correctly?

I've turned this question into a two part question.
I'm trying to understand the logic of self joins but for some reason I get twice as many rows as I think I should be getting here and I don't know why.
I also need to revise my query to prevent the extra row from appearing in the result
The table has user input errors where the begin date of a new ID2 should be the day following the EndDate of the old ID2.
The table is :
ID | BeginDate | EndDate | ID2
1 | 2000-01-01 | | TEN
1 | 2000-01-01 |2010-01-01 | ONEHUNDRED
2 | 2000-01-01 |2011-11-11 | TWENTY
2 | 2011-11-12 | | TWOHUNDRED
3 | 2000-01-01 | | THIRTY
I need to do a self join that would expose rows where the BeginDate should have been updated but hasn't been. So for above we would only see the rows for ID1.
I also have a query that should take the one correct ID2 for each ID. The table error will return two ID2's where ID = 1 when I only want the correct one; the query is below.
SELECT ID2
FROM TABLE1
WHERE inDate BETWEEN BeginDate AND NVL(EndDate, SYSDATE);
I had tried something like this
SELECT ID2
FROM TABLE1
WHERE inDate BETWEEN BeginDate AND NVL(MAX(EndDate), SYSDATE);
but MAX obviously won't work in the where clause.
All my self joins to try and expose these errors such as the one below return a row for each side.
SELECT v.*
FROM Table1 v INNER JOIN Table1 v2
ON v.ID = v2.ID
AND v.BeginDate = v2.BeginDate
AND v.ID2 != v2.ID2
I think I just miss the point of how a self join should work.
I don't think I can compare the end date where they are null as Oracle won't do a comparison with Nulls.
Does anyone have any insight as to what I would need to compare in Table1 in a self join to only show the rows from one side of a self join?
Thanks
There is no unique key on id and begin_date, so you may want to distinct the outputs as
SELECT DISTINCT v.*
FROM Table1 v INNER JOIN Table1 v2
ON v.ID = v2.ID
AND v.BeginDate = v2.BeginDate
AND v.ID2 != v2.ID2;
Or
SELECT DISTINCT v.*
FROM Table1 v , Table1 v2
WHERE v.ID = v2.ID
AND v.BeginDate = v2.BeginDate
AND v.ID2 != v2.ID2;
To get the expected number of rows, change v.ID2 != v2.ID2 to v.ID2 < v2.ID2
Self join of this kind will give you numrows*numrows records.
But you limiting it with further rules.
Compare to null will always return false, but it will work.
Should be in total numrows*numrows-numrows-nulls.

SQL: Filtering data using a join is bad?

For example, I have the following tables:
animal
-----------------------
animal_id | animal_name
-----------------------
owners
-----------------------
owner_id | owner_name
-----------------------
owners_animals
--------------------
owner_id | animal_id
--------------------
I want to find the animals with no owners so I do the query:
select animal_name
from (select * from animals) as a
left join (select * from owners_animals) as o on (a.animal_id = o.animal_id)
where owner_id is NULL
Is this way of filtering data using a join acceptable and safe? With the same schema, is there a better alternative to get the same result?
Use a Not Exists clause:
Select animal_name
From animals as a
Where Not Exists(Select 1
From owners_animals oa
Where oa.animal_id = a.animal_id)
Also, put an index of owners_animals.animal_id to make this filter as fast as possible
Assuming there's nothing postgres specific going on (I'm not familiar with postgres) then the following is easier to follow.
Select *
From animals a
left outer join owners_animals oa On a.animal_id = oa.animal_id
Where oa.owner_id is NULL
Don't ever do, FROM (SELECT * FROM table), just do FROM table, same goes with the LEFT JOIN. What you wrote is just an overly verbose
SELECT animal_name
FROM animals
LEFT JOIN owners_animals
USING ( animal_id )
WHERE owner_id IS NULL;
With that said, I often like the NOT EXISTS() option, because it keeps the owner_id IS NULL fragment out.
USING (foo) is the same as foo = foo, on the joined tables, except only one of them will be in the result set.

When should I use CROSS APPLY over INNER JOIN?

What is the main purpose of using CROSS APPLY?
I have read (vaguely, through posts on the Internet) that cross apply can be more efficient when selecting over large data sets if you are partitioning. (Paging comes to mind)
I also know that CROSS APPLY doesn't require a UDF as the right-table.
In most INNER JOIN queries (one-to-many relationships), I could rewrite them to use CROSS APPLY, but they always give me equivalent execution plans.
Can anyone give me a good example of when CROSS APPLY makes a difference in those cases where INNER JOIN will work as well?
Edit:
Here's a trivial example, where the execution plans are exactly the same. (Show me one where they differ and where cross apply is faster/more efficient)
create table Company (
companyId int identity(1,1)
, companyName varchar(100)
, zipcode varchar(10)
, constraint PK_Company primary key (companyId)
)
GO
create table Person (
personId int identity(1,1)
, personName varchar(100)
, companyId int
, constraint FK_Person_CompanyId foreign key (companyId) references dbo.Company(companyId)
, constraint PK_Person primary key (personId)
)
GO
insert Company
select 'ABC Company', '19808' union
select 'XYZ Company', '08534' union
select '123 Company', '10016'
insert Person
select 'Alan', 1 union
select 'Bobby', 1 union
select 'Chris', 1 union
select 'Xavier', 2 union
select 'Yoshi', 2 union
select 'Zambrano', 2 union
select 'Player 1', 3 union
select 'Player 2', 3 union
select 'Player 3', 3
/* using CROSS APPLY */
select *
from Person p
cross apply (
select *
from Company c
where p.companyid = c.companyId
) Czip
/* the equivalent query using INNER JOIN */
select *
from Person p
inner join Company c on p.companyid = c.companyId
Can anyone give me a good example of when CROSS APPLY makes a difference in those cases where INNER JOIN will work as well?
See the article in my blog for detailed performance comparison:
INNER JOIN vs. CROSS APPLY
CROSS APPLY works better on things that have no simple JOIN condition.
This one selects 3 last records from t2 for each record from t1:
SELECT t1.*, t2o.*
FROM t1
CROSS APPLY
(
SELECT TOP 3 *
FROM t2
WHERE t2.t1_id = t1.id
ORDER BY
t2.rank DESC
) t2o
It cannot be easily formulated with an INNER JOIN condition.
You could probably do something like that using CTE's and window function:
WITH t2o AS
(
SELECT t2.*, ROW_NUMBER() OVER (PARTITION BY t1_id ORDER BY rank) AS rn
FROM t2
)
SELECT t1.*, t2o.*
FROM t1
INNER JOIN
t2o
ON t2o.t1_id = t1.id
AND t2o.rn <= 3
, but this is less readable and probably less efficient.
Update:
Just checked.
master is a table of about 20,000,000 records with a PRIMARY KEY on id.
This query:
WITH q AS
(
SELECT *, ROW_NUMBER() OVER (ORDER BY id) AS rn
FROM master
),
t AS
(
SELECT 1 AS id
UNION ALL
SELECT 2
)
SELECT *
FROM t
JOIN q
ON q.rn <= t.id
runs for almost 30 seconds, while this one:
WITH t AS
(
SELECT 1 AS id
UNION ALL
SELECT 2
)
SELECT *
FROM t
CROSS APPLY
(
SELECT TOP (t.id) m.*
FROM master m
ORDER BY
id
) q
is instant.
Consider you have two tables.
MASTER TABLE
x------x--------------------x
| Id | Name |
x------x--------------------x
| 1 | A |
| 2 | B |
| 3 | C |
x------x--------------------x
DETAILS TABLE
x------x--------------------x-------x
| Id | PERIOD | QTY |
x------x--------------------x-------x
| 1 | 2014-01-13 | 10 |
| 1 | 2014-01-11 | 15 |
| 1 | 2014-01-12 | 20 |
| 2 | 2014-01-06 | 30 |
| 2 | 2014-01-08 | 40 |
x------x--------------------x-------x
There are many situations where we need to replace INNER JOIN with CROSS APPLY.
1. Join two tables based on TOP n results
Consider if we need to select Id and Name from Master and last two dates for each Id from Details table.
SELECT M.ID,M.NAME,D.PERIOD,D.QTY
FROM MASTER M
INNER JOIN
(
SELECT TOP 2 ID, PERIOD,QTY
FROM DETAILS D
ORDER BY CAST(PERIOD AS DATE)DESC
)D
ON M.ID=D.ID
SQL FIDDLE
The above query generates the following result.
x------x---------x--------------x-------x
| Id | Name | PERIOD | QTY |
x------x---------x--------------x-------x
| 1 | A | 2014-01-13 | 10 |
| 1 | A | 2014-01-12 | 20 |
x------x---------x--------------x-------x
See, it generated results for last two dates with last two date's Id and then joined these records only in the outer query on Id, which is wrong. This should be returning both Ids 1 and 2 but it returned only 1 because 1 has the last two dates. To accomplish this, we need to use CROSS APPLY.
SELECT M.ID,M.NAME,D.PERIOD,D.QTY
FROM MASTER M
CROSS APPLY
(
SELECT TOP 2 ID, PERIOD,QTY
FROM DETAILS D
WHERE M.ID=D.ID
ORDER BY CAST(PERIOD AS DATE)DESC
)D
SQL FIDDLE
and forms the following result.
x------x---------x--------------x-------x
| Id | Name | PERIOD | QTY |
x------x---------x--------------x-------x
| 1 | A | 2014-01-13 | 10 |
| 1 | A | 2014-01-12 | 20 |
| 2 | B | 2014-01-08 | 40 |
| 2 | B | 2014-01-06 | 30 |
x------x---------x--------------x-------x
Here's how it works. The query inside CROSS APPLY can reference the outer table, where INNER JOIN cannot do this (it throws compile error). When finding the last two dates, joining is done inside CROSS APPLY i.e., WHERE M.ID=D.ID.
2. When we need INNER JOIN functionality using functions.
CROSS APPLY can be used as a replacement with INNER JOIN when we need to get result from Master table and a function.
SELECT M.ID,M.NAME,C.PERIOD,C.QTY
FROM MASTER M
CROSS APPLY dbo.FnGetQty(M.ID) C
And here is the function
CREATE FUNCTION FnGetQty
(
#Id INT
)
RETURNS TABLE
AS
RETURN
(
SELECT ID,PERIOD,QTY
FROM DETAILS
WHERE ID=#Id
)
SQL FIDDLE
which generated the following result
x------x---------x--------------x-------x
| Id | Name | PERIOD | QTY |
x------x---------x--------------x-------x
| 1 | A | 2014-01-13 | 10 |
| 1 | A | 2014-01-11 | 15 |
| 1 | A | 2014-01-12 | 20 |
| 2 | B | 2014-01-06 | 30 |
| 2 | B | 2014-01-08 | 40 |
x------x---------x--------------x-------x
ADDITIONAL ADVANTAGE OF CROSS APPLY
APPLY can be used as a replacement for UNPIVOT. Either CROSS APPLY or OUTER APPLY can be used here, which are interchangeable.
Consider you have the below table(named MYTABLE).
x------x-------------x--------------x
| Id | FROMDATE | TODATE |
x------x-------------x--------------x
| 1 | 2014-01-11 | 2014-01-13 |
| 1 | 2014-02-23 | 2014-02-27 |
| 2 | 2014-05-06 | 2014-05-30 |
| 3 | NULL | NULL |
x------x-------------x--------------x
The query is below.
SELECT DISTINCT ID,DATES
FROM MYTABLE
CROSS APPLY(VALUES (FROMDATE),(TODATE))
COLUMNNAMES(DATES)
SQL FIDDLE
which brings you the result
x------x-------------x
| Id | DATES |
x------x-------------x
| 1 | 2014-01-11 |
| 1 | 2014-01-13 |
| 1 | 2014-02-23 |
| 1 | 2014-02-27 |
| 2 | 2014-05-06 |
| 2 | 2014-05-30 |
| 3 | NULL |
x------x-------------x
cross apply sometimes enables you to do things that you cannot do with inner join.
Example (a syntax error):
select F.* from sys.objects O
inner join dbo.myTableFun(O.name) F
on F.schema_id= O.schema_id
This is a syntax error, because, when used with inner join, table functions can only take variables or constants as parameters. (I.e., the table function parameter cannot depend on another table's column.)
However:
select F.* from sys.objects O
cross apply ( select * from dbo.myTableFun(O.name) ) F
where F.schema_id= O.schema_id
This is legal.
Edit:
Or alternatively, shorter syntax: (by ErikE)
select F.* from sys.objects O
cross apply dbo.myTableFun(O.name) F
where F.schema_id= O.schema_id
Edit:
Note:
Informix 12.10 xC2+ has Lateral Derived Tables and Postgresql (9.3+) has Lateral Subqueries which can be used to a similar effect.
It seems to me that CROSS APPLY can fill a certain gap when working with calculated fields in complex/nested queries, and make them simpler and more readable.
Simple example: you have a DoB and you want to present multiple age-related fields that will also rely on other data sources (such as employment), like Age, AgeGroup, AgeAtHiring, MinimumRetirementDate, etc. for use in your end-user application (Excel PivotTables, for example).
Options are limited and rarely elegant:
JOIN subqueries cannot introduce new values in the dataset based on data in the parent query (it must stand on its own).
UDFs are neat, but slow as they tend to prevent parallel operations. And being a separate entity can be a good (less code) or a bad (where is the code) thing.
Junction tables. Sometimes they can work, but soon enough you're joining subqueries with tons of UNIONs. Big mess.
Create yet another single-purpose view, assuming your calculations don't require data obtained mid-way through your main query.
Intermediary tables. Yes... that usually works, and often a good option as they can be indexed and fast, but performance can also drop due to to UPDATE statements not being parallel and not allowing to cascade formulas (reuse results) to update several fields within the same statement. And sometimes you'd just prefer to do things in one pass.
Nesting queries. Yes at any point you can put parenthesis on your entire query and use it as a subquery upon which you can manipulate source data and calculated fields alike. But you can only do this so much before it gets ugly. Very ugly.
Repeating code. What is the greatest value of 3 long (CASE...ELSE...END) statements? That's gonna be readable!
Tell your clients to calculate the damn things themselves.
Did I miss something? Probably, so feel free to comment. But hey, CROSS APPLY is like a godsend in such situations: you just add a simple CROSS APPLY (select tbl.value + 1 as someFormula) as crossTbl and voilĂ ! Your new field is now ready for use practically like it had always been there in your source data.
Values introduced through CROSS APPLY can...
be used to create one or multiple calculated fields without adding performance, complexity or readability issues to the mix
like with JOINs, several subsequent CROSS APPLY statements can refer to themselves: CROSS APPLY (select crossTbl.someFormula + 1 as someMoreFormula) as crossTbl2
you can use values introduced by a CROSS APPLY in subsequent JOIN conditions
As a bonus, there's the Table-valued function aspect
Dang, there's nothing they can't do!
This has already been answered very well technically, but let me give a concrete example of how it's extremely useful:
Lets say you have two tables, Customer and Order. Customers have many Orders.
I want to create a view that gives me details about customers, and the most recent order they've made. With just JOINS, this would require some self-joins and aggregation which isn't pretty. But with Cross Apply, its super easy:
SELECT *
FROM Customer
CROSS APPLY (
SELECT TOP 1 *
FROM Order
WHERE Order.CustomerId = Customer.CustomerId
ORDER BY OrderDate DESC
) T
Cross apply works well with an XML field as well. If you wish to select node values in combination with other fields.
For example, if you have a table containing some xml
<root>
<subnode1>
<some_node value="1" />
<some_node value="2" />
<some_node value="3" />
<some_node value="4" />
</subnode1>
</root>
Using the query
SELECT
id as [xt_id]
,xmlfield.value('(/root/#attribute)[1]', 'varchar(50)') root_attribute_value
,node_attribute_value = [some_node].value('#value', 'int')
,lt.lt_name
FROM dbo.table_with_xml xt
CROSS APPLY xmlfield.nodes('/root/subnode1/some_node') as g ([some_node])
LEFT OUTER JOIN dbo.lookup_table lt
ON [some_node].value('#value', 'int') = lt.lt_id
Will return a result
xt_id root_attribute_value node_attribute_value lt_name
----------------------------------------------------------------------
1 test1 1 Benefits
1 test1 4 FINRPTCOMPANY
Cross apply can be used to replace subquery's where you need a column of the subquery
subquery
select * from person p where
p.companyId in(select c.companyId from company c where c.companyname like '%yyy%')
here i won't be able to select the columns of company table
so, using cross apply
select P.*,T.CompanyName
from Person p
cross apply (
select *
from Company C
where p.companyid = c.companyId and c.CompanyName like '%yyy%'
) T
Here's a brief tutorial that can be saved in a .sql file and executed in SSMS that I wrote for myself to quickly refresh my memory on how CROSS APPLY works and when to use it:
-- Here's the key to understanding CROSS APPLY: despite the totally different name, think of it as being like an advanced 'basic join'.
-- A 'basic join' gives the Cartesian product of the rows in the tables on both sides of the join: all rows on the left joined with all rows on the right.
-- The formal name of this join in SQL is a CROSS JOIN. You now start to understand why they named the operator CROSS APPLY.
-- Given the following (very) simple tables and data:
CREATE TABLE #TempStrings ([SomeString] [nvarchar](10) NOT NULL);
CREATE TABLE #TempNumbers ([SomeNumber] [int] NOT NULL);
CREATE TABLE #TempNumbers2 ([SomeNumber] [int] NOT NULL);
INSERT INTO #TempStrings VALUES ('111'); INSERT INTO #TempStrings VALUES ('222');
INSERT INTO #TempNumbers VALUES (111); INSERT INTO #TempNumbers VALUES (222);
INSERT INTO #TempNumbers2 VALUES (111); INSERT INTO #TempNumbers2 VALUES (222); INSERT INTO #TempNumbers2 VALUES (222);
-- Basic join is like CROSS APPLY; 2 rows on each side gives us an output of 4 rows, but 2 rows on the left and 0 on the right gives us an output of 0 rows:
SELECT
st.SomeString, nbr.SomeNumber
FROM -- Basic join ('CROSS JOIN')
#TempStrings st, #TempNumbers nbr
-- Note: this also works:
--#TempStrings st CROSS JOIN #TempNumbers nbr
-- Basic join can be used to achieve the functionality of INNER JOIN by first generating all row combinations and then whittling them down with a WHERE clause:
SELECT
st.SomeString, nbr.SomeNumber
FROM -- Basic join ('CROSS JOIN')
#TempStrings st, #TempNumbers nbr
WHERE
st.SomeString = nbr.SomeNumber
-- However, for increased readability, the SQL standard introduced the INNER JOIN ... ON syntax for increased clarity; it brings the columns that two tables are
-- being joined on next to the JOIN clause, rather than having them later on in the WHERE clause. When multiple tables are being joined together, this makes it
-- much easier to read which columns are being joined on which tables; but make no mistake, the following syntax is *semantically identical* to the above syntax:
SELECT
st.SomeString, nbr.SomeNumber
FROM -- Inner join
#TempStrings st INNER JOIN #TempNumbers nbr ON st.SomeString = nbr.SomeNumber
-- Because CROSS APPLY is generally used with a subquery, the subquery's WHERE clause will appear next to the join clause (CROSS APPLY), much like the aforementioned
-- 'ON' keyword appears next to the INNER JOIN clause. In this sense, then, CROSS APPLY combined with a subquery that has a WHERE clause is like an INNER JOIN with
-- an ON keyword, but more powerful because it can be used with subqueries (or table-valued functions, where said WHERE clause can be hidden inside the function).
SELECT
st.SomeString, nbr.SomeNumber
FROM
#TempStrings st CROSS APPLY (SELECT * FROM #TempNumbers tempNbr WHERE st.SomeString = tempNbr.SomeNumber) nbr
-- CROSS APPLY joins in the same way as a CROSS JOIN, but what is joined can be a subquery or table-valued function. You'll still get 0 rows of output if
-- there are 0 rows on either side, and in this sense it's like an INNER JOIN:
SELECT
st.SomeString, nbr.SomeNumber
FROM
#TempStrings st CROSS APPLY (SELECT * FROM #TempNumbers tempNbr WHERE 1 = 2) nbr
-- OUTER APPLY is like CROSS APPLY, except that if one side of the join has 0 rows, you'll get the values of the side that has rows, with NULL values for
-- the other side's columns. In this sense it's like a FULL OUTER JOIN:
SELECT
st.SomeString, nbr.SomeNumber
FROM
#TempStrings st OUTER APPLY (SELECT * FROM #TempNumbers tempNbr WHERE 1 = 2) nbr
-- One thing CROSS APPLY makes it easy to do is to use a subquery where you would usually have to use GROUP BY with aggregate functions in the SELECT list.
-- In the following example, we can get an aggregate of string values from a second table based on matching one of its columns with a value from the first
-- table - something that would have had to be done in the ON clause of the LEFT JOIN - but because we're now using a subquery thanks to CROSS APPLY, we
-- don't need to worry about GROUP BY in the main query and so we don't have to put all the SELECT values inside an aggregate function like MIN().
SELECT
st.SomeString, nbr.SomeNumbers
FROM
#TempStrings st CROSS APPLY (SELECT SomeNumbers = STRING_AGG(tempNbr.SomeNumber, ', ') FROM #TempNumbers2 tempNbr WHERE st.SomeString = tempNbr.SomeNumber) nbr
-- ^ First the subquery is whittled down with the WHERE clause, then the aggregate function is applied with no GROUP BY clause; this means all rows are
-- grouped into one, and the aggregate function aggregates them all, in this case building a comma-delimited string containing their values.
DROP TABLE #TempStrings;
DROP TABLE #TempNumbers;
DROP TABLE #TempNumbers2;
I guess it should be readability ;)
CROSS APPLY will be somewhat unique for people reading to tell them that a UDF is being used which will be applied to each row from the table on the left.
Ofcourse, there are other limitations where a CROSS APPLY is better used than JOIN which other friends have posted above.
The essence of the APPLY operator is to allow correlation between left and right side of the operator in the FROM clause.
In contrast to JOIN, the correlation between inputs is not allowed.
Speaking about correlation in APPLY operator, I mean on the right hand side we can put:
a derived table - as a correlated subquery with an alias
a table valued function - a conceptual view with parameters, where the parameter can refer to the left side
Both can return multiple columns and rows.
Here is an article that explains it all, with their performance difference and usage over JOINS.
SQL Server CROSS APPLY and OUTER APPLY over JOINS
As suggested in this article, there is no performance difference between them for normal join operations (INNER AND CROSS).
The usage difference arrives when you have to do a query like this:
CREATE FUNCTION dbo.fn_GetAllEmployeeOfADepartment(#DeptID AS INT)
RETURNS TABLE
AS
RETURN
(
SELECT * FROM Employee E
WHERE E.DepartmentID = #DeptID
)
GO
SELECT * FROM Department D
CROSS APPLY dbo.fn_GetAllEmployeeOfADepartment(D.DepartmentID)
That is, when you have to relate with function. This cannot be done using INNER JOIN, which would give you the error "The multi-part identifier "D.DepartmentID" could not be bound." Here the value is passed to the function as each row is read. Sounds cool to me. :)
Well I am not sure if this qualifies as a reason to use Cross Apply versus Inner Join, but this query was answered for me in a Forum Post using Cross Apply, so I am not sure if there is an equalivent method using Inner Join:
Create PROCEDURE [dbo].[Message_FindHighestMatches]
-- Declare the Topical Neighborhood
#TopicalNeighborhood nchar(255)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON
Create table #temp
(
MessageID int,
Subjects nchar(255),
SubjectsCount int
)
Insert into #temp Select MessageID, Subjects, SubjectsCount From Message
Select Top 20 MessageID, Subjects, SubjectsCount,
(t.cnt * 100)/t3.inputvalues as MatchPercentage
From #temp
cross apply (select count(*) as cnt from dbo.Split(Subjects,',') as t1
join dbo.Split(#TopicalNeighborhood,',') as t2
on t1.value = t2.value) as t
cross apply (select count(*) as inputValues from dbo.Split(#TopicalNeighborhood,',')) as t3
Order By MatchPercentage desc
drop table #temp
END
This is perhaps an old question, but I still love the power of CROSS APPLY to simplify the re-use of logic and to provide a "chaining" mechanism for results.
I've provided a SQL Fiddle below which shows a simple example of how you can use CROSS APPLY to perform complex logical operations on your data set without things getting at all messy. It's not hard to extrapolate from here more complex calculations.
http://sqlfiddle.com/#!3/23862/2
While most queries which employ CROSS APPLY can be rewritten using an INNER JOIN, CROSS APPLY can yield better execution plan and better performance, since it can limit the set being joined yet before the join occurs.
Stolen from Here
We use CROSS APPLY to update a table with JSON from another (update request) table -- joins won't work for this as we use OPENJSON, to read the content of the JSON, and OPENJSON is a "table-valued function".
I was going to put a simplified version of one of our UPDATE commands here as a example but, even simplified, it is rather large and overly complex for an example. So this much simplied "sketch" of just part of the command will have to suffice:
SELECT
r.UserRequestId,
j.xxxx AS xxxx,
FROM RequestTable as r WITH (NOLOCK)
CROSS APPLY
OPENJSON(r.JSON, '$.requesttype.recordtype')
WITH(
r.userrequestid nvarchar(50) '$.userrequestid',
j.xxx nvarchar(20) '$.xxx
)j
WHERE r.Id > #MaxRequestId
and ... etc. ....