oracle 12c query is slow - oracle

below code works well in 11g BUT only in 12c works so slow (over 10 sec).
SELECT * FROM DMProgValue_00001
WHERE 1=1
AND ProgressOID IN (
SELECT P.OID FROM (
SELECT OID FROM (
SELECT A.OID, ROWNUM as seqNum FROM (
SELECT OID FROM DMProgress_00001 WHERE 1=1
AND Project = 'Q539'
ORDER BY actCode
) A
WHERE ROWNUM <= 40
) WHERE seqNum > 20
) P
);
Plan hash value: 763232015
-----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 189 | 171 (1)| 00:00:01 |
|* 1 | FILTER | | | | | |
| 2 | TABLE ACCESS FULL | DMPROGVALUE_00001 | 1 | 189 | 68 (0)| 00:00:01 |
|* 3 | VIEW | | 20 | 800 | 103 (1)| 00:00:01 |
|* 4 | COUNT STOPKEY | | | | | |
| 5 | VIEW | | 2916 | 78732 | 103 (1)| 00:00:01 |
|* 6 | SORT ORDER BY STOPKEY| | 2916 | 130K| 103 (1)| 00:00:01 |
|* 7 | TABLE ACCESS FULL | DMPROGRESS_00001 | 2916 | 130K| 102 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter( EXISTS (SELECT 0 FROM (SELECT "A"."OID" "OID",ROWNUM "SEQNUM" FROM
(SELECT "OID" "OID" FROM "DMPROGRESS_00001" "DMPROGRESS_00001" WHERE
"PHASE"='Construction' AND "PROJECT"='Q539' ORDER BY "ACTCODE") "A" WHERE ROWNUM<=40)
"from$_subquery$_003" WHERE "SEQNUM">20 AND "OID"=:B1))
3 - filter("SEQNUM">20 AND "OID"=:B1)
4 - filter(ROWNUM<=40)
6 - filter(ROWNUM<=40)
7 - filter("PHASE"='Construction' AND "PROJECT"='Q539')
DMProgress_0001 stats
NUM_ROW : 10385
BLOCKS : 370
AVG_ROW_LEN : 176
SMAPLE_SIZE : 8263
DMProgvalue_0001 stats
NUM_ROW : 15703
BLOCKS : 244
AVG_ROW_LEN : 49
SMAPLE_SIZE : 5033
It's only 10k rows and Indexs are well made ( I can tell because of 11g experience). I know some detour way to make fast ( below code - 0.001 sec) BUT I want to know real problem and fix it.
I cannot understand it only has one sub query and 10k rows for each table. Not just compared to 11g, There is no way that this query takes over 10 sec.
SELECT * FROM DMProgValue_00001 V,
(SELECT OID FROM (
SELECT A.OID, ROWNUM as seqNum FROM (
SELECT OID FROM DMProgress_00001 WHERE 1=1
AND Project = 'Q539'
ORDER BY actCode
) A
WHERE ROWNUM <= 40
) WHERE seqNum > 20
) P
WHERE 1=1
AND V.ProgressOID = P.OID;
added 11g similar query plan
Plan hash value: 3049049852
-----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 9 | 684 | 49 (5)| 00:00:01 |
|* 1 | HASH JOIN RIGHT SEMI | | 9 | 684 | 49 (5)| 00:00:01 |
| 2 | VIEW | VW_NSO_1 | 3 | 81 | 35 (3)| 00:00:01 |
|* 3 | VIEW | | 3 | 75 | 35 (3)| 00:00:01 |
|* 4 | COUNT STOPKEY | | | | | |
| 5 | VIEW | | 3 | 36 | 35 (3)| 00:00:01 |
|* 6 | SORT ORDER BY STOPKEY| | 3 | 144 | 35 (3)| 00:00:01 |
|* 7 | TABLE ACCESS FULL | DMPROGRESS_00037 | 3 | 144 | 34 (0)| 00:00:01 |
| 8 | TABLE ACCESS FULL | DMPROGVALUE_00037 | 5106 | 244K| 13 (0)| 00:00:01 |
------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("PROGRESSOID"="OID")
3 - filter("SEQNUM">20)
4 - filter(ROWNUM<=40)
6 - filter(ROWNUM<=40)
7 - filter("DISPLINE"='Q340' AND "PHASE"='Procurement' AND "PROJECT"='Moho')
oracle 11g automatically change the query as Hash join BUT 12c does not. I think this is point. They are same structure.

Related

Increase in query execution time after generating statistics for the target table (Oracle DB)

