scalar subquery has an aggregate operation - oracle

My oracle version is 10.2.
It's very strange when a scalar subquery has an aggregate operation.
my table named t_test looked like this;
t_id t_name
1 1
2 1
3 2
4 2
5 3
6 3
query string looked like this;
select t1.t_id,
(select count(t_name)
from (select t2.t_name
from t_test t2
where t2.t_id=t1.t_id
group by t2.t_name)) a
from t_test t1
this query's result is,
t_id a
1 3
2 3
3 3
4 3
5 3
6 3
which is very weird,
take t1.t_id=1 for example,
select count(t_name)
from (select t2.t_name
from t_test t2
where t2.t_id=1
group by t2.t_name)
the result is 1,
somehow,the 'where' operator doesn't work,the result is exactly the same as I put my query like this:
select t1.t_id,
(select count(t_name)
from (select t2.t_name
from t_test t2
group by t2.t_name)) a
from t_test t1
why?

Can you post a cut-and-paste from SQL*Plus showing exactly what query you're running? The query you posted does not appear to be valid-- the alias t1 is not going to be valid in the subquery where you're referencing it. That makes me suspect that you're simplifying the problem to post here but you've accidentally left something important out.
SQL> ed
Wrote file afiedt.buf
1 with x as (
2 select 1 id, 1 name from dual union all
3 select 2,1 from dual union all
4 select 3,2 from dual union all
5 select 4,2 from dual union all
6 select 5,3 from dual union all
7 select 6,3 from dual
8 )
9 select t1.id
10 ,(select count(b.name)
11 from (select t2.name
12 from x t2
13 where t2.id = t1.id
14 group by t2.name) b) a
15* from x t1
SQL> /
where t2.id = t1.id
*
ERROR at line 13:
ORA-00904: "T1"."ID": invalid identifier
Presumably, it would be much more natural to write the query like this (assuming you really want to use a scalar subquery) where t1 is going to be a valid alias in the scalar subquery.
SQL> ed
Wrote file afiedt.buf
1 with x as (
2 select 1 id, 1 name from dual union all
3 select 2,1 from dual union all
4 select 3,2 from dual union all
5 select 4,2 from dual union all
6 select 5,3 from dual union all
7 select 6,3 from dual
8 )
9 select t1.id
10 ,(select count(t2.name)
11 from x t2
12 where t2.id = t1.id) cnt
13* from x t1
SQL> /
ID CNT
---------- ----------
1 1
2 1
3 1
4 1
5 1
6 1
6 rows selected.

Related

How to insert values to newly added column

I am new to oracle thus this question.
There is a table already existed and I have added a new column to it.
There are 5 rows and I do not want to use update table with where clause to insert the values one by one in the new column. Is there a statement like INSERT ALL to insert the values into the new column in one shot ?
Thanks
You can also use something like below which in-effect I would say is multiple update only, wrapped in single statement.
SQL> select * from test_upd;
ID1 ID2
---------- ----------
1
2
3
4
SQL> update test_upd a set a.id2 =
2 (select
3 case
4 when id1=1 then 100
5 when id1=2 then 200
6 when id1=3 then 300
7 else 5000 end
8 from test_upd b
9 where a.id1=b.id1);
4 rows updated.
SQL> select * from test_upd;
ID1 ID2
---------- ----------
1 100
2 200
3 300
4 5000
Use a MERGE statement:
MERGE INTO your_table dst
USING (
SELECT 1 AS id, 'aaa' AS newvalue FROM DUAL UNION ALL
SELECT 2, 'bbb' FROM DUAL UNION ALL
SELECT 3, 'ccc' FROM DUAL UNION ALL
SELECT 4, 'ddd' FROM DUAL UNION ALL
SELECT 5, 'eee' FROM DUAL
) src
ON (dst.id = src.id)
WHEN MATCHED THEN
UPDATE SET value = src.newvalue;
Which, if you have the table:
CREATE TABLE your_table (id, value) AS
SELECT 1, CAST(NULL AS VARCHAR2(3)) FROM DUAL UNION ALL
SELECT 2, NULL FROM DUAL UNION ALL
SELECT 3, NULL FROM DUAL UNION ALL
SELECT 4, NULL FROM DUAL UNION ALL
SELECT 5, NULL FROM DUAL;
Then, after the MERGE, the table contains:
ID
VALUE
1
aaa
2
bbb
3
ccc
4
ddd
5
eee
db<>fiddle here

