Order by in sub query using oracle - oracle

I tried to get value from sub query after ordering the records of it but the following occurred when execute the query :
ORA-00907: missing right parenthesis
The Query is :
select S.value , nvl((select D.value from D
join T on D.subID = t.SubID
where D.subid2 = s.subid2 and t.subid3 = s.subid3 and rownum = 1 order by t.id),0 ) value
from S

You can't have ORDER BY clause in a subquery.
See if something like this helps: use a CTE (as it looks somewhat nicer; could be a normal subquery, if you want) which calculates ordinal number for all rows, sorted by t.id column value. In outer (main) query, select row whose rn = 1 (which should act just like your ORDER BY t.id + rownum = 1).
WITH
temp
AS
(SELECT s.VALUE s_value,
d.VALUE d_value,
ROW_NUMBER () OVER (ORDER BY t.id) rn
FROM d
JOIN t ON d.subid = t.subid
JOIN s
ON s.subid2 = d.subid2
AND s.subid3 = t.subid3)
SELECT s_value, NVL (d_value, 0) d_value
FROM temp
WHERE rn = 1

If you are on Oracle 12c or higher, you can use the FETCH FIRST... clause.
SELECT S.VALUE,
NVL (( SELECT D.VALUE
FROM D JOIN T ON D.subID = t.SubID
WHERE D.subid2 = s.subid2 AND t.subid3 = s.subid3
ORDER BY t.id
FETCH FIRST 1 ROWS ONLY),
0) VALUE
FROM S

Related

Adding filters in subquery from CTE quadruples run time

I am working on an existing query for SSRS report that focuses on aggregated financial aid data split out into 10 aggregations. User wants to be able to select students included in that aggregated data based on new vs. returning and 'selected for verification.' For the new/returning status, I added a CTE to return the earliest admit date for a student. 2 of the 10 data fields are created by a subquery. I have been trying for 3 days to get the subquery to use the CTE fields for a filter, but they won't work. Either they're ignored or I get a 'not a group by expression' error. If I put the join to the CTE within the subquery, the query time jumps from 45 second to 400 seconds. This shouldn't be that complicated! What am I missing? I have added some of the code... 3 of the chunks work - paid_something doesn't.
with stuStatus as
(select
person_uid, min(year_admitted) admit_year
from academic_study
where aid_year between :AidYearStartParameter and :AidYearEndParameter
group by person_uid)
--- above code added to get student information not originally in qry
select
finaid_applicant_status.aid_year
, count(1) as fafsa_cnt --works
, sum( --works
case
when (
package_complete_date is not null
and admit.status is not null
)
then 1
else 0
end
) as admit_and_package
, (select count(*) --does't work
from (
select distinct award_by_aid_year.person_uid
from
award_by_aid_year
where
award_by_aid_year.aid_year = finaid_applicant_status.aid_year
and award_by_aid_year.total_paid_amount > 0 )dta
where
(
(:StudentStatusParameter = 'N' and stuStatus.admit_year = finaid_applicant_status.aid_year)
OR
(:StudentStatusParameter = 'R' and stuStatus.admit_year <> finaid_applicant_status.aid_year)
OR :StudentStatusParameter = '%'
)
)
as paid_something
, sum( --works
case
when exists (
select
1
from
award_by_person abp
where
abp.person_uid = fafsa.person_uid
and abp.aid_year = fafsa.aid_year
and abp.award_paid_amount > 0
) and fafsa.requirement is not null
then 1
else 0
end
) as paid_something_fafsa
from
finaid_applicant_status
join finaid_tracking_requirement fafsa
on finaid_applicant_status.person_uid = fafsa.person_uid
and finaid_applicant_status.aid_year = fafsa.aid_year
and fafsa.requirement = 'FAFSA'
left join finaid_tracking_requirement admit
on finaid_applicant_status.person_uid = admit.person_uid
and finaid_applicant_status.aid_year = admit.aid_year
and admit.requirement = 'ADMIT'
and admit.status in ('M', 'P')
left outer join stuStatus
on finaid_applicant_status.person_uid = stuStatus.person_uid
where
finaid_applicant_status.aid_year between :AidYearStartParameter and :AidYearEndParameter
and (
(:VerifiedParameter = '%') OR
(:VerifiedParameter <> '%' AND finaid_applicant_status.verification_required_ind = :VerifiedParameter)
)
and
(
(:StudentStatusParameter = 'N' and (stuStatus.admit_year IS NULL OR stuStatus.admit_year = finaid_applicant_status.aid_year ))
OR
(:StudentStatusParameter = 'R' and stuStatus.admit_year <> finaid_applicant_status.aid_year)
OR :StudentStatusParameter = '%'
)
group by
finaid_applicant_status.aid_year
order by
finaid_applicant_status.aid_year
Not sure if this helps, but you have something like this:
select aid_year, count(1) c1,
(select count(1)
from (select distinct person_uid
from award_by_aid_year a
where a.aid_year = fas.aid_year))
from finaid_applicant_status fas
group by aid_year;
This query throws ORA-00904 FAS.AID_YEAR invalid identifier. It is because fas.aid_year is nested too deep in subquery.
If you are able to modify your subquery from select count(1) from (select distinct sth from ... where year = fas.year) to select count(distinct sth) from ... where year = fas.year then it has the chance to work.
select aid_year, count(1) c1,
(select count(distinct person_uid)
from award_by_aid_year a
where a.aid_year = fas.aid_year) c2
from finaid_applicant_status fas
group by aid_year
Here is simplified demo showing non-working and working queries. Of course your query is much more complicated, but this is something what you could check.
Also maybe you can use dbfiddle or sqlfiddle to set up some test case? Or show us sample (anonimized) data and required output for them?

