oracle 11g execution plan behaviour - oracle11g

I have this very simple query performing filtering and join by rowid.
SELECT *
FROM BOOKING.BOOKING_GRID BG,
BOOKING.BOOKING_STATES BS
WHERE BG.hotel=128
AND BS.ROWID =BG.BOOKINGSTATE;
when I explain plan I get:
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 1597031677
--------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 6137K| 1041M| | 1763K (1)| 05:48:27 |
|* 1 | HASH JOIN | | 6137K| 1041M| 538M| 1763K (1)| 05:48:27 |
|* 2 | INDEX UNIQUE SCAN| BOOKING_GRIDPK | 6137K| 468M| | 547K (1)| 01:48:05 |
|* 3 | INDEX RANGE SCAN| BOOKING_GRID_INDEX5 | 6137K| | | 90388 (1)| 00:17:52 |
| 4 | TABLE ACCESS FULL| BOOKING_STATES | 158M| 14G| | 365K (2)| 01:12:14 |
--------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("BS".ROWID="BG"."BOOKINGSTATE")
2 - access("BG"."HOTEL"=128)
3 - access("BG"."HOTEL"=128)
Index for BOOKING_GRID are:
BOOKING BOOKING_GRIDPK UNIQUE VALID IOT - TOP N NO NO HOTEL, DAY, BOOKINGSTATE
BOOKING BOOKING_GRID_UNIQ UNIQUE VALID NORMAL N NO NO HOTEL, DAY, BOOKING, VALIDITYSTART
BOOKING BOOKING_GRID_INDEX5 NONUNIQUE VALID NORMAL N NO NO HOTEL, BOOKINGSTATUS, ISDAYUSE, DAY
BOOKING BOOKING_GRID_INDEX7 NONUNIQUE VALID NORMAL N NO NO HOTEL, BOOKING, VALIDITYSTART
BOOKING BOOKING_GRID_INDEX10 NONUNIQUE VALID NORMAL N NO NO HOTEL, ISDAYUSE, BOOKINGSTATUS, DAY
Index for BOOKING_STATES are:
BOOKING BOOKING_STATES_PK UNIQUE VALID NORMAL N NO NO HOTEL, BOOKING, VALIDITYSTART
BOOKING BOOKING_STATES_INDEX2 NONUNIQUE VALID NORMAL N NO NO HOTEL, YIELDROOMTYPE, BOOKEDROOMTYPE, ROOMTYPE
BOOKING BOOKING_STATES_BOOKING NONUNIQUE VALID NORMAL N NO NO HOTEL, BOOKING, BOOKINGSTATUS
BOOKING BOOKING_NOSEGMENT_INDEX NONUNIQUE VALID FUNCTION-BASED NORMAL N NO ENABLED NO SYS_NC00034$ TO_NUMBER(DECODE(TO_CHAR("MARKETSEGMENT"),NULL,DECODE("BOOK",0,NULL,TO_CHAR(DECODE("ISDAYUSE",'N',DECODE("ISSHARED",'N',DECODE("BOOKINGSTATUS",'B',"HOTEL"*10000+LEAST("DEPARTURE","VALIDITYEND"),'I',"HOTEL"*10000+LEAST("DEPARTURE","VALIDITYEND"),'W',"HOTEL"*10000+LEAST("DEPARTURE","VALIDITYEND"))))))))
BOOKING BOOKING_NORATE_CODE_INDEX NONUNIQUE VALID FUNCTION-BASED NORMAL N NO ENABLED NO SYS_NC00033$ TO_NUMBER(DECODE(TO_CHAR("RATECODE"),NULL,DECODE("BOOK",0,NULL,TO_CHAR(DECODE("ISDAYUSE",'N',DECODE("ISSHARED",'N',DECODE("BOOKINGSTATUS",'B',"HOTEL"*10000+LEAST("DEPARTURE","VALIDITYEND"),'I',"HOTEL"*10000+LEAST("DEPARTURE","VALIDITYEND"),'W',"HOTEL"*10000+LEAST("DEPARTURE","VALIDITYEND"))))))))
BOOKING BOOKING_NOBOOKINGTYPE_INDEX NONUNIQUE VALID FUNCTION-BASED NORMAL N NO ENABLED NO SYS_NC00032$ TO_NUMBER(DECODE(TO_CHAR("BOOKINGTYPE"),NULL,DECODE("BOOK",0,NULL,TO_CHAR(DECODE("ISDAYUSE",'N',DECODE("ISSHARED",'N',DECODE("BOOKINGSTATUS",'B',"HOTEL"*10000+LEAST("DEPARTURE","VALIDITYEND"))))))))
BOOKING BOOKING_STATES_BOOKING_TYPE NONUNIQUE VALID NORMAL N NO NO HOTEL, BOOKINGTYPE, ISDAYUSE, BOOKINGSTATUS
BOOKING BOOKING_STATES_CANCEL_INDEX NONUNIQUE VALID FUNCTION-BASED NORMAL N NO ENABLED NO SYS_NC00035$, SYS_NC00036$ DECODE("BOOKINGSTATUS",'c',"HOTEL",'C',"HOTEL")
BOOKING BOOKING_STATES_CANCEL_INDEX NONUNIQUE VALID FUNCTION-BASED NORMAL N NO ENABLED NO SYS_NC00035$, SYS_NC00036$ DECODE("BOOKINGSTATUS",'c',"CANCELREASON",'C',"CANCELREASON")
I don't understand two things:
why Oracle would decide it is faster to join before filtering on hotel=128?
why using a hash join when it could use a "TABLE ACCESS BY USER ROWID"
The weird thing is that when I run the same exact request with hotel=201, it is perfectly fine:
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 4251203092
---------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 591K| 100M| 643K (1)| 02:07:12 |
| 1 | NESTED LOOPS | | 591K| 100M| 643K (1)| 02:07:12 |
|* 2 | INDEX UNIQUE SCAN | BOOKING_GRIDPK | 591K| 45M| 52686 (1)| 00:10:25 |
|* 3 | INDEX RANGE SCAN | BOOKING_GRID_INDEX5 | 591K| | 8707 (1)| 00:01:44 |
| 4 | TABLE ACCESS BY USER ROWID| BOOKING_STATES | 1 | 98 | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("BG"."HOTEL"=201)
3 - access("BG"."HOTEL"=201)
Any idea about what's going on there?
Thank you,
Renaud

