Skip to content

Commit 0d9889f

Browse files
committed
refs #42782, change from mailing_recipients to mailing_event_queue for better performance
1 parent c754e1d commit 0d9889f

File tree

2 files changed

+52
-48
lines changed

2 files changed

+52
-48
lines changed

CRM/Core/TableHierarchy.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,9 @@ class CRM_Core_TableHierarchy {
7070
'civicrm_activity' => '33',
7171
'civicrm_mailing_summary' => '35',
7272
'civicrm_mailing_recipients' => '36',
73-
'civicrm_mailing' => '37',
73+
'civicrm_mailing_event_queue' => '37',
7474
'civicrm_mailing_job' => '38',
75-
'civicrm_mailing_event_queue' => '39',
75+
'civicrm_mailing' => '39',
7676
'civicrm_mailing_event_bounce' => '40',
7777
'civicrm_mailing_event_opened' => '41',
7878
'civicrm_mailing_event_reply' => '42',

CRM/Mailing/BAO/Query.php

Lines changed: 50 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -55,17 +55,10 @@ public static function &getFields() {
5555
* @param $query
5656
*/
5757
public static function select(&$query) {
58-
// if Mailing mode add mailing id
5958
if ($query->_mode & CRM_Contact_BAO_Query::MODE_MAILING) {
6059
$query->_select['mailing_id'] = "civicrm_mailing.id as mailing_id";
6160
$query->_element['mailing_id'] = 1;
6261

63-
// base table is contact, so join recipients to it
64-
$query->_tables['civicrm_mailing_recipients'] = $query->_whereTables['civicrm_mailing_recipients']
65-
= " INNER JOIN civicrm_mailing_recipients ON civicrm_mailing_recipients.contact_id = contact_a.id ";
66-
67-
$query->_tables['civicrm_mailing'] = $query->_whereTables['civicrm_mailing'] = 1;
68-
6962
// get mailing name
7063
if (!empty($query->_returnProperties['mailing_name'])) {
7164
$query->_select['mailing_name'] = "civicrm_mailing.name as mailing_name";
@@ -80,50 +73,47 @@ public static function select(&$query) {
8073

8174
// get mailing status
8275
if (!empty($query->_returnProperties['mailing_job_status'])) {
83-
$query->_tables['civicrm_mailing_job'] = $query->_whereTables['civicrm_mailing_job']
84-
= " LEFT JOIN civicrm_mailing_job ON civicrm_mailing_job.mailing_id = civicrm_mailing.id AND civicrm_mailing_job.parent_id IS NULL AND civicrm_mailing_job.is_test != 1 ";
8576
$query->_select['mailing_job_status'] = "civicrm_mailing_job.status as mailing_job_status";
8677
$query->_element['mailing_job_status'] = 1;
8778
}
8879

89-
// get email on hold
80+
// get email on hold - only include if needed
9081
if (!empty($query->_returnProperties['email_on_hold'])) {
9182
$query->_select['email_on_hold'] = "recipient_email.on_hold as email_on_hold";
9283
$query->_element['email_on_hold'] = 1;
9384
$query->_tables['recipient_email'] = $query->_whereTables['recipient_email'] = 1;
9485
}
9586

96-
// get recipient email
87+
// get recipient email - only include if needed
9788
if (!empty($query->_returnProperties['email'])) {
9889
$query->_select['email'] = "recipient_email.email as email";
9990
$query->_element['email'] = 1;
10091
$query->_tables['recipient_email'] = $query->_whereTables['recipient_email'] = 1;
10192
}
10293

103-
// get user opt out
94+
// get user opt out - direct from contact table
10495
if (!empty($query->_returnProperties['contact_opt_out'])) {
10596
$query->_select['contact_opt_out'] = "contact_a.is_opt_out as contact_opt_out";
10697
$query->_element['contact_opt_out'] = 1;
10798
}
10899

109100
// mailing job end date / completed date
110101
if (!empty($query->_returnProperties['mailing_job_end_date'])) {
111-
$query->_tables['civicrm_mailing_job'] = $query->_whereTables['civicrm_mailing_job']
112-
= " LEFT JOIN civicrm_mailing_job ON civicrm_mailing_job.mailing_id = civicrm_mailing.id AND civicrm_mailing_job.parent_id IS NULL AND civicrm_mailing_job.is_test != 1 ";
113102
$query->_select['mailing_job_end_date'] = "civicrm_mailing_job.end_date as mailing_job_end_date";
114103
$query->_element['mailing_job_end_date'] = 1;
115104
}
116105

106+
// Only add mailing recipients ID if specifically requested
117107
if (!empty($query->_returnProperties['mailing_recipients_id'])) {
118108
$query->_select['mailing_recipients_id'] = " civicrm_mailing_recipients.id as mailing_recipients_id";
119109
$query->_element['mailing_recipients_id'] = 1;
120110
}
121-
}
122111

123-
if (CRM_Utils_Array::value('mailing_campaign_id', $query->_returnProperties)) {
124-
$query->_select['mailing_campaign_id'] = 'civicrm_mailing.campaign_id as mailing_campaign_id';
125-
$query->_element['mailing_campaign_id'] = 1;
126-
$query->_tables['civicrm_campaign'] = 1;
112+
if (CRM_Utils_Array::value('mailing_campaign_id', $query->_returnProperties)) {
113+
$query->_select['mailing_campaign_id'] = 'civicrm_mailing.campaign_id as mailing_campaign_id';
114+
$query->_element['mailing_campaign_id'] = 1;
115+
$query->_tables['civicrm_campaign'] = 1;
116+
}
127117
}
128118
}
129119

@@ -143,7 +133,6 @@ public static function where(&$query) {
143133
}
144134
$grouping = $query->_params[$id][3];
145135
self::whereClauseSingle($query->_params[$id], $query);
146-
147136
if (!$excluded) {
148137
// refs #33948, restrict hidden mailing report
149138
$query->_tables['civicrm_mailing'] = $query->_whereTables['civicrm_mailing'] = 1;
@@ -175,18 +164,17 @@ public static function from($name, $mode, $side) {
175164
break;
176165

177166
case 'civicrm_mailing_event_queue':
178-
// this is tightly binded so as to do a check WRT actual job recipients ('child' type jobs)
179-
$from = " INNER JOIN civicrm_mailing_event_queue ON
180-
civicrm_mailing_event_queue.contact_id = civicrm_mailing_recipients.contact_id
181-
AND civicrm_mailing_event_queue.job_id = civicrm_mailing_job.id AND civicrm_mailing_job.job_type = 'child'";
167+
// Add a direct join to contact rather than going through recipients when possible
168+
$from = " INNER JOIN civicrm_mailing_event_queue ON civicrm_mailing_event_queue.contact_id = contact_a.id";
182169
break;
183170

184171
case 'civicrm_mailing':
185-
$from = " $side JOIN civicrm_mailing ON civicrm_mailing.id = civicrm_mailing_recipients.mailing_id ";
172+
// Optimize the join by directly connecting to mailing job when possible
173+
$from = " $side JOIN civicrm_mailing ON civicrm_mailing.id = civicrm_mailing_job.mailing_id AND civicrm_mailing.is_hidden = 0";
186174
break;
187175

188176
case 'civicrm_mailing_job':
189-
$from = " $side JOIN civicrm_mailing_job ON civicrm_mailing_job.mailing_id = civicrm_mailing.id AND civicrm_mailing_job.is_test != 1 ";
177+
$from = " $side JOIN civicrm_mailing_job ON civicrm_mailing_event_queue.job_id = civicrm_mailing_job.id AND civicrm_mailing_job.is_test != 1 and civicrm_mailing_job.job_type = 'child'";
190178
break;
191179

192180
case 'civicrm_mailing_event_bounce':
@@ -196,17 +184,18 @@ public static function from($name, $mode, $side) {
196184
case 'civicrm_mailing_event_unsubscribe':
197185
case 'civicrm_mailing_event_forward':
198186
case 'civicrm_mailing_event_trackable_url_open':
199-
$from = " $side JOIN $name ON $name.event_queue_id = civicrm_mailing_event_queue.id";
187+
// Use LEFT JOIN for better performance on "NOT IN" type queries
188+
$joinType = ($side == 'LEFT' || strpos($name, '_delivered') !== false) ? 'LEFT' : $side;
189+
$from = " $joinType JOIN $name ON $name.event_queue_id = civicrm_mailing_event_queue.id";
200190
break;
201191

202192
case 'recipient_email':
203-
$from = " $side JOIN civicrm_email recipient_email ON recipient_email.id = civicrm_mailing_recipients.email_id";
193+
$from = " $side JOIN civicrm_email recipient_email ON recipient_email.id = civicrm_mailing_event_queue.email_id";
204194
break;
205195

206196
case 'civicrm_mailing_trackable_url':
207197
$from = " $side JOIN civicrm_mailing_trackable_url ON civicrm_mailing_trackable_url.id = civicrm_mailing_event_trackable_url_open.trackable_url_id";
208198
break;
209-
210199
}
211200

212201
return $from;
@@ -268,8 +257,9 @@ public static function whereClauseSingle(&$values, &$query) {
268257
$selectedMailings = CRM_Utils_Array::implode(' '.ts('or').' ', $selectedMailings);
269258

270259
$query->_qill[$grouping][] = ts("Mailing Name")." - \"$selectedMailings\"";
260+
$query->_tables['civicrm_mailing_event_queue'] = $query->_whereTables['civicrm_mailing_event_queue'] = 1;
261+
$query->_tables['civicrm_mailing_job'] = $query->_whereTables['civicrm_mailing_job'] = 1;
271262
$query->_tables['civicrm_mailing'] = $query->_whereTables['civicrm_mailing'] = 1;
272-
$query->_tables['civicrm_mailing_recipients'] = $query->_whereTables['civicrm_mailing_recipients'] = 1;
273263
return;
274264

275265
case 'mailing_name':
@@ -280,8 +270,9 @@ public static function whereClauseSingle(&$values, &$query) {
280270
}
281271
$query->_where[$grouping][] = "LOWER(civicrm_mailing.name) $op '$value'";
282272
$query->_qill[$grouping][] = ts("Mailing Name")." - \"$value\"";
273+
$query->_tables['civicrm_mailing_event_queue'] = $query->_whereTables['civicrm_mailing_event_queue'] = 1;
274+
$query->_tables['civicrm_mailing_job'] = $query->_whereTables['civicrm_mailing_job'] = 1;
283275
$query->_tables['civicrm_mailing'] = $query->_whereTables['civicrm_mailing'] = 1;
284-
$query->_tables['civicrm_mailing_recipients'] = $query->_whereTables['civicrm_mailing_recipients'] = 1;
285276
return;
286277

287278
case 'mailing_date_low':
@@ -303,9 +294,16 @@ public static function whereClauseSingle(&$values, &$query) {
303294
}
304295
}
305296
if (!empty($subWhere)) {
306-
$subQuery = "SELECT cmj.mailing_id FROM civicrm_mailing_job as cmj WHERE cmj.is_test != 1 AND cmj.parent_id IS NULL AND ".implode(' AND ', $subWhere)." GROUP BY mailing_id";
307-
$query->_where[$grouping]['mailing_date'] = "civicrm_mailing_recipients.mailing_id IN ($subQuery)";
308-
$query->_tables['civicrm_mailing_recipients'] = $query->_whereTables['civicrm_mailing_recipients'] = 1;
297+
// Use a more efficient subquery approach to filter by mailing date
298+
$subQuery = "SELECT cmj.mailing_id FROM civicrm_mailing_job as cmj WHERE cmj.is_test != 1 AND cmj.parent_id IS NULL AND ".implode(' AND ', $subWhere)." GROUP BY cmj.mailing_id";
299+
300+
// Set the query directly on mailing_id for better performance
301+
$query->_where[$grouping]['mailing_date'] = "civicrm_mailing.id IN ($subQuery)";
302+
303+
// Ensure necessary tables are included
304+
$query->_tables['civicrm_mailing_event_queue'] = $query->_whereTables['civicrm_mailing_event_queue'] = 1;
305+
$query->_tables['civicrm_mailing_job'] = $query->_whereTables['civicrm_mailing_job'] = 1;
306+
$query->_tables['civicrm_mailing'] = $query->_whereTables['civicrm_mailing'] = 1;
309307
}
310308
return;
311309

@@ -317,7 +315,7 @@ public static function whereClauseSingle(&$values, &$query) {
317315
self::mailingEventQueryBuilder($query, $values,
318316
'civicrm_mailing_event_delivered',
319317
'mailing_delivery_status',
320-
ts('Mailing Delivery'),
318+
ts('Delivery Status'),
321319
$options
322320
);
323321
}
@@ -327,7 +325,7 @@ public static function whereClauseSingle(&$values, &$query) {
327325
self::mailingEventQueryBuilder($query, $values,
328326
'civicrm_mailing_event_bounce',
329327
'mailing_delivery_status',
330-
ts('Mailing Delivery'),
328+
ts('Delivery Status'),
331329
$options
332330
);
333331
}
@@ -364,10 +362,9 @@ public static function whereClauseSingle(&$values, &$query) {
364362
$query->_where[$grouping][] = "civicrm_mailing_trackable_url.url $op '$value'";
365363
$query->_qill[$grouping][] = ts("URL")." - \"$value\"";
366364

367-
$query->_tables['civicrm_mailing'] = $query->_whereTables['civicrm_mailing'] = 1;
368-
$query->_tables['civicrm_mailing_job'] = $query->_whereTables['civicrm_mailing_job'] = 1;
369365
$query->_tables['civicrm_mailing_event_queue'] = $query->_whereTables['civicrm_mailing_event_queue'] = 1;
370-
$query->_tables['civicrm_mailing_recipients'] = $query->_whereTables['civicrm_mailing_recipients'] = 1;
366+
$query->_tables['civicrm_mailing_job'] = $query->_whereTables['civicrm_mailing_job'] = 1;
367+
$query->_tables['civicrm_mailing'] = $query->_whereTables['civicrm_mailing'] = 1;
371368
$query->_tables['civicrm_mailing_event_trackable_url_open'] = $query->_whereTables['civicrm_mailing_event_trackable_url_open'] = 1;
372369
$query->_tables['civicrm_mailing_trackable_url'] = $query->_whereTables['civicrm_mailing_trackable_url'] = 1;
373370
return;
@@ -413,8 +410,9 @@ public static function whereClauseSingle(&$values, &$query) {
413410
$subWhere = array();
414411
$subWhere[] = "cmj.status = '{$value}'";
415412
$subQuery = "SELECT cmj.mailing_id FROM civicrm_mailing_job as cmj WHERE cmj.is_test != 1 AND cmj.parent_id IS NULL AND ".implode(' AND ', $subWhere)." GROUP BY mailing_id";
416-
$query->_where[$grouping]['mailing_job_status'] = "civicrm_mailing_recipients.mailing_id IN ($subQuery)";
417-
$query->_tables['civicrm_mailing_recipients'] = $query->_whereTables['civicrm_mailing_recipients'] = 1;
413+
$query->_where[$grouping]['mailing_job_status'] = "civicrm_mailing_job.mailing_id IN ($subQuery)";
414+
$query->_tables['civicrm_mailing_event_queue'] = $query->_whereTables['civicrm_mailing_event_queue'] = 1;
415+
$query->_tables['civicrm_mailing_job'] = $query->_whereTables['civicrm_mailing_job'] = 1;
418416

419417
$query->_qill[$grouping][] = ts("Mailing Status")." - ".ts($value);
420418
}
@@ -507,10 +505,14 @@ public static function mailingEventQueryBuilder(&$query, &$values, $tableName, $
507505
}
508506

509507
if ($value == 'Y') {
510-
$query->_where[$grouping][] = $tableName . ".id is not null ";
508+
// Use EXISTS subquery for better performance with delivery status
509+
$subquery = "EXISTS (SELECT 1 FROM $tableName WHERE $tableName.event_queue_id = civicrm_mailing_event_queue.id)";
510+
$query->_where[$grouping][] = $subquery;
511511
}
512512
elseif ($value == 'N') {
513-
$query->_where[$grouping][] = $tableName . ".id is null ";
513+
// Use NOT EXISTS subquery for better performance with delivery status
514+
$subquery = "NOT EXISTS (SELECT 1 FROM $tableName WHERE $tableName.event_queue_id = civicrm_mailing_event_queue.id)";
515+
$query->_where[$grouping][] = $subquery;
514516
}
515517

516518
if (is_array($value)) {
@@ -521,10 +523,12 @@ public static function mailingEventQueryBuilder(&$query, &$values, $tableName, $
521523
$query->_qill[$grouping][] = $fieldTitle . ' - ' . $valueTitles[$value];
522524
}
523525

524-
$query->_tables['civicrm_mailing'] = $query->_whereTables['civicrm_mailing'] = 1;
525-
$query->_tables['civicrm_mailing_job'] = $query->_whereTables['civicrm_mailing_job'] = 1;
526+
// Always include these required tables
526527
$query->_tables['civicrm_mailing_event_queue'] = $query->_whereTables['civicrm_mailing_event_queue'] = 1;
527-
$query->_tables['civicrm_mailing_recipients'] = $query->_whereTables['civicrm_mailing_recipients'] = 1;
528+
$query->_tables['civicrm_mailing_job'] = $query->_whereTables['civicrm_mailing_job'] = 1;
529+
$query->_tables['civicrm_mailing'] = $query->_whereTables['civicrm_mailing'] = 1;
530+
531+
// Include the specific event table
528532
$query->_tables[$tableName] = $query->_whereTables[$tableName] = 1;
529533
}
530534

0 commit comments

Comments
 (0)