How to pass parameter from query to subquery ? ORA-00904

I get ORA-00904 invalid identifier error, from this query
SELECT
tab1."col1" AS ID,
tab1."col4" AS Name,
tab1."col5" AS Place,
(SELECT SUBSTR (SYS_CONNECT_BY_PATH (one_row , ';'), 2) myConString
FROM (SELECT tab2."col3" || ',' || tab2."col4" AS one_row,
ROW_NUMBER () OVER(ORDER BY tab2."col1") rn,
COUNT (*) OVER () cnt
FROM dbo."table2" tab2
WHERE tab2."col1" = tab1."col1"
AND tab2."col2" = tab1."col2")
WHERE rn = cnt
START WITH rn = 1
CONNECT BY rn = PRIOR rn + 1)
FROM dbo."table1" tab1
WHERE tab1."col1" IN (1,2,3)
AND tab1."col2" = 1 AND tab1."col3" = 1;
in this specific place
tab2."col1" = tab1."col1" AND tab2."col2" = tab1."col2"
In the subquery I concatenate rows into string and it works great and give me the right results, something like
1,100;1,200;2,150....
I think problem is that I try to refer to objects more then one level of subquery, but I can't figure it out, how to rewrite the query.
Thanks for any help
Correlated subqueries can only reference things one level deep. Your tab1 table is two levels away from your tab2 table.
I can't quite wrap my head around your query, but can you rewrite this so that you have a join between tab1 and tab2 instead of having a correlated query in the select clause?

Update with sum

I have a table with the following fields:
ID, VALUES, VARIAB
Im trying to SUM the field VALUES, but it needs to be grouped by ID.
And the Subselect doesnt accept multiple lines. (When i use it with just 1 ID, works fine).
BEGIN
UPDATE TBL2
SET SOMA =
(SELECT SUM(x.VALUES)
FROM TBL3 x INNER JOIN TBL2 y
ON y.ID = x.ID
WHERE y.ID = x.ID AND X.VARIAB = 1
GROUP BY x.ID
);
END;
Im using ORACLE DB, if anyone comment this ill be very gratefull.
Sorry for my english flaws.
To update multiple records at once and/or do an upsert, use MERGE statement like this :
MERGE INTO TBL2 T2 USING (
SELECT x.id,SUM(x.VALUES) total
FROM TBL3 x
WHERE x.VARIAB = 1
GROUP BY x.ID
) T3 ON (T2.ID = T3.ID)
WHEN MATCHED THEN UPDATE
SET T2.SOMA = T3.TOTAL
The issue is that your update statement isn't correlated - ie. your subquery has no reference to the table being updated.
You could change it to:
update tbl2 t2
set soma = (select sum(t3.values)
from tbl3 t3
where t2.id = t3.id
and t3.variab = 1);