The reason for the different execution paths is because Oracle thinks that there are about 6 million rows with hotel=128 but only 591,000 with hotel=201. In the case of the bigger intermediate set, Oracle chose a hash join over nested loops.
What I don't get is this:
AND BS.ROWID =BG.BOOKINGSTATE;
You're storing Oracle-format ROWIDs in a column called BOOKINGSTATE???
Ok, given your confirmation that BOOKINGSTATE really does contain Oracle ROWIDs, here's why I'd say you're getting a HASH JOIN instead of a NESTED LOOP join:
First, when Oracle reads a row, it doesn't just read a row at a time, it reads blocks at a time. So to do a NESTED LOOP join with a TABLE ACCESS BY USER ROWID lookup, it's going to find a row in BOOKING_GRID and then go read the block that has the row in BOOKING_STATES with that ROWID. The catch is, if later on there's another row with a rowid in a block that it's already read before, it's going to re-read that block (sure, it may be cached) to get the other row. Kind of like "open the block, get a row, close the box....then later on open the same box again, get another row, close the box, move on to the next box"
On the other hand, your hash join is:
- sorting the rows in the smaller set (in this case the rows in BOOKING_GRID where hotel=128), put them in memory
- full table scan BOOKING_STATES - and here's the kicker - using multiblock reads. it reads many blocks at a time and processes all of the rows in a block without needing to re-read it later. It's like "open the box, process all of the rows in the box, then close the box."
(For more details on the above check out http://docs.oracle.com/cd/B28359_01/server.111/b28274/optimops.htm, in particular the following sections:
11.5.1 Full Table Scans
11.5.3.1 Assessing I/O for Blocks, not Rows
11.6.3 Nested Loop Joins
11.6.4 Hash Joins
)
By the way, it's a bit curious that it's doing the "access("BG"."HOTEL"=128)" step twice using two indexes -how are the BOOKING_GRIDPK and BOOKING_GRID_INDEX5 indexes defined? You're asking for all columns from both tables, but the plan never touches the BOOKING_GRID table.)

Related

Besides indexing, how to speed up this query on 100m rows in PostgreSQL?

