Oracle SQL subquery scalar as input to other subquery - sql

In the below query, I'm using Subquery1 and Subquery2 to get Account Number and Account Name. However the first Subquery and second joins same tables except an additional table account_nameinfo_t in Subquery 2 to get the account name. Is there a way I avoid selecting from other tables and just use the value of Subquery 1 i.e account# to get the account name in Subquery 2?
SELECT
(
SELECT acct.account_no
FROM group_t grp1, account_t acct
WHERE grp1.poid_id0 = grpbm.obj_id0
AND acct.poid_id0 = grp1.ACCOUNT_OBJ_ID0
) PARENT_ACCOUNT, -- (#Subquery 1 to get the parent account)
(
SELECT ant.Firstname || ' ' || ant.LastName
FROM group_t grp1, account_t acct,account_nameinfo_t ant
WHERE grp1.poid_id0 = grpbm.obj_id0
AND acct.poid_id0 = grp1.ACCOUNT_OBJ_ID0
AND ant.obj_id0 = acct.poid_id0
) "ACCOUNT NAME", -- (#Subquery 2 to get the parent account name which is in a different table)
bgs.REC_ID2 RECORD_TYPE,
bgs.current_bal VALUE
FROM group_t grp,
group_billing_members_t grpbm,
BAL_GRP_SUB_BALS_T bgs
WHERE poid_type = '/group/sharing/discounts'
AND grpbm.OBJECT_ID0 = grp.ACCOUNT_OBJ_ID0
AND bgs.obj_id0 = grp.BAL_GRP_OBJ_ID0
AND bgs.rec_id2 NOT IN (1000203,
1030001,
1000303,
1000306)
ORDER BY PARENT_ACCOUNT;

It looks like you can simplify this using simple joins rather than subqueries, either in the select list or as inline views:
SELECT acct.account_no AS "PARENT ACCOUNT",
ant.first_name||' '||ant.last_name AS "ACCOUNT NAME",
bgs.rec_id2 AS record_type,
bgs.current_bal
FROM group_t grp
JOIN group_billing_members_t grpbm ON grpbm.obj_id0 = grp.account_obj_id0
JOIN group_t grp1 ON grp1.poid_id0 = grpbm.obj_id0
JOIN bal_grp_sub_bals_t bgs ON bgs.obj_id0 = grp.bal_grp_obj_id0
JOIN account_t acct ON acct.poid_id0 = grp1.account_obj_id0
JOIN account_nameinfo_t ant ON ant.obj_id0 = acct.poid_id0
WHERE grp.poid_type='/group/sharing/discounts'
AND bgs.rec_id2 not in (1000203, 1030001, 1000303, 1000306)
AND ant.rec_id = 1
ORDER BY "PARENT ACCOUNT";
You only seem to be using group_billing_members_t between two references to group_t, and it isn't clear if they both point to the same record, or if that expands to multiple rows. The column names seem a bit inconsistent, which may be from your retyping the code rather than copying and pasting it. If it is the same record then you seem to be able to remove that table and the rejoin:
SELECT acct.account_no AS "PARENT ACCOUNT",
ant.first_name||' '||ant.last_name AS "ACCOUNT NAME",
bgs.rec_id2 AS record_type,
bgs.current_bal
FROM group_t grp
JOIN bal_grp_sub_bals_t bgs ON bgs.obj_id0 = grp.bal_grp_obj_id0
JOIN account_t acct ON acct.poid_id0 = grp.account_obj_id0
JOIN account_nameinfo_t ant ON ant.obj_id0 = acct.poid_id0
WHERE grp.poid_type='/group/sharing/discounts'
AND bgs.rec_id2 not in (1000203, 1030001, 1000303, 1000306)
AND ant.rec_id = 1
ORDER BY "PARENT ACCOUNT";
Without table structures, relationships, sample data and expected results that's rather speculative though.

Oracle supports a WITH clause which you may find useful: http://psoug.org/reference/with.html
Essentially, it allows you to create a temporary view within a query that can be accessed multiple times. In your case, the result of your common join can be "factored out" and the result can be reused.

As ALEXPOOL suggested here is what i tried with ANSI joins and it works. Any betterment is welcome.
SELECT C1."PARENT ACCOUNT",C1."ACCOUNT NAME", A1.RECORD_TYPE, A1.CURRENT_BAL
FROM
(SELECT
bgs.REC_ID2 RECORD_TYPE,
bgs.current_bal,grpbm.OBJ_ID0
FROM group_t grp ,
group_billing_members_t grpbm,
BAL_GRP_SUB_BALS_T bgs
WHERE
poid_type='/group/sharing/discounts' and
grpbm.OBJECT_ID0 = grp.ACCOUNT_OBJ_ID0 and
bgs.obj_id0 = grp.BAL_GRP_OBJ_ID0 and
bgs.rec_id2 not in ( 1000203,
1030001,
1000303,
1000306) ) A1 JOIN
( SELECT grp1.ACCOUNT_OBJ_ID0,grp1.POID_ID0
FROM group_t grp1
) B1 ON (B1.poid_id0 = A1.OBJ_ID0)
JOIN
( SELECT acct.account_no "PARENT ACCOUNT",
ant.FIRST_NAME||' '||ant.LAST_NAME "ACCOUNT NAME",
acct.poid_id0
FROM
account_t acct,
account_nameinfo_t ant
WHERE acct.poid_id0 = ant.obj_id0 and
ant.rec_id=1) C1 ON (C1.poid_id0 = B1.ACCOUNT_OBJ_ID0)
order by C1."PARENT ACCOUNT";

Related

ORACLE SQL: Slow query when using "join table on id = id" vs "where id = number"

I'm having performance problem in a querie when I use a subquery to set an ID = number, and then join that subquery in the main query to look for that ID, this method takes about 150 seconds. But if I delete the subquery and look for the ID = number directly in the main query, it takes 0,5 second.
Here some code as exemple:
This is the example of 150 seconds
In this I set the cto_in_codigo in the With clause.
WITH CONTRATOS AS (
SELECT CTO_IN_CODIGO FROM MGCAR.CAR_CONTRATO
WHERE CTO_IN_CODIGO = 14393
)
SELECT
PT.PAR_IN_CODIGO,
PTC.PARCOR_IN_INDICE
FROM (
SELECT
MAX(PT.HPAR_IN_CODIGO) OVER (PARTITION BY PT.PAR_IN_CODIGO, PT.CTO_IN_CODIGO) HPAR_IN_CODIGO_MAX,
PT.HPAR_IN_CODIGO,
PT.CTO_IN_CODIGO,
PT.PAR_IN_CODIGO
FROM
QUERIE.PARCELA_TOTAL PT
JOIN CONTRATOS CTO
ON CTO.CTO_IN_CODIGO = PT.CTO_IN_CODIGO
WHERE
PT.PAR_DT_REAJUSTE <= TO_DATE('31/12/2017', 'DD/MM/YYYY')
) PT
LEFT OUTER JOIN (
SELECT
MAX(PTC.PARCOR_IN_CODIGO) OVER (PARTITION BY PTC.PAR_IN_CODIGO, PTC.CTO_IN_CODIGO) PARCOR_IN_CODIGO_MAX,
PTC.PARCOR_IN_CODIGO,
PTC.CTO_IN_CODIGO,
PTC.PAR_IN_CODIGO,
PTC.HPAR_IN_CODIGO,
PTC.PARCOR_IN_INDICE
FROM
QUERIE.PARCELA_TOTAL_CORRECAO PTC
JOIN CONTRATOS CTO
ON CTO.CTO_IN_CODIGO = PTC.CTO_IN_CODIGO
) PTC
ON PTC.CTO_IN_CODIGO = PT.CTO_IN_CODIGO
AND PTC.PAR_IN_CODIGO = PT.PAR_IN_CODIGO
AND PTC.HPAR_IN_CODIGO = PT.HPAR_IN_CODIGO
AND PTC.PARCOR_IN_CODIGO = PTC.PARCOR_IN_CODIGO_MAX
WHERE
PT.HPAR_IN_CODIGO = PT.HPAR_IN_CODIGO_MAX
and this is the 0,5 sec.
in this I set the cto_in_codigo inside each query
SELECT
PT.PAR_IN_CODIGO,
PTC.PARCOR_IN_INDICE
FROM (
SELECT
MAX(PT.HPAR_IN_CODIGO) OVER (PARTITION BY PT.PAR_IN_CODIGO, PT.CTO_IN_CODIGO) HPAR_IN_CODIGO_MAX,
PT.HPAR_IN_CODIGO,
PT.CTO_IN_CODIGO,
PT.PAR_IN_CODIGO
FROM
QUERIE.PARCELA_TOTAL PT
WHERE
PT.PAR_DT_REAJUSTE <= TO_DATE('31/12/2017', 'dd/MM/yyyy')
AND PT.CTO_IN_CODIGO = 14393
) PT
LEFT OUTER JOIN (
SELECT
MAX(PTC.PARCOR_IN_CODIGO) OVER (PARTITION BY PTC.PAR_IN_CODIGO, PTC.CTO_IN_CODIGO) PARCOR_IN_CODIGO_MAX,
PTC.PARCOR_IN_CODIGO,
PTC.CTO_IN_CODIGO,
PTC.PAR_IN_CODIGO,
PTC.HPAR_IN_CODIGO,
PTC.PARCOR_IN_INDICE
FROM
QUERIE.PARCELA_TOTAL_CORRECAO PTC
WHERE
PTC.CTO_IN_CODIGO = 14393
) PTC
ON PTC.CTO_IN_CODIGO = PT.CTO_IN_CODIGO
AND PTC.PAR_IN_CODIGO = PT.PAR_IN_CODIGO
AND PTC.HPAR_IN_CODIGO = PT.HPAR_IN_CODIGO
AND PTC.PARCOR_IN_CODIGO = PTC.PARCOR_IN_CODIGO_MAX
WHERE
PT.HPAR_IN_CODIGO = PT.HPAR_IN_CODIGO_MAX
what is confusing to me is that the with clause returns just one row with the cto_in_codigo number, much like if I hard code then inside each query like the second code. What is could be causing this super delay?

Why can't I access a field defined as "Select 1" from a subquery in the outer query?

I have this subquery:
LEFT JOIN (SELECT 1 as exist
, MAX (ev.EventDate) as eventdate
, evt.EventCode
, CCaseID
FROM stg.Event ev
JOIN stg.EventTemplate evt
ON ev.EventTemplateID = evt.ID
WHERE evt.EventCode = 'UN002'
Group by CCaseID, evt.EventCode) as un002
ON un002.CCaseID = ev.CCaseID
WHERE evt.EventCode = 'UN001'
AND (un002.eventdate < ev.eventdate OR un002.eventdate IS NULL)
Group by ev.CCaseID, evt.EventCode) as un001
ON cc.ID = un001.CCaseID
I am now trying to access the exist field in the outer query as per un001.exist but SQL Server tells me that it is an invalid field. What am I missing?
un001 doesnt have exist that field belong to un002 subquery.
Also you have a GROUP BY and the and ON so there is some missing code there.
You should simplify the code and use CTE to make it easy to read and debug.
Something like this :
WITH un001 as ( SELECT ... ),
un002 as ( SELECT ...)
SELECT *
FROM un001
JOIN un002
ON un001 .CCaseID = un002.CCaseID

Only return value that matches the ID on table 1

I have tried all possible joins and sub-queries but I cant get the data to only return one value from table 2 that exactly matches the vendor ID. If I dont have the address included in the query, I get one hit for the vendor ID. How can I make it so that when I add the address, I only want the one vendor that I get prior to adding the address.
The vendor from table one must be VEN-CLASS IS NOT NULL.
This was my last attempt using subquery:
SELECT DISTINCT APVENMAST.VENDOR_GROUP,
APVENMAST.VENDOR,
APVENMAST.VENDOR_VNAME,
APVENMAST.VENDOR_CONTCT,
APVENMAST.TAX_ID,
Subquery.ADDR1
FROM (TEST.dbo.APVENMAST APVENMAST
INNER JOIN
(SELECT APVENADDR.ADDR1,
APVENADDR.VENDOR_GROUP,
APVENADDR.VENDOR,
APVENMAST.VEN_CLASS
FROM TEST.dbo.APVENADDR APVENADDR
INNER JOIN TEST.dbo.APVENMAST APVENMAST
ON (APVENADDR.VENDOR_GROUP = APVENMAST.VENDOR_GROUP)
AND (APVENADDR.VENDOR = APVENMAST.VENDOR)
WHERE (APVENMAST.VEN_CLASS IS NOT NULL)) Subquery
ON (APVENMAST.VENDOR_GROUP = Subquery.VENDOR_GROUP)
AND (APVENMAST.VENDOR = Subquery.VENDOR))
INNER JOIN TEST.dbo.APVENLOC APVENLOC
ON (APVENMAST.VENDOR_GROUP = APVENLOC.VENDOR_GROUP)
AND (APVENMAST.VENDOR = APVENLOC.VENDOR)
WHERE (APVENMAST.VEN_CLASS IS NOT NULL)
Try this:
SELECT APVENMAST.VENDOR_GROUP
, APVENMAST.VENDOR
, APVENMAST.VENDOR_VNAME
, APVENMAST.VENDOR_CONTCT
, APVENMAST.TAX_ID
, APVENADDR.ADDR1
FROM TEST.dbo.APVENMAST APVENMAST
INNER JOIN (
select VENDOR_GROUP, VENDOR, ADDR1
, row_number() over (partition by VENDOR_GROUP, VENDOR order by ADDR1) r
from TEST.dbo.APVENADDR
) APVENADDR
ON APVENADDR.VENDOR_GROUP = APVENMAST.VENDOR_GROUP
AND APVENADDR.VENDOR = APVENMAST.VENDOR
AND APVENADDR.r = 1
--do you need this table; you're not using it...
--INNER JOIN TEST.dbo.APVENLOC APVENLOC
--ON APVENMAST.VENDOR_GROUP = APVENLOC.VENDOR_GROUP
--AND APVENMAST.VENDOR = APVENLOC.VENDOR
WHERE APVENMAST.VEN_CLASS IS NOT NULL
--if the above inner join was to filter results, you can do this instead:
and exists (
select top 1 1
from TEST.dbo.APVENLOC APVENLOC
ON APVENMAST.VENDOR_GROUP = APVENLOC.VENDOR_GROUP
AND APVENMAST.VENDOR = APVENLOC.VENDOR
)
I found another column in the APVENLOC table that I can filter on to get the unique vendor. Turns out if the vendor address is for the main office, the vendor location is set blank.
Easier than I thought it would be!
SELECT DISTINCT APVENMAST.VENDOR_GROUP,
APVENMAST.VENDOR,
APVENMAST.VENDOR_VNAME,
APVENADDR.ADDR1,
APVENMAST.VENDOR_SNAME,
APVENADDR.LOCATION_CODE,
APVENMAST.VEN_CLASS
FROM TEST.dbo.APVENMAST APVENMAST
INNER JOIN TEST.dbo.APVENADDR APVENADDR
ON (APVENMAST.VENDOR_GROUP = APVENADDR.VENDOR_GROUP)
AND (APVENMAST.VENDOR = APVENADDR.VENDOR)
WHERE (APVENADDR.LOCATION_CODE = ' ')
Shaji

Select statement to show the corresponding user with the lowest/highest amount?

I want to write a select statement output that, among other things, has both a lowest_bid and highest_bid column. I know how to do that bit, but want I also want is to show the user (user_firstname and user_lastname combined into their own column) as lowest_bidder and highest_bidder. What I have so far is:
select item_name, item_reserve, count(bid_id) as number_of_bids,
min(bid_amount) as lowest_bid, ???, max(big_amount) as highest_bid,
???
from vb_items
join vb_bids on item_id=bid_item_id
join vb_users on item_seller_user_id=user_id
where bid_status = ‘ok’ and
item_sold = ‘no’
sort by item_reserve
(The ???'s are where the columns should go, once I figure out what to put there!)
This seems like good use of window functions. I've assumed a column vb_bids.bid_user_id. If there's no link between a bid and a user, you can't answer this question
With x as (
Select
b.bid_item_id,
count(*) over (partition by b.bid_item_id) as number_of_bids,
row_number() over (
partition by b.bid_item_id
order by b.bid_amount desc
) as high_row,
row_number() over (
partition by b.bid_item_id
order by b.bid_amount
) as low_row,
b.bid_amount,
u.user_firstname + ' ' + u.user_lastname username
From
vb_bids b
inner join
vb_users u
on b.bid_user_id = u.user_id
Where
b.bid_status = 'ok'
)
Select
i.item_name,
i.item_reserve,
min(x.number_of_bids) number_of_bids,
min(case when x.low_row = 1 then x.bid_amount end) lowest_bid,
min(case when x.low_row = 1 then x.username end) low_bidder,
min(case when x.high_row = 1 then x.bid_amount end) highest_bid,
min(case when x.high_row = 1 then x.username end) high_bidder
From
vb_items i
inner join
x
on i.item_id = x.bid_item_id
Where
i.item_sold = 'no'
Group By
i.item_name,
i.item_reserve
Order By
i.item_reserve
Example Fiddle
In order to get the users, I broke out the aggregates into their own tables, joined them by the item_id and filtered them by a derived value that is either the min or max of bid_amount. I could have joined to vb_bids for a third time, and kept the aggregate functions, but that would've been redundant.
This will fail if you have two low bids of the exact same amount for the same item, since the join is on bid_amount. If you use this, then you'd want to created an index on vb_bids covering bid_amount.
select item_name, item_reserve, count(bid_id) as number_of_bids,
low_bid.bid_amount as lowest_bid, low_user.first_name + ' ' + low_user.last_name,
high_bid.bid_amount as highest_bid, high_user.first_name + ' ' + high_user.last_name
from vb_items
join vb_bids AS low_bid on item_id = low_bid.bid_item_id
AND low_bid.bid_amount = (
SELECT MIN(bid_amount)
FROM vb_bids
WHERE bid_item_id = low_bid.bid_item_id)
join vb_bids AS high_bid on item_id = high_bid.bid_item_id
AND high_bid.bid_amount = (
SELECT MAX(bid_amount)
FROM vb_bids
WHERE bid_item_id = high_bid.bid_item_id)
join vb_users AS low_user on low_bid.user_id=user_id
join vb_users AS high_user on high_bid.user_id=user_id
where bid_status = ‘ok’ and
item_sold = ‘no’
group by item_name, item_reserve,
low_bid.bid_amount, low_user.first_name, low_user.last_name,
high_bid.bid_amount, high_user.first_name, high_user.last_name
order by item_reserve
I am a big fan of using Common Table Expressions (CTEs) for situations like this, because of the following advantages:
Separating different parts of the logic, adding to readability, and
Reducing complexity (for example, the need to GROUP BY a large number of fields, or to repeat the same join multiple times.)
So, my suggested approach would be something like this:
-- semi-colon must precede CTE
;
-- collect bid info
WITH item_bids AS (
SELECT
i.item_id, i.item_name, i.item_reserve, b.bid_id, b.bid_amount,
(u.first_name + ' ' + u.last_name) AS bid_user_name
FROM vb_items i
JOIN vb_bids b ON i.item_id = b.bid_item_id
JOIN vb_users u ON b.user_id = u.user_id
WHERE b.bid_status = 'ok'
AND i.item_sold = 'no'
),
-- group bid info
item_bid_info AS (
SELECT item_id, item_name, item_reserve
COUNT(bid_id) AS number_of_bids, MIN(bid_amount) AS lowest_bid, MAX(bid_amount) AS highest_bid
FROM item_bids
GROUP BY item_id, item_name, item_reserve
)
-- assemble final result
SELECT
bi.item_name, bi.item_reserve, bi.number_of_bids,
bi.low_bid, low_bid.bid_user_name AS low_bid_user,
bi.high_bid, high_bid.bid_user_name AS high_bid_user
FROM item_bid_info bi
JOIN item_bids AS low_bid ON bi.lowest_bid = low_bid.bid_amount AND bi.item_id = low_bid.bid_item_id
JOIN item_bids AS high_bid ON bi.lowest_bid = high_bid.bid_amount AND bi.item_id = high_bid.bid_item_id
ORDER BY bi.item_reserve;
Note that the entire SQL statement (from the starting WITH all the way down to the final semi-colon after the ORDER BY) is a single statement, and is evaluated by the optimizer as such. (Some people think each part is evaluated separately, like temp tables, and then all the rows are joined together at the end in a final step. That's not how it works. CTEs are just as efficient as sub-queries.)
Also note that this approach does a JOIN on the bid amount, so if there are identical bids for a single item, it will fail. (Seems like that should be an invalid state anyway, though, right?) Also you may have efficiency concerns depending on:
The size of your table
Whether the lookup can use an index
You could address both issues by including a unique constraint (which has the added advantage of indexing the foreign key bid_item_id as well; always a good practice):
ALTER TABLE [dbo].[vb_bids] ADD CONSTRAINT [UK_vbBids_item_amount]
UNIQUE NONCLUSTERED (bid_item_id, bid_amount)
GO
Hope that helps!

Get num of ROWS in other table

I have two tables EXERCISE and EXERCISEUSER. I need to list all exercise entries and put an additional field in the query, which will return if that exercise exists in the table EXERCISEUSER. In other words, I need know if the user did that exercise. If so, it will have a row in EXERCISEUSER.
My current query is:
SELECT
"E".*,
"T"."NAME" AS "LEVEL"
FROM
"EXERCISE" AS "E"
INNER JOIN
"EXERCISETYPE" AS "T"
ON
E.STO_FK_EXERCISETYPEEXERCISE = T.PK_EXERCISETYPE
INNER JOIN
"LEVEL" AS "L"
ON
L.PK_LEVEL = E.STO_FK_LEVELEXERCISE
WHERE
(
E.STATUS = 1)
AND (
L.STATUS = 1)
AND (
L.PK_LEVEL = 5)
ORDER BY
"T"."ORDER" ASC
I will provide PK_USER too.
Thanks!
Well, i use a subquery, and reach the result i want.
SELECT
"E".*,
"T"."NAME" AS "LEVEL",
( SELECT COUNT(*) FROM STOUSER.EXERCISEUSER AS EU WHERE EU.STO_FK_EXERCISEEXERCISEUSER = E.PK_EXERCISE AND EU.STO_FK_USEREXERCISEUSER = 5978 ) AS MAKE_EXER_NUM
FROM
"STOUSER"."EXERCISE" AS "E"
INNER JOIN
"STOUSER"."EXERCISETYPE" AS "T"
ON
E.STO_FK_EXERCISETYPEEXERCISE = T.PK_EXERCISETYPE
INNER JOIN
"STOUSER"."LEVEL" AS "L"
ON
L.PK_LEVEL = E.STO_FK_LEVELEXERCISE
WHERE
(
E.STATUS = 1)
AND (
L.STATUS = 1)
AND (
L.PK_LEVEL = 5)
ORDER BY
"T"."ORDER" ASC
Thanks!
I think this should be done with a LEFT OUTER JOIN.