ORACLE QUERY - OPTIMISATION

I have written the below query which has many 'AND' operators, i would like to know how to optimize the performance of the below query [can i remove some of the 'AND' Operators]
SELECT I.date,
K.somcolumn,
L.somcolumn,
D.somcolumn
FROM Table1 I,
Table2 K,
Table3 L,
Table4 D
WHERE I._ID = K._ID
AND K.ID = L._ID
AND L._ID = I._ID
AND I._CODE = L._CODE
AND K.ID = D._ID(+)
AND L._ID IN ( SELECT _id
FROM I
WHERE UPPER (someflag) = 'TRUE'
GROUP BY _id
HAVING COUNT (*) > 1)
AND L._ID IN ( SELECT _id
FROM I
WHERE UPPER (code) = 'OPEN'
GROUP BY _id
HAVING COUNT (*) > 1)
ORDER BY I._ID, I._CODE;
You can't combine any of the conditions as far as I can tell, but you can improve the query and reduce the number of AND operators by using standard JOIN syntax:
SELECT I.date,
K.somcolumn,
L.somcolumn,
D.somcolumn
FROM Table1 I
INNER JOIN Table2 K ON I._ID = K._ID
INNER JOIN Table3 L ON K.ID = L._ID
LEFT JOIN Table4 D ON K.ID = D._ID
WHERE L._ID IN ( SELECT _id
FROM I
WHERE UPPER (someflag) = 'TRUE'
GROUP BY _id
HAVING COUNT (*) > 1)
AND L._ID IN ( SELECT _id
FROM I
WHERE UPPER (code) = 'OPEN'
GROUP BY _id
HAVING COUNT (*) > 1)
ORDER BY I._ID, I._CODE;
With that as a basis, you may get an optimization boost if you join to the subquery conditions rather than using correlated subqueries. No guarantees, but something like this may help:
SELECT I.date,
K.somcolumn,
L.somcolumn,
D.somcolumn
FROM Table1 I
INNER JOIN Table2 K ON I._ID = K._ID
INNER JOIN Table3 L ON K.ID = L._ID
LEFT JOIN Table4 D ON K.ID = D._ID
INNER JOIN (
SELECT _id
FROM I
WHERE UPPER (someflag) = 'TRUE'
GROUP BY _id
HAVING COUNT (*) > 1
) someflagtrue ON L._ID = someflagtrue._id
INNER JOIN (
SELECT _id
FROM I
WHERE UPPER (code) = 'OPEN'
GROUP BY _id
HAVING COUNT (*) > 1
) codeopen ON L._ID = codeopen._id
ORDER BY I._ID, I._CODE;
You can replace the two subqueries with one.
Old subqueries:
SELECT _id
FROM I
WHERE UPPER (someflag) = 'TRUE'
GROUP BY _id
HAVING COUNT (*) > 1)
SELECT _id
FROM I
WHERE UPPER (code) = 'OPEN'
GROUP BY _id
HAVING COUNT (*) > 1)
New subquery:
SELECT _ID
FROM I
GROUP BY _ID
HAVING COUNT(CASE WHEN UPPER(SOMEFLAG) = 'TRUE' THEN 1 ELSE 0 END) > 0
AND COUNT(CASE WHEN UPPER(CODE) = 'OPEN' THEN 1 ELSE 0 END) > 0
In most cases this should be at least a little faster as it may reduce the number of full table scans and joins. But it's difficult to tell if it will be faster on your system since there are so many possible choices for the optimizer to make.
After cleaning up the query the next step for performance tuning is to generate an explain plan. Run explain plan for select ...; and then run select * from table(dbms_xplan.display); That will show you how the query is executed, which may give you a hint at what is slow and what can be improved. Add the full output of th explain plan to your question if you need more help. It may also help to add information about the number of rows in the relevant tables, how many rows are returned, and what are the indexes.

CROSS APPLY too slow for running total - TSQL

