SQL: Turn a subquery into a join: How to refer to outside table in nested join where clause? - sql

I am trying to change my sub-query in to a join where it selects only one record in the sub-query. It seems to run the sub-query for each found record, taking over a minute to execute:
select afield1, afield2, (
select top 1 b.field1
from anothertable as b
where b.aForeignKey = a.id
order by field1
) as bfield1
from sometable as a
If I try to only select related records, it doesn't know how to bind a.id in the nested select.
select afield1, afield2, bfield1
from sometable a left join (
select top 1 id, bfield, aForeignKey
from anothertable
where anothertable.aForeignKey = a.id
order by bfield) b on
b.aForeignKey = a.id
-- Results in the multi-part identifier "a.id" could not be bound
If I hard code values in the nested where clause, the select duration drops from 60 seconds to under five. Anyone have any suggestions on how to join the two tables while not processing every record in the inner table?
EDIT:
I ended up adding
left outer join (
select *, row_number() over (partition by / order by) as rank) b on
b.aforeignkey = a.id and b.rank = 1
went from ~50 seconds to 8 for 22M rows.

Try this:
WITH qry AS
(
SELECT afield1,
afield2,
b.field1 AS bfield1,
ROW_NUMBER() OVER(PARTITION BY a.id ORDER BY field1) rn
FROM sometable a LEFT JOIN anothertable b
ON b.aForeignKey = a.id
)
SELECT *
FROM qry
WHERE rn = 1

Try this
select afield1,
afield2,
bfield1
from sometable a
left join
(select top 1 id, bfield, aForeignKey from anothertable where aForeignKey in(a.id) order by bfield) b on b.aForeignKey = a.id

Related

Exposing more fields on group by sql

I know, in a Group By you can't Select a field that is not in an aggregate function or the GROUP BY clause.
However, There must be a workaround using joins or something else.
I have TWO tables BMP_VISITS_SITES and BMP_VISITS_COMMENTS which are connected by StationID in a one-to-many relationship. One Site can have many comments.
I'm trying to write a query that returns all Sites and the latest (only 1) comment. I have a "working" query but it only returns two columns which are in either an aggregate function or group by.
Here is my "working" query:
select a.StationID,
MAX(b.[dateobserved]) as LastDateObserved,
a.Status
from BMP_VISITS_SITES a
left outer join BMP_VISITS_COMMENTS as b
on a.[StationID] = b.[StationID]
group by a.StationID;
But how can I access all the columns in both tables?
I've tried inner joins with 1/2 success. When I join my BMP_VISITS_SITES to the above query I get all the fields of the table (t1). Great, but as soon as I try joining on BMP_VISITS_COMMENTS (t3) I get more results than I should.
select t1.*, t2.*
--,t3.*
from BMP_VISITS_SITES t1
inner join (
select a.StationID, MAX(b.[dateobserved]) as LastDateObserved from BMP_VISITS_SITES a
left outer join BMP_VISITS_COMMENTS as b
on a.[StationID] = b.[StationID]
group by a.StationID
) t2 on t2.StationID = t1.StationID
--inner join sde.BMP_VISITS_COMMENTS t3 on t3.StationID = t2.StationID;
SELECT a.*, b.* FROM
BMP_VISITS_SITES a
OUTER APPLY
(
SELECT TOP 1 *
FROM BMP_VISITS_COMMENTS b
WHERE b.StationID = a.StationID
ORDER BY LastDateObserved DESC
) b
You can use apply to get the last comment record and return all fields from both sides of the query.
Use row_number()
select *
from
(
select a.StationID,
a.Status,
b.*,
row_number() over (partition by a.stationid, a.status order by b.[dateobserved] desc) as rn
from BMP_VISITS_SITES a
left outer join BMP_VISITS_COMMENTS as b
on a.[StationID] = b.[StationID]
) v
where rn = 1

SQL Subquery to get first record