Background
First, let me know if this is more appropriate for the DBA StackExchange. Happy to move it there.
I've got a dataset, db1_dummy with ~100 million rows worth of car and motorcycle insurance claims that I'm prepping for statistical analysis. It's in PostgreSQL v13, which I have running on a local 64bit Windows machine and accessing through DataGrip. db1_dummy has ~15 variables, but for this question only 3 are relevant. Here's a toy version of the dataset:
+-------------------+------------+--+
|member_composite_id|service_date|id|
+-------------------+------------+--+
|eof81j4 |2010-01-12 |1 |
|eof81j4 |2010-06-03 |2 |
|eof81j4 |2011-01-06 |3 |
|eof81j4 |2011-05-21 |4 |
|j42roit |2015-11-29 |5 |
|j42roit |2015-11-29 |6 |
|j42roit |2015-11-29 |7 |
|p8ur0fq |2014-01-13 |8 |
|p8ur0fq |2014-01-13 |9 |
|p8ur0fq |2016-04-04 |10|
|vplhbun |2019-08-15 |11|
|vplhbun |2019-08-15 |12|
|vplhbun |2019-08-15 |13|
|akj3vie |2009-03-31 |14|
+-------------------+------------+--+
id is unique (a primary key), and as you can see member_composite_id identifies policyholders and can have multiple entries (an insurance policyholder can have multiple claims). service_date is just the date a policyholder's vehicle was serviced for an insurance claim.
I need to get the data into a certain format in order to run my analyses, all of which are regression-based implementations of survival analysis in R (Cox proportional hazards models with shared frailty, if anyone's interested). Three main things need to happen:
service_date needs to be converted into an integer counted up from 2009-01-01 -- days since January 1st, 2009, in other words. service_date needs to be renamed service_date_2.
A new column, service_date_1, needs to be created, and it needs to contain one of two things for each row: the cell should be 0 if that row is the first for that member_composite_id, or, if it isn't the first, it should contain the value of service_date_2 for that member_composite_id's previous row.
Since the interval (the difference) between service_date_1 and service_date_2 cannot equal zero, a small amount (0.1) should be subtracted from service_date_1 in such cases.
That may sound confusing, so let me just show you. Here's what I need the dataset to look like:
+--+-------------------+--------------+--------------+
|id|member_composite_id|service_date_1|service_date_2|
+--+-------------------+--------------+--------------+
|1 |eof81j4 |0 |376 |
|2 |eof81j4 |376 |518 |
|3 |eof81j4 |518 |735 |
|4 |eof81j4 |735 |870 |
|5 |j42roit |0 |2523 |
|6 |j42roit |2522.9 |2523 |
|7 |j42roit |2522.9 |2523 |
|8 |p8ur0fq |0 |1838 |
|9 |p8ur0fq |1837.9 |1838 |
|10|p8ur0fq |1838 |2650 |
|11|vplhbun |0 |3878 |
|12|vplhbun |3877.9 |3878 |
|13|vplhbun |3877.9 |3878 |
|14|akj3vie |0 |89 |
+--+-------------------+--------------+--------------+
The good news: I have a query that can do this -- indeed, this query spat out the output above. Here's the query:
CREATE TABLE db1_dummy_2 AS
SELECT
d1.id
, d1.member_composite_id
,
CASE
WHEN (COALESCE(MAX(d2.service_date)::TEXT,'') = '') THEN 0
WHEN (MAX(d2.service_date) - '2009-01-01'::DATE = d1.service_date - '2009-01-01'::DATE) THEN d1.service_date - '2009-01-01'::DATE - 0.1
ELSE MAX(d2.service_date) - '2009-01-01'::DATE
END service_date_1
, d1.service_date - '2009-01-01'::DATE service_date_2
FROM db1_dummy d1
LEFT JOIN db1_dummy d2
ON d2.member_composite_id = d1.member_composite_id
AND d2.service_date <= d1.service_date
AND d2.id < d1.id
GROUP BY
d1.id
, d1.member_composite_id
, d1.service_date
ORDER BY
d1.id;
The Problem
The bad news is that while this query runs very speedily on the dummy dataset I've given you all here, it takes interminably long on the "real" dataset of ~100 million rows. I've waited as much as 9.5 hours for this thing to finish working, but have had zero luck.
My question is mainly: is there a faster way to do what I'm asking Postgres to do?
What I've tried
I'm not database genius by any means, so the best I've come up with here is to index the variables being used in the query:
create index index_member_comp_id on db1_dummy(member_composite_id)
And so on like that for id, too. But it doesn't seem to make a dent, time-wise. I'm not sure how to benchmark code in Postgres, but it's a bit of a moot point if I can't get the query to run after 10 hours. I've also thought of trimming some variables in the dataset (ones I won't need for analysis), but that only gets me down from ~15 columns to ~11.
I had outside help with the query above, but they're unsure (for now) about how to approach this issue, too. So I decided to see if the boffins on SO have any ideas. Thanks in advance for your kind help.
EDIT
Per Laurenz's request, here's the output for EXPLAIN on the version of the query I've given you here:
+-------------------------------------------------------------------------------------+
|QUERY PLAN |
+-------------------------------------------------------------------------------------+
|GroupAggregate (cost=2.98..3.72 rows=14 width=76) |
| Group Key: d1.id |
| -> Sort (cost=2.98..3.02 rows=14 width=44) |
| Sort Key: d1.id |
| -> Hash Left Join (cost=1.32..2.72 rows=14 width=44) |
| Hash Cond: (d1.member_composite_id = d2.member_composite_id) |
| Join Filter: ((d2.service_date <= d1.service_date) AND (d2.id < d1.id))|
| -> Seq Scan on db1_dummy d1 (cost=0.00..1.14 rows=14 width=40) |
| -> Hash (cost=1.14..1.14 rows=14 width=40) |
| -> Seq Scan on db1_dummy d2 (cost=0.00..1.14 rows=14 width=40) |
+-------------------------------------------------------------------------------------+
Your query is a real server killer(*). Use the window function lag().
select
id,
member_composite_id,
case service_date_1
when service_date_2 then service_date_1- .1
else service_date_1
end as service_date_1,
service_date_2
from (
select
id,
member_composite_id,
lag(service_date, 1, '2009-01-01') over w - '2009-01-01' as service_date_1,
service_date - '2009-01-01' as service_date_2
from db1_dummy
window w as (partition by member_composite_id order by id)
) main_query
order by id
Create the index before running the query
create index on db1_dummy(member_composite_id, id)
Read in the docs:
3.5. Window Functions
9.22. Window Functions
4.2.8. Window Function Calls
(*) The query produces several additional records for each member_composite_id. In the worst case, this is half the Cartesian product. So before the server can group and calculate aggregates, it has to create some several hundred million rows. My laptop couldn't stand it, the server run out of memory on a table with a million rows. Self-joins always are suspicious, especially on large tables.