I have created a new table (tmp_requests_5) in the database and populate it with 290k rows of test data. Right after that, I execute the following query:
select *
from tmp_requests_5 r
join request_statuses rs on ( rs.id = r.status_id )
left join users u on ( u.id = r.created_user_id )
left join executors e on ( e.id = r.executor_id )
order by r.id desc
It runs in 0.045 seconds with the following execution plan:
Execution plan before generating statistics
------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 361K| 10G| 1039K (1)| 00:00:41 |
| 1 | NESTED LOOPS | | 361K| 10G| 1039K (1)| 00:00:41 |
| 2 | NESTED LOOPS OUTER | | 361K| 10G| 678K (1)| 00:00:27 |
| 3 | NESTED LOOPS OUTER | | 361K| 10G| 335K (1)| 00:00:14 |
|* 4 | TABLE ACCESS BY INDEX ROWID| TMP_REQUESTS_5 | 361K| 10G| 622 (0)| 00:00:01 |
| 5 | INDEX FULL SCAN DESCENDING| TMP_REQUESTS_5_PK | 361K| | 622 (0)| 00:00:01 |
| 6 | TABLE ACCESS BY INDEX ROWID| USERS | 1 | 119 | 1 (0)| 00:00:01 |
|* 7 | INDEX UNIQUE SCAN | USERS_PK | 1 | | 0 (0)| 00:00:01 |
|* 8 | TABLE ACCESS BY INDEX ROWID | EXECUTORS | 1 | 50 | 1 (0)| 00:00:01 |
|* 9 | INDEX UNIQUE SCAN | EXECUTORS_PK | 1 | | 0 (0)| 00:00:01 |
| 10 | TABLE ACCESS BY INDEX ROWID | REQUEST_STATUSES | 1 | 69 | 1 (0)| 00:00:01 |
|* 11 | INDEX UNIQUE SCAN | REQUEST_STATUSES_PK | 1 | | 0 (0)| 00:00:01 |
------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
4 - filter(INTERNAL_FUNCTION("R"."DESCRIPTION" /*+ LOB_BY_VALUE */ ) AND
INTERNAL_FUNCTION("R"."RESOLUTION" /*+ LOB_BY_VALUE */ ) AND
INTERNAL_FUNCTION("R"."RESPONSE_TO_APPLICANT" /*+ LOB_BY_VALUE */ ))
7 - access("U"."ID"(+)="R"."CREATED_USER_ID")
8 - filter(INTERNAL_FUNCTION("E"."RESPONSIBILITY_AREA" /*+ LOB_BY_VALUE */ ))
9 - access("E"."ID"(+)="R"."EXECUTOR_ID")
11 - access("RS"."ID"="R"."STATUS_ID")
But after statistics are generated for the new table, the same query runs much slower (10 seconds), although the cost is reduced. Here is the execution plan after generating statistics:
Execution plan after generating statistics
----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 289K| 390M| | 100K (1)| 00:00:04 |
| 1 | SORT ORDER BY | | 289K| 390M| 453M| 100K (1)| 00:00:04 |
|* 2 | HASH JOIN RIGHT OUTER | | 289K| 390M| | 14375 (1)| 00:00:01 |
| 3 | TABLE ACCESS FULL | USERS | 14 | 1666 | | 3 (0)| 00:00:01 |
|* 4 | HASH JOIN RIGHT OUTER| | 289K| 357M| | 14371 (1)| 00:00:01 |
|* 5 | TABLE ACCESS FULL | EXECUTORS | 4 | 200 | | 3 (0)| 00:00:01 |
|* 6 | HASH JOIN | | 289K| 343M| | 14367 (1)| 00:00:01 |
| 7 | TABLE ACCESS FULL | REQUEST_STATUSES | 5 | 345 | | 3 (0)| 00:00:01 |
|* 8 | TABLE ACCESS FULL | TMP_REQUESTS_5 | 289K| 324M| | 14363 (1)| 00:00:01 |
----------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("U"."ID"(+)="R"."CREATED_USER_ID")
4 - access("E"."ID"(+)="R"."EXECUTOR_ID")
5 - filter(INTERNAL_FUNCTION( /*+ LVC_LAZY_LOAD */ "E"."RESPONSIBILITY_AREA" /*+
LOB_BY_VALUE */ ))
6 - access("RS"."ID"="R"."STATUS_ID")
8 - filter(INTERNAL_FUNCTION( /*+ LVC_LAZY_LOAD */ "R"."DESCRIPTION" /*+ LOB_BY_VALUE */
) AND INTERNAL_FUNCTION( /*+ LVC_LAZY_LOAD */ "R"."RESOLUTION" /*+ LOB_BY_VALUE */ ) AND
INTERNAL_FUNCTION( /*+ LVC_LAZY_LOAD */ "R"."RESPONSE_TO_APPLICANT" /*+ LOB_BY_VALUE */ ))
I would like the execution plan to remain the same as before the statistics were generated, since it takes much less time. I've tried using optimizer hints, but have not been successful.
Is it possible to specify for the database the execution plan for this query as it was originally, before generating statistics for this table? If so, how can this be done and will there be any negative effects from this? I will be grateful for any help.
Database version: Oracle 19с standatrd edition

Explanation of Oracle PARTITION BY vs GROUP BY for similar results