I need to execute a query something like below.
SELECT TO_CHAR(ROWNUM),
A.Name,
B.Order,
(SELECT * FROM (
SELECT ROUND(LAST_ORDER_AMOUNT,5) FROM ORDERS WHERE ID=A.id AND REQUEST_LEVEL='N' ORDER BY O_DATE DESC)
WHERE ROWNUM =1) AS AMOUNT
FROM Table1 A LEFT JOIN Table2 B
ON A.TYPE_CODE = B.ENTITY_TYPE
But this gives me A.ID is invalid error in oracle. I need to get the first record from inner query as it will return multiple records.
Can someone please let me know how can i bind these tables to achieve my goal.
Thank you in advance.
You can rewrite subquery using WITH clause, not exactly sure on syntax but should be something like following.
WITH AmountQuery
AS (
SELECT ID
,ROUND(LAST_ORDER_AMOUNT, 5) AS AmountValue
,ROW_NUMBER() OVER ( ORDER BY O_DATE DESC ) AS RN
FROM ORDERS
WHERE REQUEST_LEVEL = 'N'
)
SELECT TO_CHAR(ROWNUM)
,A.Name
,B.Order
,C.AmountValue
FROM Table1 A
LEFT JOIN Table2 B
ON A.TYPE_CODE = B.ENTITY_TYPE
LEFT JOIN AmountQuery C
ON a.ID = c.ID
AND c.RN = 1
here is SQLFiddle to show how it works.
http://sqlfiddle.com/#!4/696b6/36
Probably, LIMIT will do the job for you selecting just one record from the subquery (It worked for me in MySQL. I do not have Oracle, but I think it may be similar). Try something like this:
SELECT TO_CHAR(ROWNUM),
A.Name,
B.Order,
COALESCE( C.AMOUNT ) as AMOUNT,
FROM Table1 A LEFT JOIN Table2 B
ON A.TYPE_CODE = B.ENTITY_TYPE
LEFT JOIN ( SELECT ROUND(LAST_ORDER_AMOUNT,5) AS AMOUNT FROM ORDERS WHERE REQUEST_LEVEL='N' ORDER BY O_DATE DESC ) C ON C.ID = A.id
group by A.id;

LEFT JOIN - How to join tables and include extra row even if you have right match

I have two tables
Table A
-------
ID
ProductName
Table B
-------
ID
ProductID
Size
I want to join these two tables
SELECT * FROM
(SELECT * FROM A)
LEFT JOIN
(SELECT * FROM B)
ON A.ID = B.ProductID
This is easy, I will get all rows from A multiplied by rows matched in B, and NULL fields if there is no match.
But here comes the tricky question, how can I get all rows from A with NULL fields for table B, even if there is a match, so I get an extra line with NULL values plus all the matches?
SELECT A.*
, B3.ID
, B3.ProductID
, B3.Size
FROM A
LEFT JOIN
(
SELECT ProductID as MatchID
, ID
, ProductID
, Size
FROM B
UNION ALL
SELECT ID
, null
, null
, null
FROM A A2
) B3
ON A.ID = B3.MatchID
Live example at SQL Fiddle.
Instead of using UNION ALL in a subquery as suggested by others, you could also (and I would) use UNION ALL at the outer level, which keeps the query simpler:
SELECT A.ID, A.ProductName, B.ID, B.Size
FROM A
INNER JOIN B
ON B.ProductID = A.ID
UNION ALL
SELECT A.ID, A.ProductName, NULL, NULL
FROM A
Since every join is going to be successful, we can switch to a full/inner join:
SELECT
*
FROM
A
INNER JOIN
(SELECT ID,ProductID,Size FROM B
UNION ALL
SELECT NULL,ID,NULL FROM A) B
ON
A.ID = B.ProductID
Now would be a very good time to switch to naming columns explicitly, rather than using SELECT *
Or, if, as per #Andomar's comment, you need all of the B columns to be NULL:
SELECT
A.ID,A.ProductName,
B.ID,B.ProductID,B.Size
FROM
A
INNER JOIN
(SELECT ID,ProductID,Size,ProductID as MatchID FROM B
UNION ALL
SELECT NULL,NULL,NULL,ID FROM A) B
ON
A.ID = B.MatchID

What is the alternative for outer apply?

