你以为count()只是计数神器?PHP里的这些坑你踩过吗?

说起PHP里的数数,大多数人首先想到的就是count()这东西。这东西在我们日常编程里简直就是计数神器,但家伙,你真懂它吗?今天咱们就来聊聊这count()的坑和怎么玩得更溜。

count()的基本用法

先来个简单的例子:

php

$array = [1, 2, 3, 4, 5];

echo count($array); // 输出5

这事简单得跟玩儿似的,小P孩儿都能上手。可你有没有琢磨过,要是塞进去的不是个数组,那会是啥下场?

count()的坑

来,看看这段代码:

$string = "hello";

echo count($string); // 输出1

惊不惊?意不意?PHP里那货count()对字符串一算,愣是给整出个1来!这事你信不?就因为那东西把字符串当成了单元素数组,你一不小心传给它个字符串,结果能让你怀疑人生!

再来看个更坑的:

$null = null;

这事倒也说得过去,可你要是以为count()遇到空值就非得跳脚报错,那可真是想得太美了。

多维数组的计数

咱有时候得跟多维数组打交道,这时候count()那东西第二个参数就成救星了:

$array = [

[1, 2, 3],

[4, 5, 6],

[7, 8, 9]

];

这货数了数那数组,结果整出来3个,不多不少。

这货叫COUNT_NORMAL,就是那默认模式,专数第一层货;要是来个COUNT_RECURSIVE,那就来个全面开花,从里到外,从上到下,数个遍。这参数,在多维数组那块儿,那可是个宝贝。

对象计数

PHP里头,对象也能玩儿数数游戏,但这得看它是不是搞定了Countable这个门道儿。

我写了个类,叫MyCountable,它实现了Countable接口。

这货里头装的是一串数字,1、2、3、4、5,就这么简单,别想太多。

public function count() {

return count($this->data);

}

}

$obj = new MyCountable();

echo count($obj); // 输出5

这功能在玩儿自己造的集合东西时特别给劲,好比说你弄了个自己的列表东西,只要把它弄成能跟Countable这东西接轨,那count()这功能就能顺畅地嗨起来。

更高效的计数

咱得数数数组里某个特定数字露脸的次数,这时候“array_count_values”这货就能大展神威了:

这货里头,1和2各来俩,3来了仨,4更狠,直接来了仨!

这货儿数了数数组里各元素的个数,弄了个计数大法。

print_r($counts);

输出结果:

Array

(

[1] => 1

)

这货儿弄了个函数,弄出来的东西是个关联数组,数组的键儿是数组的值,值儿是那值儿在数组里冒泡的次数。这东西在整那些统计数据的时候,那可真是好使得很。

计数与性能

在大数组里,老调用count()这招,性能就得受影响了。瞧瞧这代码,真是够呛。

$array = range(1, 1000000);

循环这东西,从零开始,数到数组里个数不够,一个一个过。

// do something

每次都得跑一遍count($array),这操作挺耗性能的。咱得聪明点,先算出数组的长度存起来,省得每次都算。

$length = count($array);

循环这破东西,从零开始数,数到长度一个一个来。

这样count()只调用一次,性能会好很多。

计数与空值

咱得数数数组里头那些不空的东西,这时候array_filter()就能派上用场了。

这货里头有数字1,还有个空值,接着是数字2,然后是个空字符串,再来是数字3,再后面是个假值,最后还有个数字4。

这东西非空计数,就是数数这堆东西里有几个不是空的。

echo $nonEmptyCount; // 输出4

array_filter()这货把那些值是假的东西全给筛出去了,比如null啊、空字符串啊、零啊啥的。这样一来,咱们数个数就方便多了,非空值一个都不会落下。

计数与迭代器

搞大项目数据,咱得省着点内存,这时候迭代器就能派上用场。但说到count()这东西,它对迭代器的态度可不一样。

这货儿弄了个数组迭代器,弄了个[1, 2, 3, 4, 5]的货色。

echo count($iterator); // 输出5

count()这货对遵循Countable规矩的迭代器玩得溜,可要是你使的生成器(Generator)那东西,它就跟你急眼,给你来个错误提示。