Return Count Value back as 0 if emtpy or none

I am running the following query in our database and I am trying to get the count value back as 0 if none are present, but having problems doing so. Could someone help assist and explain how to accomplish this
select state, count(phone_number) from VOIP_PHONE_NUMBER_POOL
where status = 3
group by state
order by state asc;
For example, currently my query is returning something back like this
State Count
AZ 25
When I want it to return the empty values and look like this
State Count
AZ 25
CA 0
This mimics what you have now:
SQL> with voip_phone_number_pool (state, phone_number, status) as
2 (select 'AZ', 1234, 3 from dual union all
3 select 'AZ', 2232, 3 from dual union all
4 select 'AZ', 4444, 3 from dual union all
5 select 'AZ', 7756, 1 from dual union all
6 --
7 select 'CA', 9999, 1 from dual
8 )
9 select state,
10 count(phone_number)
11 from voip_phone_number_pool
12 where status = 3
13 group by state
14 order by state;
ST COUNT(PHONE_NUMBER)
-- -------------------
AZ 3
SQL>
There's no row for the CA state which has status = 3, so it isn't returned by that query.
What you could do, is to use outer self-join in such a manner:
SQL> with voip_phone_number_pool (state, phone_number, status) as
2 (select 'AZ', 1234, 3 from dual union all
3 select 'AZ', 2232, 3 from dual union all
4 select 'AZ', 4444, 3 from dual union all
5 select 'AZ', 7756, 1 from dual union all
6 --
7 select 'CA', 9999, 1 from dual
8 ),
9 states as
10 (select distinct state from voip_phone_number_pool)
11 select s.state,
12 count(v.phone_number)
13 from states s left join voip_phone_number_pool v on v.state = s.state
14 and v.status = 3
15 group by s.state
16 order by s.state;
ST COUNT(V.PHONE_NUMBER)
-- ---------------------
AZ 3
CA 0
SQL>
the states CTE selects all states
it is then used in outer join with voip_phone_number_pool
note that condition (status = 3) has to be moved into join (line #14); if you leave it in the where clause, you won't get desired result
If there are no rows in the fact table (VOIP_PHONE_NUMBER_POOL) with a particular state, you will not get such a result from only querying that table.
You will need to join your fact table to a dimension table (a table with all possible states) in order to get the desired counts.
For example, assuming you have such a dimension table and it is named "state_info":
SELECT s.state,
COUNT(v.phone_number) AS phone_number_count
FROM state_info s
LEFT OUTER JOIN voip_phone_number_pool v
ON s.state = v.state
GROUP BY s.state;
SELECT s.state,
COUNT(v.phone_number) AS phone_number_count
FROM CORP_ACCT s
LEFT OUTER JOIN voip_phone_number_pool v
ON s.state = v.state
GROUP BY s.state;
worked for me

Select default in case of no value returned

I am trying to get some default value in my resultset if query does not return anything. I am trying nvl for the same but it is not returning the expected default value. To simulate, Consider following query,
select nvl(null, '10') from dual where 1=0;
I want to get 10 in case of given condition is not true and query does not return any value. However above query not returning any row.
Your query returns zero rows. NVL() isn't going to change that (*).
The correct solution is for the program which executes the query to handle NO_DATA_FOUND exception rather than fiddling the query.
However, you need a workaround so here is one using two sub-queries, one for your actual query, one to for the default.
When your_query returns an empty set you get this:
SQL> with your_qry as
2 ( select col1 from t42 where 1=0 )
3 , dflt as
4 ( select 10 as col1 from dual )
5 select col1
6 from your_qry
7 union all
8 select col1
9 from dflt
10 where not exists (select * from your_qry );
COL1
----------
10
SQL>
And when it returns a row you get this:
SQL> with your_qry as
2 ( select col1 from t42 )
3 , dflt as
4 ( select 10 as col1 from dual )
5 select col1
6 from your_qry
7 union all
8 select col1
9 from dflt
10 where not exists (select * from your_qry );
COL1
----------
12
13
SQL>
The WITH clause is optional here, it just makes it easier to write the query without duplication. This would have the same outcome:
select col1
from t42
where col0 is null
union all
select 10
from dual
where not exists (select col1
from t42
where col0 is null)
;
(*) Okay, there are solutions which use NVL() or COALESCE() with aggregations to do this. They work with single column projections in a single row as this question poses, but break down when the real query has more than one row and/or more than one column. Aggregations change the results.
So this looks alright ...
SQL> with cte as (
2 select 'Z' as col0, 12 as col1 from dual where 1=0 union all
3 select 'X' as col0, 13 as col1 from dual where 1=0 )
4 select
5 nvl(max(col0), 'Y') as col0, nvl(max( col1), 10) as col1
6 from cte;
COL0 COL1
---------- ----------
Y 10
SQL>
... but this not so much:
SQL> with cte as (
2 select 'Z' as col0, 12 as col1 from dual union all
3 select 'X' as col0, 13 as col1 from dual )
4 select
5 nvl(max(col0), 'Y') as col0, nvl(max( col1), 10) as col1
6 from cte;
COL0 COL1
---------- ----------
Z 13
SQL>
May be something like this is what you need
You could change WHERE clause (in this case WHERE COL > 1) similarly in both places.
WITH T(COL) AS(
SELECT 1 FROM DUAL UNION ALL
SELECT 2 FROM DUAL UNION ALL
SELECT 3 FROM DUAL
)
SELECT COL FROM T WHERE COL > 1
UNION ALL
SELECT 10 AS COL FROM DUAL WHERE NOT EXISTS( SELECT 1 FROM T WHERE COL > 1)
You can use aggregation. An aggregation query always returns one row:
select coalesce(max(null), '10')
from dual
where 1 = 0;
I prefer coalesce() to nvl() because coalesce() is the ANSI standard function. But, nvl() would work here just as well.

How to breakdown data by month and showing zero for months with no data?

Using information in Table A, how can I produce results in Table B below?
Table A:
CASE_ID DATE_EFF COPAY STATUS
1 11/04/2016 10 A
1 11/20/2016 5 A
1 11/23/2016 5 R
1 12/01/2016 1 A
1 12/10/2016 2 A
1 12/12/2016 10 A
1 12/31/2016 50 R
For the above CASE_ID, we have dates in Nov 2016 and Dec 2016 only, however, I want to produce a breakdown of this CASE_ID for a period of 6 months as below where for each month the copays are summed where applicable as per the DATE_EFF and for the months that are not within the above dates, a zero is entered. Also, only records with copays with a status of 'A' are summed for any month -- so those with status of 'R' are ignored in the summation. For example, based on data in Table A above, the intended results are as follow:
Table B:
CASE_ID MONTH TOTAL_COPAY
1 01/2017 0
1 12/2016 13
1 11/2016 15
1 10/2016 0
1 09/2016 0
1 08/2016 0
I have below as a possible solution[using a with clause], but can this be achieved without the use of the 'with' clause?
Possible Solution:
WITH
XRF AS
( SELECT CASE_ID, COPAY, DATE_EFF
FROM Table_A WHERE STATUS = 'A'
)
SELECT F.CASE_ID, ST, NVL(SUM(F.COPAY),0) TOTAL_COPAY FROM XRF F PARTITION BY (F.CASE_ID)
RIGHT OUTER JOIN (SELECT '12/2016' ST FROM DUAL UNION ALL
SELECT '11/2016' FROM DUAL UNION ALL
SELECT '10/2016' FROM DUAL UNION ALL
SELECT '09/2016' FROM DUAL UNION ALL
SELECT '08/2016' FROM DUAL UNION ALL
SELECT '07/2016' FROM DUAL) STS
ON (TO_CHAR(LAST_DAY((F.DATE_EFF)),'MM/YYYY') = STS.ST)
GROUP BY F.CASE_ID, ST ORDER BY F.CASE_ID, ST DESC
;
UPDATE AND SOLUTION:
Using the above query, I believe I am have answered my own question by implementing it as below -- not sure though if using this method is expensive when you have millions of records of such CASE_IDs. Any thoughts?
SELECT F.CASE_ID, ST, NVL(SUM(F.COPAY),0) TOTAL_COPAY FROM (SELECT CASE_ID, COPAY, DATE_EFF FROM TABLE_A WHERE STATUS = 'A') F PARTITION BY (F.CASE_ID)
RIGHT OUTER JOIN (SELECT '12/2016' ST FROM DUAL UNION ALL
SELECT '11/2016' FROM DUAL UNION ALL
SELECT '10/2016' FROM DUAL UNION ALL
SELECT '09/2016' FROM DUAL UNION ALL
SELECT '08/2016' FROM DUAL UNION ALL
SELECT '07/2016' FROM DUAL) STS
ON (TO_CHAR(LAST_DAY((F.DATE_EFF)),'MM/YYYY') = STS.ST)
GROUP BY F.CASE_ID, ST ORDER BY F.CASE_ID, ST DESC
;

plsql: Getting another field values along with the aggregation values in a grouping statement

I am working on a time attendance system. I have the employees' transactions stored in the following table:
I want to get the earliest and the latest transactions for each employee including their date and type.
I am able to get the dates using grouping and aggregation. However, I am not able to figure out how to get types with them.
Would you please help me in it.
Thank you.
That's what the FIRST and LAST aggregate functions are designed for.
Here is a link to the documentation:
FIRST: http://download.oracle.com/docs/cd/E11882_01/server.112/e17118/functions065.htm#SQLRF00641
LAST: http://download.oracle.com/docs/cd/E11882_01/server.112/e17118/functions083.htm#sthref1206
And here is an example:
SQL> create table my_transactions (id,employee_id,action_date,type)
2 as
3 select 1, 1, sysdate, 'A' from dual union all
4 select 2, 1, sysdate-1, 'B' from dual union all
5 select 3, 1, sysdate-2, 'C' from dual union all
6 select 4, 1, sysdate-3, 'D' from dual union all
7 select 5, 2, sysdate-11, 'E' from dual union all
8 select 6, 2, sysdate-12, 'F' from dual union all
9 select 7, 2, sysdate-13, 'G' from dual
10 /
Table created.
SQL> select *
2 from my_transactions
3 order by id
4 /
ID EMPLOYEE_ID ACTION_DATE T
---------- ----------- ------------------- -
1 1 04-07-2011 10:15:07 A
2 1 03-07-2011 10:15:07 B
3 1 02-07-2011 10:15:07 C
4 1 01-07-2011 10:15:07 D
5 2 23-06-2011 10:15:07 E
6 2 22-06-2011 10:15:07 F
7 2 21-06-2011 10:15:07 G
7 rows selected.
SQL> select employee_id
2 , min(action_date) min_date
3 , max(type) keep (dense_rank first order by action_date) min_date_type
4 , max(action_date) max_date
5 , max(type) keep (dense_rank last order by action_date) max_date_type
6 from my_transactions
7 group by employee_id
8 /
EMPLOYEE_ID MIN_DATE M MAX_DATE M
----------- ------------------- - ------------------- -
1 01-07-2011 10:15:07 D 04-07-2011 10:15:07 A
2 21-06-2011 10:15:07 G 23-06-2011 10:15:07 E
2 rows selected.
Regards,
Rob.
You could try to use analytical(or windowing functions)
select *
from
(select id, employee_id, action_date,type,
max(action_date) over (partition by employee_id) max_action_date,
min(action_date) over (partition by employee_id) min_action_date
from transaction)
where action_date in (max_action_date, min_action_date)

Resources