Recently I have added outer apply in my a query. Since then this query takes forever. One reason i know that the table it is associated with is the biggest table in the database now.
select
a.*,
b.*,
BTab.*,
BTTab.*
from
tableA a
join tableB b ON a.ID = b.UID
join *****
left join *******
....
....
....
outer apply
(SELECT TOP 1 *
FROM
biggestTable bt
WHERE
bt.id = a.id
and a.id <> 100
ORDER BY a.datetime desc) BTab
Outer apply
(SELECT TOP 1 *
FROM
biggestTable btt
WHERE
btt.id = a.id
AND btt.DateTime <> '1948-01-01 00:00:00.000'
and btt.value = 0
order by btt.datetime desc) BTTab
where
..................
.................
....................
.................
Is there any better solution than using outer apply?
Here's an alternative, can't say whether its better or not. You may simply need better indexes on your big table
WITH BTAB as
( SELECT TOP 1
* ,
row_nubmer() over (partition by b.id) rn
FROM
biggestTable bt
) ,
BTTab as (SELECT TOP 1
* ,
row_nubmer() over (partition by btt.id order by btt.datetime desc) rn
FROM
biggestTable btt
WHERE
AND btt.DateTime <> '1948-01-01 00:00:00.000'
and btt.value = 0
)
select
a.*,
b.*,
BTab.*,
BTTab.*
from
tableA a
join tableB b ON a.ID = b.UID
join *****
left join BTab on ON a.ID = BTab.ID
and BTAB.rn = 1
left join BTTabon ON a.ID = BTTab.ID
and BTTab.rn = 1
+1 for Conrad as his answer might be all you need and I reused some of his syntax.
Problem with Apply and CTE is they are evaluated for each row in the a, b join.
I would create two temporary tables. To represent the max rows and put a PK on them. The benefit is these two expensive quires are done once and the join is to a PK. Big benefit joining to a PK. I eat the overhead of #temp to get a single evaluation and PK a lot.
Create table #Btab (int ID PK, ...)
insert into #Btab
WITH BTAB as
( SELECT * ,
row_nubmer() over (partition by b.id) rn
FROM
biggestTable
where ID <> 100
)
Select * from BTAB
Where RN = 1
order by ID
Create table #Bttab (int ID PK, ...)
insert into #Bttab
WITH BTTAB as
( SELECT * ,
row_nubmer() over (partition by id order by datetime desc) rn
FROM
biggestTable
where DateTime <> '1948-01-01 00:00:00.000' and value = 0
)
Select * from BTAB
Where RN = 1
order by ID
select
a.*,
b.*,
#Btab.*,
#Bttab.*
from
tableA a
join tableB b ON a.ID = b.UID
join *****
left join *******
....
....
....
left outer outer join #Btab
on #Btab.ID = a.ID
left outer outer join #Bttab
on #Bttab.ID = a.ID
where
..................
.................
P.S. I am exploring TVP over #TEMP for this. A TVP supports a PK and has less overhead than #tmp. But I have not compared them head to head in this type of application.
Tested TVP over #TEMP and got a 1/2 second improvement (about the time it take to create and delete a temporary table).

How to query total when I have a join table

Hallo,
I have a join table, said tableA and tableB. tableA have a column called Amount. tableB have a column called refID. I would like to total up the Amount column when refID having the same value. I was using SUM in my query, but it throw me an error:
ORA-30483: window functions are not allowed here
30483. 00000 - "window functions are not allowed here"
*Cause: Window functions are allowed only in the SELECT list of a query.
And, window function cannot be an argument to another window or group
function.
Here is my query for your reference:
select *
from (
select SUM(A.Amount), B.refId, Rank() over (partition by B.refID order by B.id desc) as ranking
from table A
left outer join table B on A.refID = B.refID
)
where ranking=1;
May I know is there any alternate solution in order for me to SUM the Amount?
THanks #!
select
SUM(A.Amount),
B.refId
from table A
left outer join table B on A.refID = B.refID
GROUP BY
B.refId
SELECT *
FROM (
SELECT A.Amount, B.refId,
Rank() over (partition by A.refID order by B.id desc) as ranking,
SUM(amount) OVER (PARTITION BY a.refId) AS asum
FROM tableA A
LEFT JOIN
tableB B
ON B.refID = A.refID
)
WHERE ranking = 1
Declare #T table(id int)
insert into #T values (1),(2)
Declare #T1 table(Tid int,fkid int,Amount int)
insert into #T1 values (1,1,200),(2,1,250),(3,2,100),(4,2,25)
Select SUM(t1.Amount) as amount,t1.fkid as id from #T t
left outer join #T1 t1 on t1.fkid = t.id group by t1.fkid
SELECT refid, sum(a.amount)
FROM table AS a LEFT table AS b USING (refid)
GROUP BY refid;
I'm a little confused. The query you posted did not have a SUM function anywhere, and performed a self-join of a table named "TABLE" to itself. I'm going to guess that you actually have two tables (I'll call them TABLE_A and TABLE_B), in which case the following should do it:
SELECT a.REFID, SUM(a.AMOUNT)
FROM TABLE_A a
INNER JOIN TABLE_B b
ON (b.REFID = a.REFID)
GROUP BY a.REFID;
If I understood your question you only wanted results when you have a TABLE_B.REFID which matches a TABLE_A.REFID, so an INNER JOIN would be appropriate.
Share and enjoy.