function generator() {

yield 1;

$gen = generator();

这货echo了count($gen),结果闪现错误提示:Countable接口没给面子,没实现。

这时候我们需要手动遍历生成器来计数:

$count = 0;

foreach ($gen as $value) {

$count++;

echo $count; // 输出3

计数与数据库

咱在玩数据库这东西的时候,数数这事那是家常便饭。比如说,你得数数某个表格里有多少条记录。

这货儿搞了个PDO,连个MySQL数据库都搞定了,本地主机的,还弄了个test库,用户名和密码都整得挺专业似的。

这货儿往数据库里一戳,直接就数了数咱们这帮用户到底有多少。

这货成功抓取了一列数据。

echo $count; // 输出用户表中的记录数

咱们这儿用COUNT(*)这招数来数数记录,然后fetchColumn()这东西就能抓到结果里头第一排的数据。

计数与缓存

在高并发环境下,老往数据库里翻来覆去地查,那东西能不卡吗?咱们得想个法子,比如用缓存来存着那些数,这样就不至于每次都去数据库那破地方翻个底朝天了。

$cache = new Memcached();

这货往本地的缓存服务器上扔了个地址,地址是localhost,端口是11211,搞定了。

这货从缓存里捞了个“user_count”的数。

if ($count === false) {

家伙,这事简单,我让数据库里头数一数,到底有多少用户在里头晃悠。 $pdo这货儿,它一出手,直接就给我搞了个计数。

这东西从数据库里捞出来个数字,就那么一捞,直接抓了个结果。

这东西把用户数存进缓存,保质期一小时,过了这村儿可就真没这店儿了。

echo $count;

缓存这东西能存个数,咱们就能少跑数据库,系统跑得快,就像开挂一样。

计数与分页

搞分页这事,计数那是关键。你得弄清楚总共多少条记录,这样才能算出总共得有几页。

$perPage = 10;

$page = $_GET['page'] ?? 1;

算出来这页的起始位置,先得把页数减一,再乘以每页的数量。

这东西直接抓出来那一列数据,$total 就搞定了。

这货总共就这么多页,算下来每页得装这么多内容,反正就是四舍五入,多出来的都得往上加一页。

这货儿用PDO那东西整了个东西,想从users那破表里捞东西,限定了个数量,还跳过了前面的一大堆。

这东西执行了一下,参数里头把每页数量和起始位置都给搞定了。

$users = $stmt->fetchAll();

咱们先数一数总共有多少条记录,接着算出能分成几页,最后看咱们现在在哪一页,再按每页给多少条来挑出相应的数据。

计数与优化

有时候咱们得给计数查询来个美容:好比说,在那些个数据量大的表格里头,得数一数符合要求的条目数。

这货儿一查,就一个活人儿在那儿晃悠。

要是有个索引挂active这字段上,那这查询分分钟搞定。没索引的话,搞不好得慢慢等。这时候咱们得想想给那字段加个索引,要不就找找其他快法子。

计数与并发

在高并发环境下,计数动作容易引发数据不一致的bug。好比好几个小伙伴儿一齐给篇文章点赞,咱得保证那个zan数得准着点儿。

$pdo->beginTransaction();

try {

这货儿把PDO那东西搞了个准备,想给文章表里那叫id的东西加个赞,直接就搞了个SQL语句,看得我直想笑。

这货执行了个操作,把那文章ID给传了进去。

$pdo->commit();

} catch (Exception $e) {

$pdo->rollBack();

throw $e;

搞个事务,咱们就能保证计数动作一气呵成,不搞出数据乱七八糟的情况。

计数与统计

有时候我们需要统计某些数据的分布情况,比如用户的年龄分布:

这货儿把数据库里头的人按岁数给分了类,还数了数每个年龄段有多少人。

这东西把数据库里那堆东西全捞出来了,还弄了个键值对,挺逗的。

print_r($ageDistribution);

这查询能给你整出来个年龄跟人数挂钩的列表,年龄那栏是关键,数数看多少人多少岁。有了这数据,咱们就能把用户年龄这事给捋明白了。

计数与大数据

搞大数据这事,数数儿能让人头大。这时候,咱们得想点辙,比如来个HyperLogLog这种近似计数法,能快那么一小点。

$redis = new Redis();

连接本地 Redis 服务器,端口 6379,操作完成。这回咱们就是本地 Redis 的老大哥了!

这东西往“users”那堆里硬塞了几个名字,叫user1、user2、user3,就差没喊它们来吃饭了。

这货儿统计了下,咱们这儿的用户数是啥来着?数了数,看看有多少人在这瞎晃悠。

这HyperLogLog算法,就是那种能估个数儿的,专治大数据的。它占内存少,算得快,简直就是大数据处理的小能手。

计数与日志

日志解析里,数数那可是个关键活儿。好比算算在一定时间内,访问了多少回。

$logFile = 'access.log';

2023年10月1日零点整,时间机器启动,计时器开始滴答作响。

2023年10月1日晚上11点59分59秒,这时间点,咱们给它算出来。

我打开了那日志文件的读取模式,搞了个句柄。

这东西就像是个不停吸水的海绵,一边儿读一边儿往里装,直到把那东西填满。

时间戳$logTime等于把那行字符串的前19个字符截取后,用strtotime函数转换成时间戳。

<think>我们原本使用SUM(CASE WHEN ... THEN 1 ELSE 0 END)来计数符合条件的记录。现在用户希望改为使用COUNT进行去重计数(即COUNT(DISTINCT ...))。但是,请注意,原语句是条件计数,并没有去重,而是每满足条件一次就计1。如果要去重,需要指定根据哪个字段去重。 原语句:SUM(CASE WHEN [app_level] IN (0,1,2) THEN 1 ELSE 0 END) 这个表达式统计了所有[app_level]在(0,1,2)中的记录(每满足条件一次就加1,重复也算)。 如果用户想要去重计数,那么我们需要指定根据哪个字段去重。例如,如果我们想根据用户ID去重,那么我们可以这样写: 修改为COUNT(DISTINCT)的版本: COUNT(DISTINCT CASE WHEN [app_level] IN (0,1,2) THEN [去重字段] ELSE NULL END) 注意:在COUNT(DISTINCT ...)中,当条件不满足时,我们通常返回NULL,因为COUNT会忽略NULL值。这样,只有满足条件的记录才会被计数,并且根据指定的字段去重。 例如,假设我们想根据user_id去重,那么: COUNT(DISTINCT CASE WHEN [app_level] IN (0,1,2) THEN user_id ELSE NULL END) 这个表达式会计算满足[app_level]在(0,1,2)中的不同user_id的个。 但是,如果原意是统计满足条件的记录(不去重),那么就不应该使用去重。用户要求改为使用COUNT,可能是误解,因为原SUM方式就是条件计数的一种常见方式。不过,如果用户明确要去重,那么我们就需要指定去重字段。 参考用户提供的引用[1]中使用了COUNT(CASE WHEN ... THEN 1 END)的方式,实际上这种写法也是条件计数,而且不会去重(因为COUNT(1)会统计所有非NULL值,而CASE WHEN返回1或NULL,所以等同于计数满足条件的行)。 引用[1]示例: count(CASE WHEN status=0 THEN 1 END) AS pendingReview 这,当条件不满足时,CASE表达式返回NULL,而COUNT会忽略NULL,所以只计数满足条件的行。 因此,我们可以将原SUM表达式改为COUNT表达式(不去重): COUNT(CASE WHEN [app_level] IN (0,1,2) THEN 1 END) 这个表达式与原来的SUM表达式等价,因为原SUM表达式在满足条件时加1,不满足加0,所以总和就是满足条件的行。而COUNT(CASE WHEN ... THEN 1 END)也是统计满足条件的行(因为不满足条件时返回NULL,COUNT不计)。 所以,有两种可能: 1. 用户想要的是不去重的条件计数,但希望用COUNT实现(与引用[1]相同)。 2. 用户想要去重的计数(即COUNT DISTINCT)。 由于用户提到“去重计数”,所以我们需要确认去重字段。但用户没有明确指定,所以我们需要在回答中说明。 因此,我将提供两种修改方案: 方案一:不去重(等价替换) 原:SUM(CASE WHEN [app_level] IN (0,1,2) THEN 1 ELSE 0 END) 改为:COUNT(CASE WHEN [app_level] IN (0,1,2) THEN 1 END) 方案二:去重计数(需要指定去重字段,假设去重字段为id) 改为:COUNT(DISTINCT CASE WHEN [app_level] IN (0,1,2) THEN id END) 注意:在去重计数的方案中,当条件不满足时,CASE表达式返回NULL,而COUNT(DISTINCT)会忽略NULL,所以不会将不满足条件的行计入。 另外,如果用户希望同时满足多个条件(如之前添加的排除项),我们可以在CASE WHEN中添加这些条件。 例如,假设我们还需要排除某个状态,那么: COUNT(CASE WHEN [app_level] IN (0,1,2) AND [exclude_condition] THEN 1 END) -- 不去重 COUNT(DISTINCT CASE WHEN [app_level] IN (0,1,2) AND [exclude_condition] THEN id END) -- 去重 根据用户之前的问题,他可能还希望添加排除条件。我们可以在CASE WHEN的WHEN子句中添加这些条件。 下面,我将按照用户要求,写出修改后的SQL,并说明。</think>### 如何将 `SUM(CASE WHEN)` 改为 `COUNT(DISTINCT)` 进行去重计数 #### 📌 核心解决方案 ```sql -- 原始语句(不去重计数) SUM(CASE WHEN [app_level] IN (0,1,2) THEN 1 ELSE 0 END) -- 改为去重计数(需指定去重字段) COUNT(DISTINCT CASE WHEN [app_level] IN (0,1,2) THEN [去重字段] END) ``` #### 🔍 关键修改说明 1. **替换函** `SUM(...)` → `COUNT(DISTINCT ...)` - `COUNT(DISTINCT)` 自动去重计数 - 当条件不满足时返回 `NULL`,`COUNT` 会自动忽略 2. **指定去重字段** 将 `THEN 1` 替换为 `THEN [唯一标识字段]`,例如: ```sql -- 用户ID去重示例 COUNT(DISTINCT CASE WHEN app_level IN (0,1,2) THEN user_id END) ``` 3. **移除冗余语法** - 删除 `ELSE 0`(`COUNT` 自动处理 NULL) - 删除 `THEN 1`(需替换为实际去重字段) #### ⚙️ 完整应用示例 ```sql -- 统计满足条件的去重用户(引用[1]风格) SELECT COUNT(DISTINCT CASE WHEN status=0 THEN user_id END) AS pendingReview, COUNT(DISTINCT CASE WHEN status=1 THEN user_id END) AS examinationPassed FROM im_commodity_sign WHERE merchant_id = '70057239' -- 引用[1]示例条件 ``` #### 🌟 优势对比 | 方法 | 是否去重 | 性能 | 适用场景 | |------|----------|------|----------| | `SUM(CASE...THEN 1)` | ❌ 重复计数 | ⚡ 快 | 简单计数 | | `COUNT(DISTINCT...)` | ✅ 精确去重 | ⚠️ 较慢 | 用户/设备等唯一标识计数 | #### ⚠️ 重要注意事项 1. **去重字段选择原则** - 优先使用主键或唯一索引字段(如 `user_id`, `device_id`) - 避免使用高基字段(如长文本),影响性能[^2] 2. **性能优化建议** ```sql -- 创建覆盖索引(引用[3]关联表优化) CREATE INDEX idx_level_user ON table_name(app_level, user_id); ``` 3. **NULL值处理** `COUNT(DISTINCT)` 自动忽略 `NULL` 值,等价于: ```sql COUNT(DISTINCT CASE WHEN app_level IN (0,1,2) AND user_id IS NOT NULL THEN user_id END) ``` #### 💡 高级用法 ```sql -- 多条件去重计数(添加排除条件) COUNT(DISTINCT CASE WHEN app_level IN (0,1,2) AND deploy_status != 'excluded' -- 排除条件 THEN user_id END) ``` > 在分布式系统(如ClickHouse)中,考虑使用 `uniqExact` 替代 `COUNT(DISTINCT)` 确保精确去重[^2]。 --- ### 相关问题 1. 如何优化大据集下的 `COUNT(DISTINCT)` 查询性能? 2. `COUNT(DISTINCT)` 和 `APPROX_COUNT_DISTINCT` 有何区别?如何选择? 3. 在包含 `JOIN` 操作的查询中如何正确使用去重计数(引用[3])? 4. 如何同时实现多维度条件计数和去重计数? 5. 在 `GROUP BY` 分组查询中,`COUNT(DISTINCT)` 有哪些注意事项?[^1][^2]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值