Activiti and candidate groups

In APS 1.8.1, I have defined a process where each task has a candidate group.
When I login in with a user that belongs to a candidate group, I cannot see the process instance.
I have found out that when I try to access the process instances, APS executes the following query in the database:
select distinct RES.* , DEF.KEY_ as PROC_DEF_KEY_, DEF.NAME_ as PROC_DEF_NAME_, DEF.VERSION_ as PROC_DEF_VERSION_, DEF.DEPLOYMENT_ID_ as DEPLOYMENT_ID_
from ACT_HI_PROCINST RES
left outer join ACT_RE_PROCDEF DEF on RES.PROC_DEF_ID_ = DEF.ID_
left join ACT_HI_IDENTITYLINK I_OR0 on I_OR0.PROC_INST_ID_ = RES.ID_
WHERE RES.TENANT_ID_ = 'tenant_1'
and
( (
exists(select LINK.USER_ID_ from ACT_HI_IDENTITYLINK LINK where USER_ID_ = '1003' and LINK.PROC_INST_ID_ = RES.ID_)
)
or (
I_OR0.TYPE_ = 'participant'
and
I_OR0.GROUP_ID_ IN ('1','2','2023','2013','2024','2009','2025','2026','2027','2028','2029','2007','2018','2020','2017','2015','2012','2003','2021','2019','2004','2002','2005','2030','2031','2032','2011','2006','2008','2014','2010','2016','2022','2033','2034','2035','2036','2037','1003')
) )
order by RES.START_TIME_ desc
LIMIT 50 OFFSET 0
This query does not return any record for two reasons:
In my ACT_HI_IDENTITYLINK no tasks have both the group_id_ and the proc_inst_id_ set.
The type of the record is "candidate" but the query is looking for "participant"
select * fro m ACT_HI_IDENTITYLINK;
-[ RECORD 1 ]-+----------
id_ | 260228
group_id_ |
type_ | starter
user_id_ | 1002
task_id_ |
proc_inst_id_ | 260226
-[ RECORD 2 ]-+----------
id_ | 260294
group_id_ | 2006
type_ | candidate
user_id_ |
task_id_ | 260293
proc_inst_id_ |
-[ RECORD 3 ]-+----------
id_ | 260300
group_id_ | 2009
type_ | candidate
user_id_ |
task_id_ | 260299
proc_inst_id_ |
-[ RECORD 4 ]-+----------
id_ | 262503
group_id_ |
type_ | starter
user_id_ | 1002
task_id_ |
proc_inst_id_ | 262501
-[ RECORD 5 ]-+----------
id_ | 262569
group_id_ | 2016
type_ | candidate
user_id_ |
task_id_ | 262568
proc_inst_id_ |
-[ RECORD 6 ]-+----------
id_ | 262575
group_id_ | 2027
type_ | candidate
user_id_ |
task_id_ | 262574
proc_inst_id_ |
Why the query is looking only for "participant" and why the records that have type_ = 'candidate' do not have any proc_inst_id_ set ?
UPDATE:
The problem with the constraint "participant" has a simple workaround: it would be enough to add the same candidate group as a participant.
See also Feature allowing "Participant" configuration in BPM Modeler
Unfortunately this is not enough to solve the second problem. The record is still not returned because the column proc_inst_id_ is not set.
I tried to update the column manually on the "participant" record and I have verified that doing so the page is accessible and works well.
Does anyone know why the column is not set ?
A possible solution (or workaround until ACTIVITI-696 is fixed) is to add each group added as candidate to a task as a participant of the process instance.
There is a REST API that does it:
POST /enterprise/process-instances/{processInstanceId}/identitylinks
What this API does should be done by a task listener that will automatically add the candidate groups of the created task as participant of the process instance.
To add the new identity link, use in the listener the following lines:
ActivitiEntityEvent aee = (ActivitiEntityEvent)activitiEvent;
TaskEntity taskEntity = (TaskEntity)aee.getEntity();
List<IdentityLinkEntity> identities = taskEntity.getIdentityLinks();
if (identities != null) {
for (IdentityLinkEntity identityLinkEntity : identities) {
String groupId = identityLinkEntity.getGroupId();
runtimeService.addGroupIdentityLink(activitiEvent.getProcessInstanceId(), groupId, "participant");
};
}
first try to check that your workflow is really started by access to "workflow I have started". You should see your task in "active task" if not, that means there is some errors in your definitions. If everything is ok, check your group name and don’t forget to add "GROUP_"myGRPName.
If you want to see the workflow instances it’s simpler with web script and services.