I have the following table (Marks):
firstname lastname Mark
------------------------------
arun prasanth 40
ann antony 45
sruthy abc 41
new abc 47
arun prasanth 45
arun prasanth 49
ann antony 49
And would like to add a column that tags if a record with specific columns occurs more than once. This is the result:
firstname lastname Mark MULTI_FLAG
----------------------------------------------
arun prasanth 40 1
ann antony 45 1
sruthy abc 41 0
new abc 47 0
arun prasanth 45 1
arun prasanth 49 1
ann antony 49 1
I can get the result with the following GROUP BY query:
SELECT M1.firstname
,M1.lastname
,M1.Mark
,M2.MULTI_COUNT
FROM Marks M1
JOIN (SELECT firstname, lastname, CASE WHEN COUNT (*) > 1 THEN 1 ELSE 0 END AS MULTI_COUNT
FROM Marks
GROUP BY firstname, lastname) M2
ON M2.firstname = M1.firstname AND M2.lastname = M1.lastname;
Or by this much prettier PARTITION BY query:
SELECT
firstname,
lastname,
CASE WHEN COUNT(*) OVER (PARTITION BY
firstname,
lastname) > 1 THEN 1 ELSE 0 END AS MULTI_FLAG
FROM
Marks
Running the GROUP BY query on a similar large table returned in:
34 m 56 s 595 ms
Running the PARTITION BY query on a similar large table returned in:
First run: 55 m 47 s 851 ms
Second run: 36 m 46 s 95 ms
I would be interested in knowing:
The best way to achieve my results
What accounts for the performance difference.
EDIT: How to read the query plan.
EDIT:
Oracle Version
Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit Production
PL/SQL Release 11.2.0.3.0 - Production
"CORE 11.2.0.3.0 Production"
TNS for Linux: Version 11.2.0.3.0 - Production
NLSRTL Version 11.2.0.3.0 - Production
PARTITION BY Plan
PLAN_TABLE_OUTPUT
Plan hash value: 3822227444
---------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 668K| 90M| | 90429 (1)| 00:18:06 |
| 1 | WINDOW SORT | | 668K| 90M| 98M| 90429 (1)| 00:18:06 |
|* 2 | HASH JOIN RIGHT OUTER | | 668K| 90M| | 69340 (1)| 00:13:53 |
| 3 | TABLE ACCESS FULL | COUNTRY_REGION_MAPPINGS | 177 | 4779 | | 3 (0)| 00:00:01 |
| 4 | NESTED LOOPS | | | | | | |
| 5 | NESTED LOOPS | | 377K| 41M| | 69335 (1)| 00:13:53 |
| 6 | MAT_VIEW ACCESS FULL | PROJINFO_MAX_ITER_MVW | 17713 | 328K| | 782 (1)| 00:00:10 |
|* 7 | INDEX RANGE SCAN | Q_CLIN_ASSUM_BYCOUN_PK | 1 | | | 3 (0)| 00:00:01 |
| 8 | TABLE ACCESS BY INDEX ROWID| Q_CLINICAL_ASSUM_BYCOUNTRY | 21 | 2016 | | 4 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access(UPPER("CRM"."COUNTRY"(+))=UPPER("QCAB"."TRIAL_COUNTRY"))
7 - access("PMIM"."OPPORTUNITYNUM"="QCAB"."OPPORTUNITYNUM" AND "PMIM"."CONTRACTNUM"="QCAB"."CONTRACTNUM"
AND "PMIM"."ITERATION"="QCAB"."ITERATION")
filter(UPPER("QCAB"."SHEET_LOC") LIKE '%COUNTRY ASSUMPTIONS%' OR UPPER("QCAB"."SHEET_LOC") LIKE
'INPUT%')
GROUP BY Plan
PLAN_TABLE_OUTPUT
Plan hash value: 648231064
------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 912 | 2052K| | 226K (1)| 00:45:22 |
|* 1 | HASH JOIN | | 912 | 2052K| | 226K (1)| 00:45:22 |
| 2 | TABLE ACCESS FULL | COUNTRY_REGION_MAPPINGS | 177 | 4779 | | 3 (0)| 00:00:01 |
|* 3 | HASH JOIN | | 89667 | 194M| 45M| 226K (1)| 00:45:22 |
| 4 | NESTED LOOPS | | | | | | |
| 5 | NESTED LOOPS | | 377K| 41M| | 69335 (1)| 00:13:53 |
| 6 | MAT_VIEW ACCESS FULL | PROJINFO_MAX_ITER_MVW | 17713 | 328K| | 782 (1)| 00:00:10 |
|* 7 | INDEX RANGE SCAN | Q_CLIN_ASSUM_BYCOUN_PK | 1 | | | 3 (0)| 00:00:01 |
| 8 | TABLE ACCESS BY INDEX ROWID | Q_CLINICAL_ASSUM_BYCOUNTRY | 21 | 2016 | | 4 (0)| 00:00:01 |
| 9 | VIEW | | 668K| 1377M| | 86518 (1)| 00:17:19 |
| 10 | HASH GROUP BY | | 668K| 72M| 80M| 86518 (1)| 00:17:19 |
|* 11 | HASH JOIN RIGHT OUTER | | 668K| 72M| | 69340 (1)| 00:13:53 |
| 12 | TABLE ACCESS FULL | COUNTRY_REGION_MAPPINGS | 177 | 2478 | | 3 (0)| 00:00:01 |
| 13 | NESTED LOOPS | | | | | | |
| 14 | NESTED LOOPS | | 377K| 35M| | 69335 (1)| 00:13:53 |
| 15 | MAT_VIEW ACCESS FULL | PROJINFO_MAX_ITER_MVW | 17713 | 328K| | 782 (1)| 00:00:10 |
|* 16 | INDEX RANGE SCAN | Q_CLIN_ASSUM_BYCOUN_PK | 1 | | | 3 (0)| 00:00:01 |
| 17 | TABLE ACCESS BY INDEX ROWID| Q_CLINICAL_ASSUM_BYCOUNTRY | 21 | 1701 | | 4 (0)| 00:00:01 |
------------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("R2"."TRIAL_COUNTRY_CD"="CRM"."COUNTRY_CD" AND
UPPER("CRM"."COUNTRY")=UPPER("QCAB"."TRIAL_COUNTRY"))
3 - access("R2"."OPPORTUNITYNUM"="QCAB"."OPPORTUNITYNUM" AND "R2"."ITERATION"="QCAB"."ITERATION" AND
"R2"."CONTRACTNUM"="QCAB"."CONTRACTNUM" AND "R2"."ASSUMPTION"="QCAB"."ASSUMPTION")
7 - access("PMIM"."OPPORTUNITYNUM"="QCAB"."OPPORTUNITYNUM" AND "PMIM"."CONTRACTNUM"="QCAB"."CONTRACTNUM" AND
"PMIM"."ITERATION"="QCAB"."ITERATION")
filter(UPPER("QCAB"."SHEET_LOC") LIKE '%COUNTRY ASSUMPTIONS%' OR UPPER("QCAB"."SHEET_LOC") LIKE 'INPUT%')
11 - access(UPPER("CRM"."COUNTRY"(+))=UPPER("QCAB"."TRIAL_COUNTRY"))
16 - access("PMIM"."OPPORTUNITYNUM"="QCAB"."OPPORTUNITYNUM" AND "PMIM"."CONTRACTNUM"="QCAB"."CONTRACTNUM" AND
"PMIM"."ITERATION"="QCAB"."ITERATION")
filter(UPPER("QCAB"."SHEET_LOC") LIKE '%COUNTRY ASSUMPTIONS%' OR UPPER("QCAB"."SHEET_LOC") LIKE 'INPUT%')
Typically you start with the analytic function count(*) which leads to a compact SQL.
The drawback of this aproach is that the data must be sorted (see WINDOW SORT operation). The GROUP BY approach avoids
the sorting as HASH GROUP BY may be used, which can lead to a better performance.
Your example is a bit more involved, as you do not use table but a view that joins three tables - this join is performed twice, for the GROUP BY and for the detail data; which
is of course not optimal.
So I'll start with the analytic function version of the query (possible with a PARALLELoption).
If you want to try the GROUP BY a lightway version is possible:
1) group only the duplicated keys
2) make OUTER JOIN to assign the MULTI_FLAG
example with execution plan below - simple test with your data
with dups as (
select firstname,lastname from tmp
group by firstname,lastname
having count(*) > 1)
select tmp.FIRSTNAME, tmp.LASTNAME, tmp.MARK,
case when dups.firstname is not NULL then 1 else 0 end as MULTI_FLAG
from tmp
left outer join dups on tmp.firstname = dups.firstname and tmp.lastname = dups.lastname;
You still need to access your view twice, but the final join will be faster (espetially if you have only small number of duplicated keys).
--------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
--------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 105K| 26M| | 1673 (1)| 00:00:21 |
|* 1 | HASH JOIN RIGHT OUTER| | 105K| 26M| 11M| 1673 (1)| 00:00:21 |
| 2 | VIEW | | 105K| 10M| | 128 (4)| 00:00:02 |
|* 3 | FILTER | | | | | | |
| 4 | HASH GROUP BY | | 105K| 10M| | 128 (4)| 00:00:02 |
| 5 | TABLE ACCESS FULL| TMP | 105K| 10M| | 125 (1)| 00:00:02 |
| 6 | TABLE ACCESS FULL | TMP | 105K| 15M| | 125 (1)| 00:00:02 |
--------------------------------------------------------------------------------------

