与 SQL 客户端中的 运行 相同查询相比,PHP 查询返回的结果正好少 1 个
PHP query returning exactly 1 less result compared to running the same query in a SQL client
我有一个查询要获取一些数据。当我运行在TablePlus(数据库客户端)的SQL编辑器中查询时,结果符合预期。
当我将查询转换为我的 PHP 项目时,结果每次都偏离 1 条记录。下图是通过在 PHP 中执行查询对结果 return 进行 var_dump
的结果。注意数组值的数量和第一个结果。
可以看到 SQL 将 return 9 个结果,最上面的结果有 id
57115(正确)。 PHP 将 return 8 个结果与 id
57115 的记录丢失(不正确)。我的 PHP 项目使用 CodeIgniter 3.1.10 版。我试过使用 CodeIgniter 的查询生成器、常规 $this->db->query()
函数和 PHP 的 mysqli
扩展。所有这三种方法都给出相同的 8 行结果。也不只是 8 和 9 个结果,如果我修改查询,它总是会差一个。例如,我使用
行限制了按日期 return 编辑的结果
and `acd_3cx_leads`.`lastModified` >= '2022-01-01'
删除它会 return 2,449 个结果 SQL 而 PHP 会 return 2,448 个结果。再次落后 1。我完全不知道是什么原因造成的,它在所有 4 种实现中都是完全相同的查询,那么为什么 PHP 差了一个?
作为参考,这里有 4 个查询:
SQL
select
`acd_3cx_leads`.`id`,
`acd_3cx_leads`.`dialCount`,
@hoursSinceLastUpdate := timestampdiff(hour, `acd_3cx_leadstatus`.`createdOn`, NOW()) hoursSinceLastUpdate,
max(acd_3cx_leadstatus.createdOn) leadCountLastUpdated,
max(acd_3cx_lead_schedule_call_back_sms_log.smsType) lastSmsType
from
`acd_3cx_leads`
left join `acd_3cx_leadstatus` on `acd_3cx_leadstatus`.`leadId` = `acd_3cx_leads`.`id`
and `acd_3cx_leadstatus`.`dialCount` = `acd_3cx_leads`.`dialCount`
left join `acd_3cx_lead_schedule_call_back_sms_log` on `acd_3cx_lead_schedule_call_back_sms_log`.`leadId` = `acd_3cx_leads`.`id`
where
`acd_3cx_leads`.`doNotContact` = 0
and `acd_3cx_leads`.`active` = 1
and `acd_3cx_leads`.`closeTypeId` is null
and `acd_3cx_leads`.`lastModified` >= '2022-01-01'
group by
`acd_3cx_leads`.`id`
having (acd_3cx_leads.dialCount = 1
and @hoursSinceLastUpdate >= 5
and lastSmsType is null)
OR(acd_3cx_leads.dialCount = 1
and @hoursSinceLastUpdate >= 24
and(lastSmsType is null
or lastSmsType < 2))
OR(acd_3cx_leads.dialCount = 1
and @hoursSinceLastUpdate >= 48
and(lastSmsType is null
or lastSmsType < 3))
OR(acd_3cx_leads.dialCount = 2
and @hoursSinceLastUpdate >= 4
and lastSmsType is null)
OR(acd_3cx_leads.dialCount = 2
and @hoursSinceLastUpdate >= 24
and(lastSmsType is null
or lastSmsType < 2))
OR(acd_3cx_leads.dialCount = 2
and @hoursSinceLastUpdate >= 48
and(lastSmsType is null
or lastSmsType < 3))
OR(acd_3cx_leads.dialCount = 3
and @hoursSinceLastUpdate >= 6
and(lastSmsType is null
or lastSmsType < 4))
order by
`acd_3cx_leads`.`lastModified` desc
CodeIgniter 的查询生成器
public function getRedialLeads(): array
{
$lead = TABLE_NAMES['acd_3cx_leads'];
$leadStatus = TABLE_NAMES['acd_3cx_leadstatus'];
$smsLog = TABLE_NAMES['acd_3cx_lead_schedule_call_back_sms_log'];
$this->db->select("
{$lead}.id,
{$lead}.dialCount,
@hoursSinceLastUpdate := timestampdiff(hour, {$leadStatus}.createdOn, NOW()) hoursSinceLastUpdate,
max({$leadStatus}.createdOn) leadCountLastUpdated,
max({$smsLog}.smsType) lastSmsType
");
$this->db->join($leadStatus, "{$leadStatus}.leadId = {$lead}.id and {$leadStatus}.dialCount = {$lead}.dialCount", 'left');
$this->db->join($smsLog, "{$smsLog}.leadId = {$lead}.id", 'left');
$this->db->where("{$lead}.doNotContact", 0);
$this->db->where("{$lead}.active", 1);
$this->db->where("{$lead}.closeTypeId is null");
$this->db->where("{$lead}.lastModified >= '2022-01-01'");
$this->db->group_by("{$lead}.id");
// Dial 2 after +5 hours
$this->db->having("(
{$lead}.dialCount = 1
and @hoursSinceLastUpdate >= 5
and lastSmsType is null
)", null, false);
// Dial 2 after +24 hours
$this->db->or_having("(
{$lead}.dialCount = 1
and @hoursSinceLastUpdate >= 24
and (lastSmsType is null or lastSmsType < 2)
)", null, false);
// Dial 2 after +48 hours
$this->db->or_having("(
{$lead}.dialCount = 1
and @hoursSinceLastUpdate >= 48
and (lastSmsType is null or lastSmsType < 3)
)", null, false);
// Dial 3 after +4 hours
$this->db->or_having("(
{$lead}.dialCount = 2
and @hoursSinceLastUpdate >= 4
and lastSmsType is null
)", null, false);
// Dial 3 after +24 hours
$this->db->or_having("(
{$lead}.dialCount = 2
and @hoursSinceLastUpdate >= 24
and (lastSmsType is null or lastSmsType < 2)
)", null, false);
// Dial 3 after +48 hours
$this->db->or_having("(
{$lead}.dialCount = 2
and @hoursSinceLastUpdate >= 48
and (lastSmsType is null or lastSmsType < 3)
)", null, false);
// Dial 4 after +6 hours
$this->db->or_having("(
{$lead}.dialCount = 3
and @hoursSinceLastUpdate >= 6
and (lastSmsType is null or lastSmsType < 4)
)", null, false);
$this->db->order_by("{$lead}.lastModified desc");
return $this->db->get($lead)->result_array();
}
CodeIgniter 的查询函数
public function getRedialLeads2(): array
{
$query = $this->db->query("
select
`acd_3cx_leads`.`id`,
`acd_3cx_leads`.`dialCount`,
@hoursSinceLastUpdate := timestampdiff(hour, `acd_3cx_leadstatus`.`createdOn`, NOW()) hoursSinceLastUpdate,
max(acd_3cx_leadstatus.createdOn) leadCountLastUpdated,
max(acd_3cx_lead_schedule_call_back_sms_log.smsType) lastSmsType
from
`acd_3cx_leads`
left join `acd_3cx_leadstatus` on `acd_3cx_leadstatus`.`leadId` = `acd_3cx_leads`.`id`
and `acd_3cx_leadstatus`.`dialCount` = `acd_3cx_leads`.`dialCount`
left join `acd_3cx_lead_schedule_call_back_sms_log` on `acd_3cx_lead_schedule_call_back_sms_log`.`leadId` = `acd_3cx_leads`.`id`
where
`acd_3cx_leads`.`doNotContact` = 0
and `acd_3cx_leads`.`active` = 1
and `acd_3cx_leads`.`closeTypeId` is null
and `acd_3cx_leads`.`lastModified` >= '2022-01-01'
group by
`acd_3cx_leads`.`id`
having (acd_3cx_leads.dialCount = 1
and @hoursSinceLastUpdate >= 5
and lastSmsType is null)
OR(acd_3cx_leads.dialCount = 1
and @hoursSinceLastUpdate >= 24
and(lastSmsType is null
or lastSmsType < 2))
OR(acd_3cx_leads.dialCount = 1
and @hoursSinceLastUpdate >= 48
and(lastSmsType is null
or lastSmsType < 3))
OR(acd_3cx_leads.dialCount = 2
and @hoursSinceLastUpdate >= 4
and lastSmsType is null)
OR(acd_3cx_leads.dialCount = 2
and @hoursSinceLastUpdate >= 24
and(lastSmsType is null
or lastSmsType < 2))
OR(acd_3cx_leads.dialCount = 2
and @hoursSinceLastUpdate >= 48
and(lastSmsType is null
or lastSmsType < 3))
OR(acd_3cx_leads.dialCount = 3
and @hoursSinceLastUpdate >= 6
and(lastSmsType is null
or lastSmsType < 4))
order by
`acd_3cx_leads`.`lastModified` desc
");
return $query->result_array();
}
PHP 的 mysqli 扩展
public function getRedialLeads3(): array
{
// Obviously omitted sensitive details
$mysqli = mysqli_connect('hostname', 'username', 'password', 'database');
$query = "
select
`acd_3cx_leads`.`id`,
`acd_3cx_leads`.`dialCount`,
@hoursSinceLastUpdate := timestampdiff(hour, `acd_3cx_leadstatus`.`createdOn`, NOW()) hoursSinceLastUpdate,
max(acd_3cx_leadstatus.createdOn) leadCountLastUpdated,
max(acd_3cx_lead_schedule_call_back_sms_log.smsType) lastSmsType
from
`acd_3cx_leads`
left join `acd_3cx_leadstatus` on `acd_3cx_leadstatus`.`leadId` = `acd_3cx_leads`.`id`
and `acd_3cx_leadstatus`.`dialCount` = `acd_3cx_leads`.`dialCount`
left join `acd_3cx_lead_schedule_call_back_sms_log` on `acd_3cx_lead_schedule_call_back_sms_log`.`leadId` = `acd_3cx_leads`.`id`
where
`acd_3cx_leads`.`doNotContact` = 0
and `acd_3cx_leads`.`active` = 1
and `acd_3cx_leads`.`closeTypeId` is null
and `acd_3cx_leads`.`lastModified` >= '2022-01-01'
group by
`acd_3cx_leads`.`id`
having (acd_3cx_leads.dialCount = 1
and @hoursSinceLastUpdate >= 5
and lastSmsType is null)
OR(acd_3cx_leads.dialCount = 1
and @hoursSinceLastUpdate >= 24
and(lastSmsType is null
or lastSmsType < 2))
OR(acd_3cx_leads.dialCount = 1
and @hoursSinceLastUpdate >= 48
and(lastSmsType is null
or lastSmsType < 3))
OR(acd_3cx_leads.dialCount = 2
and @hoursSinceLastUpdate >= 4
and lastSmsType is null)
OR(acd_3cx_leads.dialCount = 2
and @hoursSinceLastUpdate >= 24
and(lastSmsType is null
or lastSmsType < 2))
OR(acd_3cx_leads.dialCount = 2
and @hoursSinceLastUpdate >= 48
and(lastSmsType is null
or lastSmsType < 3))
OR(acd_3cx_leads.dialCount = 3
and @hoursSinceLastUpdate >= 6
and(lastSmsType is null
or lastSmsType < 4))
order by
`acd_3cx_leads`.`lastModified` desc
";
$result = mysqli_query($mysqli, $query, MYSQLI_USE_RESULT);
return mysqli_fetch_all($result, MYSQLI_ASSOC);
}
根据@mickmackusa 的建议,我从 SELECT
和 HAVING
子句中删除了 SQL 变量 hoursSinceLastUpdate
。这解决了我的问题。所以 SELECT
行
@hoursSinceLastUpdate := timestampdiff(hour, `acd_3cx_leadstatus`.`createdOn`, NOW()) hoursSinceLastUpdate,
现在
timestampdiff(hour, `acd_3cx_leadstatus`.`createdOn`, NOW()) hoursSinceLastUpdate,
在 HAVING
中,所有出现的
and @hoursSinceLastUpdate >= x
现在
and hoursSinceLastUpdate >= x
因此 SQL 和 PHP 的记录 return 之间现在存在奇偶校验。
我有一个查询要获取一些数据。当我运行在TablePlus(数据库客户端)的SQL编辑器中查询时,结果符合预期。
当我将查询转换为我的 PHP 项目时,结果每次都偏离 1 条记录。下图是通过在 PHP 中执行查询对结果 return 进行 var_dump
的结果。注意数组值的数量和第一个结果。
可以看到 SQL 将 return 9 个结果,最上面的结果有 id
57115(正确)。 PHP 将 return 8 个结果与 id
57115 的记录丢失(不正确)。我的 PHP 项目使用 CodeIgniter 3.1.10 版。我试过使用 CodeIgniter 的查询生成器、常规 $this->db->query()
函数和 PHP 的 mysqli
扩展。所有这三种方法都给出相同的 8 行结果。也不只是 8 和 9 个结果,如果我修改查询,它总是会差一个。例如,我使用
and `acd_3cx_leads`.`lastModified` >= '2022-01-01'
删除它会 return 2,449 个结果 SQL 而 PHP 会 return 2,448 个结果。再次落后 1。我完全不知道是什么原因造成的,它在所有 4 种实现中都是完全相同的查询,那么为什么 PHP 差了一个?
作为参考,这里有 4 个查询:
SQL
select
`acd_3cx_leads`.`id`,
`acd_3cx_leads`.`dialCount`,
@hoursSinceLastUpdate := timestampdiff(hour, `acd_3cx_leadstatus`.`createdOn`, NOW()) hoursSinceLastUpdate,
max(acd_3cx_leadstatus.createdOn) leadCountLastUpdated,
max(acd_3cx_lead_schedule_call_back_sms_log.smsType) lastSmsType
from
`acd_3cx_leads`
left join `acd_3cx_leadstatus` on `acd_3cx_leadstatus`.`leadId` = `acd_3cx_leads`.`id`
and `acd_3cx_leadstatus`.`dialCount` = `acd_3cx_leads`.`dialCount`
left join `acd_3cx_lead_schedule_call_back_sms_log` on `acd_3cx_lead_schedule_call_back_sms_log`.`leadId` = `acd_3cx_leads`.`id`
where
`acd_3cx_leads`.`doNotContact` = 0
and `acd_3cx_leads`.`active` = 1
and `acd_3cx_leads`.`closeTypeId` is null
and `acd_3cx_leads`.`lastModified` >= '2022-01-01'
group by
`acd_3cx_leads`.`id`
having (acd_3cx_leads.dialCount = 1
and @hoursSinceLastUpdate >= 5
and lastSmsType is null)
OR(acd_3cx_leads.dialCount = 1
and @hoursSinceLastUpdate >= 24
and(lastSmsType is null
or lastSmsType < 2))
OR(acd_3cx_leads.dialCount = 1
and @hoursSinceLastUpdate >= 48
and(lastSmsType is null
or lastSmsType < 3))
OR(acd_3cx_leads.dialCount = 2
and @hoursSinceLastUpdate >= 4
and lastSmsType is null)
OR(acd_3cx_leads.dialCount = 2
and @hoursSinceLastUpdate >= 24
and(lastSmsType is null
or lastSmsType < 2))
OR(acd_3cx_leads.dialCount = 2
and @hoursSinceLastUpdate >= 48
and(lastSmsType is null
or lastSmsType < 3))
OR(acd_3cx_leads.dialCount = 3
and @hoursSinceLastUpdate >= 6
and(lastSmsType is null
or lastSmsType < 4))
order by
`acd_3cx_leads`.`lastModified` desc
CodeIgniter 的查询生成器
public function getRedialLeads(): array
{
$lead = TABLE_NAMES['acd_3cx_leads'];
$leadStatus = TABLE_NAMES['acd_3cx_leadstatus'];
$smsLog = TABLE_NAMES['acd_3cx_lead_schedule_call_back_sms_log'];
$this->db->select("
{$lead}.id,
{$lead}.dialCount,
@hoursSinceLastUpdate := timestampdiff(hour, {$leadStatus}.createdOn, NOW()) hoursSinceLastUpdate,
max({$leadStatus}.createdOn) leadCountLastUpdated,
max({$smsLog}.smsType) lastSmsType
");
$this->db->join($leadStatus, "{$leadStatus}.leadId = {$lead}.id and {$leadStatus}.dialCount = {$lead}.dialCount", 'left');
$this->db->join($smsLog, "{$smsLog}.leadId = {$lead}.id", 'left');
$this->db->where("{$lead}.doNotContact", 0);
$this->db->where("{$lead}.active", 1);
$this->db->where("{$lead}.closeTypeId is null");
$this->db->where("{$lead}.lastModified >= '2022-01-01'");
$this->db->group_by("{$lead}.id");
// Dial 2 after +5 hours
$this->db->having("(
{$lead}.dialCount = 1
and @hoursSinceLastUpdate >= 5
and lastSmsType is null
)", null, false);
// Dial 2 after +24 hours
$this->db->or_having("(
{$lead}.dialCount = 1
and @hoursSinceLastUpdate >= 24
and (lastSmsType is null or lastSmsType < 2)
)", null, false);
// Dial 2 after +48 hours
$this->db->or_having("(
{$lead}.dialCount = 1
and @hoursSinceLastUpdate >= 48
and (lastSmsType is null or lastSmsType < 3)
)", null, false);
// Dial 3 after +4 hours
$this->db->or_having("(
{$lead}.dialCount = 2
and @hoursSinceLastUpdate >= 4
and lastSmsType is null
)", null, false);
// Dial 3 after +24 hours
$this->db->or_having("(
{$lead}.dialCount = 2
and @hoursSinceLastUpdate >= 24
and (lastSmsType is null or lastSmsType < 2)
)", null, false);
// Dial 3 after +48 hours
$this->db->or_having("(
{$lead}.dialCount = 2
and @hoursSinceLastUpdate >= 48
and (lastSmsType is null or lastSmsType < 3)
)", null, false);
// Dial 4 after +6 hours
$this->db->or_having("(
{$lead}.dialCount = 3
and @hoursSinceLastUpdate >= 6
and (lastSmsType is null or lastSmsType < 4)
)", null, false);
$this->db->order_by("{$lead}.lastModified desc");
return $this->db->get($lead)->result_array();
}
CodeIgniter 的查询函数
public function getRedialLeads2(): array
{
$query = $this->db->query("
select
`acd_3cx_leads`.`id`,
`acd_3cx_leads`.`dialCount`,
@hoursSinceLastUpdate := timestampdiff(hour, `acd_3cx_leadstatus`.`createdOn`, NOW()) hoursSinceLastUpdate,
max(acd_3cx_leadstatus.createdOn) leadCountLastUpdated,
max(acd_3cx_lead_schedule_call_back_sms_log.smsType) lastSmsType
from
`acd_3cx_leads`
left join `acd_3cx_leadstatus` on `acd_3cx_leadstatus`.`leadId` = `acd_3cx_leads`.`id`
and `acd_3cx_leadstatus`.`dialCount` = `acd_3cx_leads`.`dialCount`
left join `acd_3cx_lead_schedule_call_back_sms_log` on `acd_3cx_lead_schedule_call_back_sms_log`.`leadId` = `acd_3cx_leads`.`id`
where
`acd_3cx_leads`.`doNotContact` = 0
and `acd_3cx_leads`.`active` = 1
and `acd_3cx_leads`.`closeTypeId` is null
and `acd_3cx_leads`.`lastModified` >= '2022-01-01'
group by
`acd_3cx_leads`.`id`
having (acd_3cx_leads.dialCount = 1
and @hoursSinceLastUpdate >= 5
and lastSmsType is null)
OR(acd_3cx_leads.dialCount = 1
and @hoursSinceLastUpdate >= 24
and(lastSmsType is null
or lastSmsType < 2))
OR(acd_3cx_leads.dialCount = 1
and @hoursSinceLastUpdate >= 48
and(lastSmsType is null
or lastSmsType < 3))
OR(acd_3cx_leads.dialCount = 2
and @hoursSinceLastUpdate >= 4
and lastSmsType is null)
OR(acd_3cx_leads.dialCount = 2
and @hoursSinceLastUpdate >= 24
and(lastSmsType is null
or lastSmsType < 2))
OR(acd_3cx_leads.dialCount = 2
and @hoursSinceLastUpdate >= 48
and(lastSmsType is null
or lastSmsType < 3))
OR(acd_3cx_leads.dialCount = 3
and @hoursSinceLastUpdate >= 6
and(lastSmsType is null
or lastSmsType < 4))
order by
`acd_3cx_leads`.`lastModified` desc
");
return $query->result_array();
}
PHP 的 mysqli 扩展
public function getRedialLeads3(): array
{
// Obviously omitted sensitive details
$mysqli = mysqli_connect('hostname', 'username', 'password', 'database');
$query = "
select
`acd_3cx_leads`.`id`,
`acd_3cx_leads`.`dialCount`,
@hoursSinceLastUpdate := timestampdiff(hour, `acd_3cx_leadstatus`.`createdOn`, NOW()) hoursSinceLastUpdate,
max(acd_3cx_leadstatus.createdOn) leadCountLastUpdated,
max(acd_3cx_lead_schedule_call_back_sms_log.smsType) lastSmsType
from
`acd_3cx_leads`
left join `acd_3cx_leadstatus` on `acd_3cx_leadstatus`.`leadId` = `acd_3cx_leads`.`id`
and `acd_3cx_leadstatus`.`dialCount` = `acd_3cx_leads`.`dialCount`
left join `acd_3cx_lead_schedule_call_back_sms_log` on `acd_3cx_lead_schedule_call_back_sms_log`.`leadId` = `acd_3cx_leads`.`id`
where
`acd_3cx_leads`.`doNotContact` = 0
and `acd_3cx_leads`.`active` = 1
and `acd_3cx_leads`.`closeTypeId` is null
and `acd_3cx_leads`.`lastModified` >= '2022-01-01'
group by
`acd_3cx_leads`.`id`
having (acd_3cx_leads.dialCount = 1
and @hoursSinceLastUpdate >= 5
and lastSmsType is null)
OR(acd_3cx_leads.dialCount = 1
and @hoursSinceLastUpdate >= 24
and(lastSmsType is null
or lastSmsType < 2))
OR(acd_3cx_leads.dialCount = 1
and @hoursSinceLastUpdate >= 48
and(lastSmsType is null
or lastSmsType < 3))
OR(acd_3cx_leads.dialCount = 2
and @hoursSinceLastUpdate >= 4
and lastSmsType is null)
OR(acd_3cx_leads.dialCount = 2
and @hoursSinceLastUpdate >= 24
and(lastSmsType is null
or lastSmsType < 2))
OR(acd_3cx_leads.dialCount = 2
and @hoursSinceLastUpdate >= 48
and(lastSmsType is null
or lastSmsType < 3))
OR(acd_3cx_leads.dialCount = 3
and @hoursSinceLastUpdate >= 6
and(lastSmsType is null
or lastSmsType < 4))
order by
`acd_3cx_leads`.`lastModified` desc
";
$result = mysqli_query($mysqli, $query, MYSQLI_USE_RESULT);
return mysqli_fetch_all($result, MYSQLI_ASSOC);
}
根据@mickmackusa 的建议,我从 SELECT
和 HAVING
子句中删除了 SQL 变量 hoursSinceLastUpdate
。这解决了我的问题。所以 SELECT
行
@hoursSinceLastUpdate := timestampdiff(hour, `acd_3cx_leadstatus`.`createdOn`, NOW()) hoursSinceLastUpdate,
现在
timestampdiff(hour, `acd_3cx_leadstatus`.`createdOn`, NOW()) hoursSinceLastUpdate,
在 HAVING
中,所有出现的
and @hoursSinceLastUpdate >= x
现在
and hoursSinceLastUpdate >= x
因此 SQL 和 PHP 的记录 return 之间现在存在奇偶校验。