neo4j graphity how to implement

so I have been trying for quite some time to impelement the Graphity on neo4j
But i can find a way to build the queries, anyone have any leads?
for example on the neo4j document for Graphity there is a query just to get only the first element on the chain. how do i get the second one?
and also why there is an order by in the query? isn't that algorithm suppose to eliminate that?
Here is the query:
MATCH p=(me { name: 'Jane' })-[:jane_knows*]->(friend),(friend)-[:has]->(status)
RETURN me.name, friend.name, status.name, length(p)
ORDER BY length(p)
[UPDATED]
That is a variable-length query (notice the * in the relationship pattern), and it gets all the elements in the chain in N result rows (where N is the length of the chain). Each result row's path will contain the previous row's path (if there was a previous row) plus the next element in the chain. And, because every row has a different path length, ordering by the path length makes sense.
If you want to see the names (in order) of all the statuses for each friend, this query should do that:
MATCH p=(me { name: 'Jane' })-[:jane_knows*]->(friend)
WITH me, friend, LENGTH(p) AS len
MATCH (friend)-[:has|next*]->(status)
RETURN me.name, friend.name, COLLECT(status.name), len
ORDER BY len;
With the same data as in the linked example, the result is:
+-----------------------------------------------------+
| me.name | friend.name | COLLECT(status.name) | len |
+-----------------------------------------------------+
| "Jane" | "Bill" | ["Bill_s1","Bill_s2"] | 1 |
| "Jane" | "Joe" | ["Joe_s1","Joe_s2"] | 2 |
| "Jane" | "Bob" | ["Bob_s1"] | 3 |
+-----------------------------------------------------+