Union all and function based index

I have big tables with identical function based indexes. When one table is queried then query plan uses the index. When I join second table with union all then I have full scans.
I have found one working solution (WORKING JOIN WITH TABLE), but it has some limitations:
I cannot use ROWNUM not CONNECT BY in the x query merged to union
I cannot use pipeline function in a query.
How to overcome the mentioned limitations?
I tried hints like CARDINALITY to tell that the number of record is small, and some rewrite (REWRITE, PUSH_PRED) without success.
I cannot use partitioning since the problem is on Oracle SE.
The problem shown here is a simplification of my actual problem which is:
I have huge data set divided into identical tables
Each table contains data from a different month
I have a view which union all the underlying tables
It is kind of partitioning style practiced on oracle before v8.0
In real I may have adhoc queries and joins with many different tables. Thus I cannot simply push the join into a union which will be the simplest solution.
Here is the DDL script.
-- FULL SCAN FOR IN SUBQUERY
SELECT * FROM (
SELECT * FROM TEST_EXPV1
UNION ALL SELECT * FROM TEST_EXPV2
)
WHERE DECODE(Value, -1, CAST(NULL AS NUMBER(38)), 0, CAST(NULL AS NUMBER(38)), Value) IN (SELECT Id FROM test_10r)
-----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 200K| 5078K| 916 (3)| 00:00:11 |
|* 1 | HASH JOIN | | 200K| 5078K| 916 (3)| 00:00:11 |
| 2 | VIEW | VW_NSO_1 | 10 | 130 | 4 (25)| 00:00:01 |
| 3 | HASH UNIQUE | | 10 | 30 | 4 (25)| 00:00:01 |
| 4 | TABLE ACCESS FULL| TEST_10R | 10 | 30 | 3 (0)| 00:00:01 |
| 5 | VIEW | | 2000K| 24M| 902 (2)| 00:00:11 |
| 6 | UNION-ALL | | | | | |
| 7 | TABLE ACCESS FULL| TEST_EXPV1 | 1000K| 3906K| 451 (2)| 00:00:06 |
| 8 | TABLE ACCESS FULL| TEST_EXPV2 | 1000K| 3906K| 451 (2)| 00:00:06 |
-----------------------------------------------------------------------------------
-- CORRECT RANGE INDEX SCAN for bound value
SELECT * FROM (
SELECT * FROM TEST_EXPV1
UNION ALL SELECT * FROM TEST_EXPV2
)
WHERE DECODE(Value, -1, CAST(NULL AS NUMBER(38)), 0, CAST(NULL AS NUMBER(38)), Value) = :b1001
----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 20000 | 253K| 979 (10)| 00:00:12 |
| 1 | VIEW | | 20000 | 253K| 979 (10)| 00:00:12 |
| 2 | UNION-ALL | | | | | |
| 3 | TABLE ACCESS BY INDEX ROWID| TEST_EXPV1 | 2 | 8 | 3 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | I_TEST_EXPV1 | 2 | | 1 (0)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID| TEST_EXPV2 | 2 | 8 | 3 (0)| 00:00:01 |
|* 6 | INDEX RANGE SCAN | I_TEST_EXPV2 | 2 | | 1 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------
-- WORKING JOIN WITH TABLE
WITH x AS (SELECT Id FROM test_10r WHERE Id BETWEEN :a AND :b)
SELECT /*+ FIRST_ROWS */ * FROM (
SELECT * FROM TEST_EXPV1
UNION ALL SELECT * FROM TEST_EXPV2
) U, x
WHERE DECODE(Value, -1, CAST(NULL AS NUMBER(38)), 0, CAST(NULL AS NUMBER(38)), Value) = x.id
-------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 16 | 996 (11)| 00:00:12 |
|* 1 | FILTER | | | | | |
|* 2 | HASH JOIN | | 500 | 8000 | 996 (11)| 00:00:12 |
|* 3 | TABLE ACCESS FULL | TEST_10R | 10 | 30 | 3 (0)| 00:00:01 |
| 4 | VIEW | | 5000 | 65000 | 993 (11)| 00:00:12 |
| 5 | UNION-ALL | | | | | |
|* 6 | FILTER | | | | | |
| 7 | TABLE ACCESS BY INDEX ROWID| TEST_EXPV1 | 2500 | 10000 | 4192 (1)| 00:00:51 |
|* 8 | INDEX RANGE SCAN | I_TEST_EXPV1 | 4500 | | 11 (0)| 00:00:01 |
|* 9 | FILTER | | | | | |
| 10 | TABLE ACCESS BY INDEX ROWID| TEST_EXPV2 | 2500 | 10000 | 4192 (1)| 00:00:51 |
|* 11 | INDEX RANGE SCAN | I_TEST_EXPV2 | 4500 | | 11 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------
You're dong the UNION ALL, which is doing a full scan of both tables to get combined result set; and only then filtering on the values from the third table.
A more usual pattern for subquery factoring is to refer to the CTE in each branch of the union:
WITH x AS (SELECT Id FROM test_10r)
SELECT TEST_EXPV1.* FROM x JOIN TEST_EXPV1
ON DECODE(Value, -1, CAST(NULL AS NUMBER(38)), 0, CAST(NULL AS NUMBER(38)), Value) = x.Id
UNION ALL SELECT TEST_EXPV2.* FROM x JOIN TEST_EXPV2
ON DECODE(Value, -1, CAST(NULL AS NUMBER(38)), 0, CAST(NULL AS NUMBER(38)), Value) = x.Id;
----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 32 | 224 | 66 (0)| 00:00:01 |
| 1 | UNION-ALL | | | | | |
| 2 | NESTED LOOPS | | 16 | 112 | 33 (0)| 00:00:01 |
| 3 | TABLE ACCESS FULL | TEST_10R | 10 | 30 | 3 (0)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID| TEST_EXPV1 | 2 | 8 | 3 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | I_TEST_EXPV1 | 2 | | 1 (0)| 00:00:01 |
| 6 | NESTED LOOPS | | 16 | 112 | 33 (0)| 00:00:01 |
| 7 | TABLE ACCESS FULL | TEST_10R | 10 | 30 | 3 (0)| 00:00:01 |
| 8 | TABLE ACCESS BY INDEX ROWID| TEST_EXPV2 | 2 | 8 | 3 (0)| 00:00:01 |
|* 9 | INDEX RANGE SCAN | I_TEST_EXPV2 | 2 | | 1 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------
Pushing predicates into a UNION ALL can be temperamental. Try this:
SELECT /*+ PUSH_PRED(v) */ *
FROM (SELECT * FROM test_expv1
UNION ALL
SELECT * FROM test_expv2) v INNER JOIN test_10r ON
(DECODE(Value, -1, CAST(NULL AS NUMBER(38)), 0, CAST(NULL AS NUMBER(38)), Value)) = test_10r.id;
Here are my results for the above query from an 11.2.0.4 instance, using the OP's DDL:
SQL_ID df6dvkgjwjsq1, child number 1
-------------------------------------
SELECT /*+ PUSH_PRED(v) */ * FROM (SELECT * FROM test_expv1
UNION ALL SELECT * FROM test_expv2) v INNER JOIN test_10r ON
(DECODE(Value, -1, CAST(NULL AS NUMBER(38)), 0, CAST(NULL AS
NUMBER(38)), Value)) = test_10r.id
Plan hash value: 191389749
---------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
---------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 16 |00:00:00.01 | 69 |
| 1 | NESTED LOOPS | | 1 | 2000K| 16 |00:00:00.01 | 69 |
| 2 | TABLE ACCESS FULL | TEST_10R | 1 | 10 | 10 |00:00:00.01 | 22 |
| 3 | VIEW | | 10 | 32 | 16 |00:00:00.01 | 47 |
| 4 | UNION ALL PUSHED PREDICATE | | 10 | | 16 |00:00:00.01 | 47 |
| 5 | TABLE ACCESS BY INDEX ROWID| TEST_EXPV1 | 10 | 158 | 8 |00:00:00.01 | 24 |
|* 6 | INDEX RANGE SCAN | I_TEST_EXPV1 | 10 | 2 | 8 |00:00:00.01 | 16 |
| 7 | TABLE ACCESS BY INDEX ROWID| TEST_EXPV2 | 10 | 158 | 8 |00:00:00.01 | 23 |
|* 8 | INDEX RANGE SCAN | I_TEST_EXPV2 | 10 | 2 | 8 |00:00:00.01 | 15 |
---------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
6 - access("TEST_EXPV1"."SYS_NC00002$"="TEST_10R"."ID")
8 - access("TEST_EXPV2"."SYS_NC00002$"="TEST_10R"."ID")