Please see my code below as it is running too slowly with the CROSS APPLY.
How can I remove the CROSS APPLY and add something else that will run faster?
Please note I am using SQL Server 2008 R2.
;WITH MyCTE AS
(
SELECT
R.NetWinCURRENCYValue AS NetWin
,dD.[Date] AS TheDay
FROM
dimPlayer AS P
JOIN
dbo.factRevenue AS R ON P.playerKey = R.playerKey
JOIN
dbo.vw_Date AS dD ON Dd.dateKey = R.dateKey
WHERE
P.CustomerID = 12345)
SELECT
A.TheDay AS [Date]
,ISNULL(A.NetWin, 0) AS NetWin
,rt.runningTotal AS CumulativeNetWin
FROM MyCTE AS A
CROSS APPLY (SELECT SUM(NetWin) AS runningTotal
FROM MyCTE WHERE TheDay <= A.TheDay) AS rt
ORDER BY A.TheDay
CREATE TABLE #temp (NetWin money, TheDay datetime)
insert into #temp
SELECT
R.NetWinCURRENCYValue AS NetWin
,dD.[Date] AS TheDay
FROM
dimPlayer AS P
JOIN
dbo.factRevenue AS R ON P.playerKey = R.playerKey
JOIN
dbo.vw_Date AS dD ON Dd.dateKey = R.dateKey
WHERE
P.CustomerID = 12345;
SELECT
A.TheDay AS [Date]
,ISNULL(A.NetWin, 0) AS NetWin
,SUM(B.NetWin) AS CumulativeNetWin
FROM #temp AS A
JOIN #temp AS B
ON A.TheDay >= B.TheDay
GROUP BY A.TheDay, ISNULL(A.NetWin, 0);
Here https://stackoverflow.com/a/13744550/613130 it's suggested to use recursive CTE.
;WITH MyCTE AS
(
SELECT
R.NetWinCURRENCYValue AS NetWin
,dD.[Date] AS TheDay
,ROW_NUMBER() OVER (ORDER BY dD.[Date]) AS RN
FROM dimPlayer AS P
JOIN dbo.factRevenue AS R ON P.playerKey = R.playerKey
JOIN dbo.vw_Date AS dD ON Dd.dateKey = R.dateKey
WHERE P.CustomerID = 12345
)
, MyCTERec AS
(
SELECT C.TheDay AS [Date]
,ISNULL(C.NetWin, 0) AS NetWin
,ISNULL(C.NetWin, 0) AS CumulativeNetWin
,C.RN
FROM MyCTE AS C
WHERE C.RN = 1
UNION ALL
SELECT C.TheDay AS [Date]
,ISNULL(C.NetWin, 0) AS NetWin
,P.CumulativeNetWin + ISNULL(C.NetWin, 0) AS CumulativeNetWin
,C.RN
FROM MyCTERec P
INNER JOIN MyCTE AS C ON C.RN = P.RN + 1
)
SELECT *
FROM MyCTERec
ORDER BY RN
OPTION (MAXRECURSION 0)
Note that this query will work if you have 1 record == 1 day! If you have multiple records in a day, the results will be different from the other query.
As I said here, if you want really fast calculation, put it into temporary table with sequential primary key and then calculate rolling total:
create table #Temp (
ID bigint identity(1, 1) primary key,
[Date] date,
NetWin decimal(29, 10)
)
insert into #Temp ([Date], NetWin)
select
dD.[Date],
sum(R.NetWinCURRENCYValue) as NetWin,
from dbo.dimPlayer as P
inner join dbo.factRevenue as R on P.playerKey = R.playerKey
inner join dbo.vw_Date as dD on Dd.dateKey = R.dateKey
where P.CustomerID = 12345
group by dD.[Date]
order by dD.[Date]
;with cte as (
select T.ID, T.[Date], T.NetWin, T.NetWin as CumulativeNetWin
from #Temp as T
where T.ID = 1
union all
select T.ID, T.[Date], T.NetWin, T.NetWin + C.CumulativeNetWin as CumulativeNetWin
from cte as C
inner join #Temp as T on T.ID = C.ID + 1
)
select C.[Date], C.NetWin, C.CumulativeNetWin
from cte as C
order by C.[Date]
I assume that you could have duplicates dates in the input, but don't want duplicates in the output, so I grouped data before puting it into the table.

Resources