Performance issue on CLOB column

I’m facing one issue with a table which has CLOB column.
The table is just a 15column table with one column as CLOB.
When i do SELECT on the table excluding CLOB column, it take only 15min, but if i include this column the SELECT query runs for 2hrs.
Have check the plan and found both the query with and without COLUM uses same Plan.
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
---------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 330K| 61M| 147K (1)| 00:29:34 | | |
| 1 | PARTITION RANGE ALL | | 330K| 61M| 147K (1)| 00:29:34 | 1 | 50 |
| 2 | TABLE ACCESS BY LOCAL INDEX ROWID| CC_CONSUMER_EV_PRFL | 330K| 61M| 147K (1)| 00:29:34 | 1 | 50 |
|* 3 | INDEX RANGE SCAN | CC_CON_EV_P_EV_TYPE_BTIDX | 337K| | 811 (1)| 00:00:10 | 1 | 50 |
Below are the stats i collected.
Stats Without CLOB Column With CLOB Column
recursive calls 0 1
db block gets 0 0
consistent gets 1374615 3131269
physical reads 103874 1042358
redo size 0 0
bytes sent via SQL*Net to client 449499347 3209044367
bytes received via SQL*Net from client 1148445 1288482930
SQL*Net roundtrips to/from client 104373 2215166
sorts (memory)
sorts (disk)
rows processed 1565567 1565567
I'm planing to perform below, is it worth to try?
1) Gather stats on the table and retry
2) compress the table and retry

Sumtotal in ReportViewer

+----------+------------+------+------+--------------+---------+---------+
| | SUBJ | MIN | MAX | RESULT | STATUS | PERCENT |
| +------------+------+------+--------------+---------+---------+
| | Subj1 | 35 | 100 | 13 | FAIL | 13.00% |
|EXAM NAME | Subj2 | 35 | 100 | 63 | PASS | 63.00% |
| | Subj3 | 35 | 100 | 35 | PASS | 35.00% |
| +------------+------+------+--------------+---------+---------+
| | Total | 105 | 300 | 111 | PASS | 37.00% |
+----------+------------+------+------+--------------+---------+---------+
This is my report viewer report format.The SubTotal row counts the
total of all the above column.Every thing is fine. But in the status
column its showing Pass. I want it to show fail if there is single
fail in the status column. I am generating Status if Result < Min then
it is fail or else it is pass. Now how to change the SubTotal row
below depending upon the condition. And is there any way to show the
Subtotal row directly from database. Any suggestion.
The easiest way to do this would be to use custom code (right-click non-display area of report, choose Properties and click the Code tab) - calculate the pass/fail score in the detail, display it in the group footer and reset it in the group header:
Dim PassFail As String
// Reset Pass or Fail status in group header
Public Function ResetAndDisplayStatusTitle() AS String
PassFail = "PASS" // Initialise status to pass
ResetAndDisplayStatusTitle = "Status"
End Function
// Calculate pass/fail on each detail row and remember any fails
Public Function CalculatePassFail() As String
Dim ThisResult As String
// Calculate whether this result is pass or fail
If Fields!Result.Value < Fields!Min.Value Then
ThisResult = "FAIL"
Else
ThisResult ="PASS"
End If
// Remember any failure as overall failure
If ThisResult = "FAIL" Then PassFail = "FAIL"
CalculatePassFail = ThisResult
End Function
Then you tie in the custom code to your cells in your table as follows:
In the value for the status column in your group header you put:
=Code.ResetAndDisplayStatusTitle()
In the value for the status column in the detail row you put:
=Code.CalculatePassFail()
In the value for the status column in the group footer you put:
=Code.PassFail
With respect to getting the subtotal row from the database directly from the database, there are a couple of ways depending on what result you are after.
Join the detail row to a subtotalling row in your SQL (so that the subtotal fields appear on every row in the dataset) and use those fields.
Again, use custom code (but this is probably overly complicated for subtotalling)
However, these tricks are only for strange circumstances and in general the normal out-of-the-box subtotalling can be tweaked to give the result you are after. If there is something specific you want to know, it is probably best to explain the problem in a separate question so that issue can be dealt with individually.

Resources