inefficient SQL plan on the partitioned Table

we are facing issue with one of query which joins between few tables.
even though there are few hundreds of records in the table the plan is going to Merge join thinking only one record in the table, please find the below plan.
when the Merge sort plan is used the query fails with temp space issue.
oracle choose Merge plan only when job loaded to newly created partition. but the rest of old partitions it is choosing Hash Join where we get results in few seconds.
for information. All the joined table has same volume.
could you please explain why this is happening?
Merg join( query hung)
-----------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
-----------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 712 | 36 (3)| 00:00:01 | | |
|* 1 | HASH JOIN | | 1 | 712 | 36 (3)| 00:00:01 | | |
| 2 | MERGE JOIN CARTESIAN | | 1 | 679 | 28 (0)| 00:00:01 | | |
| 3 | MERGE JOIN CARTESIAN | | 1 | 615 | 21 (0)| 00:00:01 | | |
| 4 | MERGE JOIN CARTESIAN | | 1 | 388 | 14 (0)| 00:00:01 | | |
| 5 | PARTITION RANGE SINGLE | | 1 | 105 | 7 (0)| 00:00:01 | 4 | 4 |
|* 6 | TABLE ACCESS FULL | PCA_DCM_CLNT_BSPKE_REFS_M_LND | 1 | 105 | 7 (0)| 00:00:01 | 4 | 4 |
| 7 | BUFFER SORT | | 1 | 283 | 7 (0)| 00:00:01 | | |
| 8 | PARTITION RANGE SINGLE| | 1 | 283 | 7 (0)| 00:00:01 | 4 | 4 |
|* 9 | TABLE ACCESS FULL | PCA_DCM_INDBTDNS_BLK_M_LND | 1 | 283 | 7 (0)| 00:00:01 | 4 | 4 |
| 10 | BUFFER SORT | | 1 | 227 | 14 (0)| 00:00:01 | | |
| 11 | PARTITION RANGE SINGLE | | 1 | 227 | 7 (0)| 00:00:01 | 4 | 4 |
|* 12 | TABLE ACCESS FULL | PCA_DCM_DELPHI_BLK_M_LND | 1 | 227 | 7 (0)| 00:00:01 | 4 | 4 |
| 13 | BUFFER SORT | | 1 | 64 | 21 (0)| 00:00:01 | | |
| 14 | PARTITION RANGE SINGLE | | 1 | 64 | 7 (0)| 00:00:01 | 4 | 4 |
|* 15 | TABLE ACCESS FULL | PCA_DCM_APACS_BLK_M_LND | 1 | 64 | 7 (0)| 00:00:01 | 4 | 4 |
| 16 | PARTITION RANGE SINGLE | | 1 | 33 | 7 (0)| 00:00:01 | 4 | 4 |
|* 17 | TABLE ACCESS FULL | PCA_DCM_SCORE_BLK_M_LND | 1 | 33 | 7 (0)| 00:00:01 | 4 | 4 |
-----------------------------------------------------------------------------------------------------------------------------
Hash join(Few seconds we get the results)
----------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
----------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 491 | 341K| 74 (3)| 00:00:01 | | |
|* 1 | HASH JOIN | | 491 | 341K| 74 (3)| 00:00:01 | | |
| 2 | PARTITION RANGE SINGLE | | 493 | 109K| 14 (0)| 00:00:01 | 3 | 3 |
|* 3 | TABLE ACCESS FULL | PCA_DCM_DELPHI_BLK_M_LND | 493 | 109K| 14 (0)| 00:00:01 | 3 | 3 |
|* 4 | HASH JOIN | | 491 | 232K| 60 (4)| 00:00:01 | | |
|* 5 | HASH JOIN | | 492 | 99384 | 45 (3)| 00:00:01 | | |
|* 6 | HASH JOIN | | 492 | 47724 | 31 (4)| 00:00:01 | | |
| 7 | PARTITION RANGE SINGLE| | 493 | 16269 | 14 (0)| 00:00:01 | 3 | 3 |
|* 8 | TABLE ACCESS FULL | PCA_DCM_SCORE_BLK_M_LND | 493 | 16269 | 14 (0)| 00:00:01 | 3 | 3 |
| 9 | PARTITION RANGE SINGLE| | 493 | 31552 | 16 (0)| 00:00:01 | 3 | 3 |
|* 10 | TABLE ACCESS FULL | PCA_DCM_APACS_BLK_M_LND | 493 | 31552 | 16 (0)| 00:00:01 | 3 | 3 |
| 11 | PARTITION RANGE SINGLE | | 493 | 51765 | 14 (0)| 00:00:01 | 3 | 3 |
|* 12 | TABLE ACCESS FULL | PCA_DCM_CLNT_BSPKE_REFS_M_LND | 493 | 51765 | 14 (0)| 00:00:01 | 3 | 3 |
| 13 | PARTITION RANGE SINGLE | | 493 | 136K| 14 (0)| 00:00:01 | 3 | 3 |
|* 14 | TABLE ACCESS FULL | PCA_DCM_INDBTDNS_BLK_M_LND | 493 | 136K| 14 (0)| 00:00:01 | 3 | 3 |
----------------------------------------------------------------------------------------------------------------------------
Please find the query
SELECT
substr(BLK.ACC_NUM,1,14) AS ACCOUNT_NUMBER,
CASE WHEN SUBSTR(BLK.ACC_NUM,20,1) = '1' THEN 'F1'
WHEN SUBSTR(BLK.ACC_NUM,20,1) = ' ' THEN 'F1'
WHEN SUBSTR(BLK.ACC_NUM,20,1) = '0' THEN 'F1'
WHEN SUBSTR(BLK.ACC_NUM,20,1) = '2' THEN 'F2'
END FLTR,
DELPHI.ND_SPA_CII_SPA
FROM
BUR_LND.PCA_DCM_SCORE_BLK_M_LND BLK
INNER JOIN BUR_LND.PCA_DCM_CLNT_BSPKE_REFS_M_LND REFFS
ON BLK.ACC_NUM= REFFS.ACC_NUM
INNER JOIN BUR_LND.PCA_DCM_INDBTDNS_BLK_M_LND IND
ON BLK.ACC_NUM= IND.ACC_NUM
INNER JOIN BUR_LND.PCA_DCM_DELPHI_BLK_M_LND DELPHI
ON BLK.ACC_NUM= DELPHI.ACC_NUM
INNER JOIN BUR_LND.PCA_DCM_APACS_BLK_M_LND APACS
ON BLK.ACC_NUM= APACS.ACC_NUM
WHERE
BLK.EFF_DT=TO_DATE('2013-10-30','YYYY-MM-DD')
AND REFFS.EFF_DT=TO_DATE('2013-10-30','YYYY-MM-DD')
AND IND.EFF_DT=TO_DATE('2013-10-30','YYYY-MM-DD')
AND DELPHI.EFF_DT=TO_DATE('2013-10-30','YYYY-MM-DD')
AND APACS.EFF_DT=TO_DATE('2013-10-30','YYYY-MM-DD')
Thanks in advance for help.
Thanks
arkesh
The plans are bad because the new partition is missing statistics. Statistics should be updated after partition changes, ideally using incremental statistics. If that's not possible then a hint like /*+ dynamic_sampling(4) */ can help.
Statistics can be accurate, inaccurate, or missing. Missing statistics are generally not a huge problem because of dynamic sampling. With the default dynamic sampling level, 2, Oracle will automatically gather statistics if a statement includes tables without statistics.
Unfortunately for this case, Oracle only considers missing statistics per table, not per partition. (That would be a good feature request, but that won't help you right now.) With the literals in the SQL statement Oracle appears to know exactly which partition to look in. Since there are no statistics for that partition it assumes there are no rows, leading to the bad plans.
Example
Create a sample partitioned table with 1000 rows but no gathered statistics.
create table partition_test
(
a number,
b number
)
partition by range (a)
(
partition p1 values less than (2)
);
insert into partition_test select 1, level from dual connect by level <= 1000;
When there are no statistics Oracle will use dynamic sampling automatically and get a good row count. You can't see it in this simple plan, but normally this would lead to better access methods and better join operations.
explain plan for select * from partition_test where a = 1 and b <= 1000;
select * from table(dbms_xplan.display);
Plan hash value: 4097357352
---------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
---------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1000 | 26000 | 16 (0)| 00:00:01 | | |
| 1 | PARTITION RANGE SINGLE| | 1000 | 26000 | 16 (0)| 00:00:01 | 1 | 1 |
|* 2 | TABLE ACCESS FULL | PARTITION_TEST | 1000 | 26000 | 16 (0)| 00:00:01 | 1 | 1 |
---------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("A"=1 AND "B"<=1000)
Note
-----
- dynamic statistics used: dynamic sampling (level=2)
Gather stats, then create a new partition with data. Although that partition has 1000 rows and no stats, Oracle does not know that and just assumes it's empty.
begin
dbms_stats.gather_table_stats(user, 'partition_test');
end;
/
alter table partition_test add partition p2 values less than (3);
insert into partition_test select 2, level from dual connect by level <= 1000;
explain plan for select * from partition_test where a = 2 and b <= 1000;
select * from table(dbms_xplan.display);
Plan hash value: 4097357352
---------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
---------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 7 | 9 (0)| 00:00:01 | | |
| 1 | PARTITION RANGE SINGLE| | 1 | 7 | 9 (0)| 00:00:01 | 2 | 2 |
|* 2 | TABLE ACCESS FULL | PARTITION_TEST | 1 | 7 | 9 (0)| 00:00:01 | 2 | 2 |
---------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("A"=2 AND "B"<=1000)
Explicitly requesting dynamic sampling will fix the cardinality estimates, which would likely solve your execution plan problems.
explain plan for select /*+ dynamic_sampling(4) */ * from partition_test where a = 2 and b <= 1000;
select * from table(dbms_xplan.display);
Plan hash value: 4097357352
---------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
---------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1000 | 7000 | 9 (0)| 00:00:01 | | |
| 1 | PARTITION RANGE SINGLE| | 1000 | 7000 | 9 (0)| 00:00:01 | 2 | 2 |
|* 2 | TABLE ACCESS FULL | PARTITION_TEST | 1000 | 7000 | 9 (0)| 00:00:01 | 2 | 2 |
---------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("A"=2 AND "B"<=1000)
Note
-----
- dynamic statistics used: dynamic sampling (level=4)

Problem with predicte pushing in a Max and Min statistic about each user in a group

I have two tables in an ORACLE 10g DB:
The table USERS with columns:
UserId | GroupId | Other details
where there's 1 record for each User, total 400, and there c.a. 1-10 users in each Group.
and the table USAGE with columns:
UserId | Date | Amount
where there's 10000 rows for each user. (so 400*10000 rows)
I have the Users table indexed on UserId, GroupId and (UserId,GroupId) and the table Usage is indexed on (UserId,Date)
I want to see the Max and Total amount of usage for each user in a given group.
Something like this:
select User.UserId, maxAmount, Total from
Users
JOIN
( select UserId,max(Amount) as maxAmount from USAGE group by UserId ) A
ON User.UserId = A.UserId
JOIN
( select UserId,min(Amount) as minAmount from USAGE group by UserId ) B
ON User.UserId = B.UserId
WHERE User.GroupId = 'some_group_id'
But it is very slow (approx 20 sec), but when I do them separately (just Max and just Min, but not both at once), like this:
select User.UserId, maxAmount, minAmount from
Users
JOIN
( select UserId,max(Amount) as maxAmount from USAGE group by UserId ) A
ON User.UserId = A.UserId
WHERE User.GroupId = 'some_group_id'
it runs in a flash!
It just makes no sense that I can join them separately very fast, but when I join them all three it's very slow.
Here are the plans, where I created the inline views in the preceeding statements as actual views:
create view usage_min as select user_id, min(amount) from usage group by user_id;
create view usage_max as select user_id, max(amount) from usage group by user_id;
explain plan for select * from usage_min join users using(user_id) where group_id='1212882339';
select * from table(dbms_xplan.display);
Plan hash value: 3874246446
-----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 139 | 1162 (1)| 00:00:14 |
| 1 | HASH GROUP BY | | 1 | 139 | 1162 (1)| 00:00:14 |
| 2 | TABLE ACCESS BY INDEX ROWID | USAGE | 7977 | 116K| 1157 (1)| 00:00:14 |
| 3 | NESTED LOOPS | | 11085 | 1504K| 1160 (1)| 00:00:14 |
| 4 | MAT_VIEW ACCESS BY INDEX ROWID| USERS | 1 | 124 | 3 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | USERS_KT | 1 | | 1 (0)| 00:00:01 |
|* 6 | INDEX RANGE SCAN | SYS_C0099818 | 7977 | | 79 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
5 - access("USERS"."group_id"='1212882339')
6 - access("user_id"="USERS"."user_id")
explain plan for select * from users join usage_max using(user_id) where group_id='1212882339';
select * from table(dbms_xplan.display);
Plan hash value: 2384977958
-----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 145 | 519 (2)| 00:00:07 |
| 1 | HASH GROUP BY | | 1 | 145 | 519 (2)| 00:00:07 |
| 2 | TABLE ACCESS BY INDEX ROWID | USAGE2 | 18251 | 374K| 512 (1)| 00:00:07 |
| 3 | NESTED LOOPS | | 25362 | 3591K| 515 (1)| 00:00:07 |
| 4 | MAT_VIEW ACCESS BY INDEX ROWID| USERS | 1 | 124 | 3 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | USERS_KT | 1 | | 1 (0)| 00:00:01 |
|* 6 | INDEX RANGE SCAN | T2_user_id | 18251 | | 25 (4)| 00:00:01 |
-----------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
5 - access("USERS"."group_id"='1212882339')
6 - access("USERS"."user_id"="user_id")
explain plan for select * from users join usage_max using(user_id) join usage_min using(user_id) where group_id='1212882339';
select * from table(dbms_xplan.display);
Plan hash value: 3190011991
-----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 100 | 16600 | 6782 (19)| 00:01:22 |
|* 1 | HASH JOIN | | 100 | 16600 | 6782 (19)| 00:01:22 |
| 2 | MERGE JOIN | | 100 | 14500 | 3176 (19)| 00:00:39 |
| 3 | SORT JOIN | | 2920K| 58M| 3172 (19)| 00:00:39 |
| 4 | VIEW | USAGE_MAX | 2920K| 58M| 3172 (19)| 00:00:39 |
| 5 | HASH GROUP BY | | 2920K| 58M| 3172 (19)| 00:00:39 |
| 6 | TABLE ACCESS FULL | USAGE2 | 2920K| 58M| 2686 (5)| 00:00:33 |
|* 7 | SORT JOIN | | 1 | 124 | 4 (25)| 00:00:01 |
| 8 | MAT_VIEW ACCESS BY INDEX ROWID| USERS | 1 | 124 | 3 (0)| 00:00:01 |
|* 9 | INDEX RANGE SCAN | USERS_KT | 1 | | 1 (0)| 00:00:01 |
| 10 | VIEW | USAGE_MIN | 398 | 8358 | 3605 (19)| 00:00:44 |
| 11 | HASH GROUP BY | | 398 | 5970 | 3605 (19)| 00:00:44 |
| 12 | TABLE ACCESS FULL | USAGE | 3174K| 45M| 3073 (4)| 00:00:37 |
-----------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("USAGE_MAX"."user_id"="USAGE_MIN"."user_id")
7 - access("USERS"."user_id"="USAGE_MAX"."user_id")
filter("USERS"."user_id"="USAGE_MAX"."user_id")
9 - access("USERS"."group_id"='1212882339')
What are the query plans?
Are you measuring the time to fetch the first row? Or are you measuring the time to fetch the last row?
At a minimum, I'd want to hit the USAGE table just once using something like
select User.UserId, maxAmount, Total from
Users
JOIN
( select UserId,
max(Amount) as maxAmount,
sum(Amount) as Total
from USAGE group by UserId ) A
ON User.UserId = A.UserId
WHERE User.GroupId = 'some_group_id'
or even more simply
SELECT user.userId,
max(usage.Amount) maxAmount,
sum(usage.Amount) total
FROM user
join usage on (user.userId = usage.userId)
WHERE user.GroupId = 'some_group_id'
GROUP BY user.userId

Resources