Skip to content

4.2 查询文档 (Read)

概述

查询文档是MongoDB中最常用的操作之一,用于从集合中检索符合条件的数据。MongoDB提供了强大的查询功能,支持简单的条件查询、复杂的聚合查询、全文搜索等多种查询方式。本章节将详细介绍MongoDB文档查询的各种方法、查询操作符和性能优化技巧。

MongoDB的查询系统具有灵活性、高性能和丰富功能的特点。掌握查询操作对于构建高效的数据检索系统至关重要。

基本概念

查询方法

MongoDB提供以下查询方法:

  1. findOne():查询单个文档
  2. find():查询多个文档
  3. aggregate():聚合查询
  4. distinct():获取唯一值
  5. countDocuments():统计文档数量

查询语法

MongoDB查询使用以下语法:

  • 简单查询{field: value}
  • 比较查询{field: {$operator: value}}
  • 逻辑查询{$operator: [{condition1}, {condition2}]}
  • 数组查询{field: {$operator: value}}
  • 嵌套查询{'field.nested': value}

查询选项

查询支持以下选项:

  • projection:指定返回字段
  • sort:排序结果
  • limit:限制返回数量
  • skip:跳过指定数量
  • hint:使用指定索引

原理深度解析

单文档查询原理

使用findOne()方法查询单个文档:

php
<?php
// 单文档查询详解
class SingleDocumentQuery {
    private $database;
    
    public function __construct($databaseName) {
        $client = new MongoDB\Client("mongodb://localhost:27017");
        $this->database = $client->selectDatabase($databaseName);
    }
    
    public function findById($collectionName, $id) {
        $collection = $this->database->selectCollection($collectionName);
        
        try {
            $document = $collection->findOne(['_id' => new MongoDB\BSON\ObjectId($id)]);
            
            if ($document) {
                return [
                    'found' => true,
                    'document' => $document
                ];
            } else {
                return [
                    'found' => false,
                    'message' => 'Document not found'
                ];
            }
            
        } catch (Exception $e) {
            return [
                'error' => 'Query failed',
                'message' => $e->getMessage()
            ];
        }
    }
    
    public function findByField($collectionName, $field, $value) {
        $collection = $this->database->selectCollection($collectionName);
        
        try {
            $document = $collection->findOne([$field => $value]);
            
            if ($document) {
                return [
                    'found' => true,
                    'document' => $document
                ];
            } else {
                return [
                    'found' => false,
                    'message' => 'Document not found'
                ];
            }
            
        } catch (Exception $e) {
            return [
                'error' => 'Query failed',
                'message' => $e->getMessage()
            ];
        }
    }
    
    public function findByMultipleFields($collectionName, $conditions) {
        $collection = $this->database->selectCollection($collectionName);
        
        try {
            $document = $collection->findOne($conditions);
            
            if ($document) {
                return [
                    'found' => true,
                    'document' => $document
                ];
            } else {
                return [
                    'found' => false,
                    'message' => 'Document not found'
                ];
            }
            
        } catch (Exception $e) {
            return [
                'error' => 'Query failed',
                'message' => $e->getMessage()
            ];
        }
    }
    
    public function findWithProjection($collectionName, $filter, $projection) {
        $collection = $this->database->selectCollection($collectionName);
        
        try {
            $document = $collection->findOne($filter, ['projection' => $projection]);
            
            if ($document) {
                return [
                    'found' => true,
                    'document' => $document,
                    'projection_applied' => true
                ];
            } else {
                return [
                    'found' => false,
                    'message' => 'Document not found'
                ];
            }
            
        } catch (Exception $e) {
            return [
                'error' => 'Query failed',
                'message' => $e->getMessage()
            ];
        }
    }
    
    public function findWithSort($collectionName, $filter, $sort) {
        $collection = $this->database->selectCollection($collectionName);
        
        try {
            $document = $collection->findOne($filter, ['sort' => $sort]);
            
            if ($document) {
                return [
                    'found' => true,
                    'document' => $document,
                    'sort_applied' => $sort
                ];
            } else {
                return [
                    'found' => false,
                    'message' => 'Document not found'
                ];
            }
            
        } catch (Exception $e) {
            return [
                'error' => 'Query failed',
                'message' => $e->getMessage()
            ];
        }
    }
}

// 使用示例
$singleQuery = new SingleDocumentQuery('testdb');

// 按ID查询
$idResult = $singleQuery->findById('users', '507f1f77bcf86cd799439011');
print_r($idResult);

// 按字段查询
$fieldResult = $singleQuery->findByField('users', 'email', 'john@example.com');
print_r($fieldResult);

// 按多个字段查询
$conditions = [
    'name' => 'John Doe',
    'active' => true
];

$multipleResult = $singleQuery->findByMultipleFields('users', $conditions);
print_r($multipleResult);

// 带投影的查询
$projection = [
    'name' => 1,
    'email' => 1,
    'age' => 1
];

$projectionResult = $singleQuery->findWithProjection('users', ['active' => true], $projection);
print_r($projectionResult);

// 带排序的查询
$sort = ['created_at' => -1];
$sortResult = $singleQuery->findWithSort('users', ['active' => true], $sort);
print_r($sortResult);
?>

多文档查询原理

使用find()方法查询多个文档:

php
<?php
// 多文档查询详解
class MultipleDocumentQuery {
    private $database;
    
    public function __construct($databaseName) {
        $client = new MongoDB\Client("mongodb://localhost:27017");
        $this->database = $client->selectDatabase($databaseName);
    }
    
    public function findAll($collectionName, $filter = []) {
        $collection = $this->database->selectCollection($collectionName);
        
        try {
            $cursor = $collection->find($filter);
            $documents = iterator_to_array($cursor);
            
            return [
                'success' => true,
                'count' => count($documents),
                'documents' => $documents
            ];
            
        } catch (Exception $e) {
            return [
                'error' => 'Query failed',
                'message' => $e->getMessage()
            ];
        }
    }
    
    public function findWithLimit($collectionName, $filter, $limit) {
        $collection = $this->database->selectCollection($collectionName);
        
        try {
            $cursor = $collection->find($filter, ['limit' => $limit]);
            $documents = iterator_to_array($cursor);
            
            return [
                'success' => true,
                'limit' => $limit,
                'returned_count' => count($documents),
                'documents' => $documents
            ];
            
        } catch (Exception $e) {
            return [
                'error' => 'Query failed',
                'message' => $e->getMessage()
            ];
        }
    }
    
    public function findWithPagination($collectionName, $filter, $page, $pageSize) {
        $collection = $this->database->selectCollection($collectionName);
        
        try {
            $skip = ($page - 1) * $pageSize;
            
            $cursor = $collection->find($filter, [
                'skip' => $skip,
                'limit' => $pageSize,
                'sort' => ['_id' => 1]
            ]);
            
            $documents = iterator_to_array($cursor);
            
            // 获取总数
            $totalCount = $collection->countDocuments($filter);
            $totalPages = ceil($totalCount / $pageSize);
            
            return [
                'success' => true,
                'page' => $page,
                'page_size' => $pageSize,
                'total_count' => $totalCount,
                'total_pages' => $totalPages,
                'returned_count' => count($documents),
                'documents' => $documents
            ];
            
        } catch (Exception $e) {
            return [
                'error' => 'Query failed',
                'message' => $e->getMessage()
            ];
        }
    }
    
    public function findWithSort($collectionName, $filter, $sort, $options = []) {
        $collection = $this->database->selectCollection($collectionName);
        
        try {
            $defaultOptions = ['sort' => $sort];
            $queryOptions = array_merge($defaultOptions, $options);
            
            $cursor = $collection->find($filter, $queryOptions);
            $documents = iterator_to_array($cursor);
            
            return [
                'success' => true,
                'sort' => $sort,
                'count' => count($documents),
                'documents' => $documents
            ];
            
        } catch (Exception $e) {
            return [
                'error' => 'Query failed',
                'message' => $e->getMessage()
            ];
        }
    }
    
    public function findWithProjection($collectionName, $filter, $projection, $options = []) {
        $collection = $this->database->selectCollection($collectionName);
        
        try {
            $defaultOptions = ['projection' => $projection];
            $queryOptions = array_merge($defaultOptions, $options);
            
            $cursor = $collection->find($filter, $queryOptions);
            $documents = iterator_to_array($cursor);
            
            return [
                'success' => true,
                'projection' => $projection,
                'count' => count($documents),
                'documents' => $documents
            ];
            
        } catch (Exception $e) {
            return [
                'error' => 'Query failed',
                'message' => $e->getMessage()
            ];
        }
    }
    
    public function findWithComplexOptions($collectionName, $filter, $options) {
        $collection = $this->database->selectCollection($collectionName);
        
        try {
            $cursor = $collection->find($filter, $options);
            $documents = iterator_to_array($cursor);
            
            return [
                'success' => true,
                'options' => $options,
                'count' => count($documents),
                'documents' => $documents
            ];
            
        } catch (Exception $e) {
            return [
                'error' => 'Query failed',
                'message' => $e->getMessage()
            ];
        }
    }
}

// 使用示例
$multipleQuery = new MultipleDocumentQuery('testdb');

// 查询所有文档
$allResult = $multipleQuery->findAll('users', ['active' => true]);
print_r($allResult);

// 带限制的查询
$limitResult = $multipleQuery->findWithLimit('users', ['active' => true], 10);
print_r($limitResult);

// 分页查询
$paginationResult = $multipleQuery->findWithPagination('users', ['active' => true], 2, 10);
print_r($paginationResult);

// 带排序的查询
$sort = ['created_at' => -1];
$sortResult = $multipleQuery->findWithSort('users', ['active' => true], $sort);
print_r($sortResult);

// 带投影的查询
$projection = [
    'name' => 1,
    'email' => 1,
    'age' => 1
];

$projectionResult = $multipleQuery->findWithProjection('users', ['active' => true], $projection);
print_r($projectionResult);

// 复杂选项查询
$complexOptions = [
    'projection' => ['name' => 1, 'email' => 1],
    'sort' => ['created_at' => -1],
    'limit' => 5,
    'skip' => 10
];

$complexResult = $multipleQuery->findWithComplexOptions('users', ['active' => true], $complexOptions);
print_r($complexResult);
?>

比较查询操作符

使用比较操作符进行条件查询:

php
<?php
// 比较查询操作符详解
class ComparisonQueryOperators {
    private $database;
    
    public function __construct($databaseName) {
        $client = new MongoDB\Client("mongodb://localhost:27017");
        $this->database = $client->selectDatabase($databaseName);
    }
    
    public function queryWithGreaterThan($collectionName, $field, $value) {
        $collection = $this->database->selectCollection($collectionName);
        
        try {
            $cursor = $collection->find([$field => ['$gt' => $value]]);
            $documents = iterator_to_array($cursor);
            
            return [
                'operator' => '$gt',
                'field' => $field,
                'value' => $value,
                'count' => count($documents),
                'documents' => $documents
            ];
            
        } catch (Exception $e) {
            return [
                'error' => 'Query failed',
                'message' => $e->getMessage()
            ];
        }
    }
    
    public function queryWithLessThan($collectionName, $field, $value) {
        $collection = $this->database->selectCollection($collectionName);
        
        try {
            $cursor = $collection->find([$field => ['$lt' => $value]]);
            $documents = iterator_to_array($cursor);
            
            return [
                'operator' => '$lt',
                'field' => $field,
                'value' => $value,
                'count' => count($documents),
                'documents' => $documents
            ];
            
        } catch (Exception $e) {
            return [
                'error' => 'Query failed',
                'message' => $e->getMessage()
            ];
        }
    }
    
    public function queryWithRange($collectionName, $field, $minValue, $maxValue) {
        $collection = $this->database->selectCollection($collectionName);
        
        try {
            $cursor = $collection->find([
                $field => [
                    '$gte' => $minValue,
                    '$lte' => $maxValue
                ]
            ]);
            
            $documents = iterator_to_array($cursor);
            
            return [
                'operator' => 'range',
                'field' => $field,
                'range' => [$minValue, $maxValue],
                'count' => count($documents),
                'documents' => $documents
            ];
            
        } catch (Exception $e) {
            return [
                'error' => 'Query failed',
                'message' => $e->getMessage()
            ];
        }
    }
    
    public function queryWithNotEqual($collectionName, $field, $value) {
        $collection = $this->database->selectCollection($collectionName);
        
        try {
            $cursor = $collection->find([$field => ['$ne' => $value]]);
            $documents = iterator_to_array($cursor);
            
            return [
                'operator' => '$ne',
                'field' => $field,
                'value' => $value,
                'count' => count($documents),
                'documents' => $documents
            ];
            
        } catch (Exception $e) {
            return [
                'error' => 'Query failed',
                'message' => $e->getMessage()
            ];
        }
    }
    
    public function queryWithIn($collectionName, $field, $values) {
        $collection = $this->database->selectCollection($collectionName);
        
        try {
            $cursor = $collection->find([$field => ['$in' => $values]]);
            $documents = iterator_to_array($cursor);
            
            return [
                'operator' => '$in',
                'field' => $field,
                'values' => $values,
                'count' => count($documents),
                'documents' => $documents
            ];
            
        } catch (Exception $e) {
            return [
                'error' => 'Query failed',
                'message' => $e->getMessage()
            ];
        }
    }
    
    public function queryWithNotIn($collectionName, $field, $values) {
        $collection = $this->database->selectCollection($collectionName);
        
        try {
            $cursor = $collection->find([$field => ['$nin' => $values]]);
            $documents = iterator_to_array($cursor);
            
            return [
                'operator' => '$nin',
                'field' => $field,
                'values' => $values,
                'count' => count($documents),
                'documents' => $documents
            ];
            
        } catch (Exception $e) {
            return [
                'error' => 'Query failed',
                'message' => $e->getMessage()
            ];
        }
    }
    
    public function demonstrateAllComparisonOperators($collectionName, $field, $testValue) {
        $collection = $this->database->selectCollection($collectionName);
        
        $operators = [
            '$eq' => [$field => $testValue],
            '$gt' => [$field => ['$gt' => $testValue]],
            '$gte' => [$field => ['$gte' => $testValue]],
            '$lt' => [$field => ['$lt' => $testValue]],
            '$lte' => [$field => ['$lte' => $testValue]],
            '$ne' => [$field => ['$ne' => $testValue]],
            '$in' => [$field => ['$in' => [$testValue, $testValue + 1, $testValue + 2]]],
            '$nin' => [$field => ['$nin' => [$testValue, $testValue + 1, $testValue + 2]]]
        ];
        
        $results = [];
        
        foreach ($operators as $operator => $filter) {
            try {
                $cursor = $collection->find($filter);
                $documents = iterator_to_array($cursor);
                
                $results[$operator] = [
                    'count' => count($documents),
                    'sample_count' => min(3, count($documents)),
                    'samples' => array_slice($documents, 0, 3)
                ];
                
            } catch (Exception $e) {
                $results[$operator] = [
                    'error' => $e->getMessage()
                ];
            }
        }
        
        return [
            'field' => $field,
            'test_value' => $testValue,
            'operator_results' => $results
        ];
    }
}

// 使用示例
$comparisonQuery = new ComparisonQueryOperators('testdb');

// 大于查询
$gtResult = $comparisonQuery->queryWithGreaterThan('users', 'age', 30);
print_r($gtResult);

// 小于查询
$ltResult = $comparisonQuery->queryWithLessThan('users', 'age', 30);
print_r($ltResult);

// 范围查询
$rangeResult = $comparisonQuery->queryWithRange('users', 'age', 25, 35);
print_r($rangeResult);

// 不等于查询
$neResult = $comparisonQuery->queryWithNotEqual('users', 'status', 'inactive');
print_r($neResult);

// IN查询
$inResult = $comparisonQuery->queryWithIn('users', 'role', ['admin', 'moderator']);
print_r($inResult);

// NOT IN查询
$ninResult = $comparisonQuery->queryWithNotIn('users', 'status', ['deleted', 'banned']);
print_r($ninResult);

// 演示所有比较操作符
$allOperators = $comparisonQuery->demonstrateAllComparisonOperators('users', 'age', 30);
print_r($allOperators);
?>

逻辑查询操作符

使用逻辑操作符组合多个条件:

php
<?php
// 逻辑查询操作符详解
class LogicalQueryOperators {
    private $database;
    
    public function __construct($databaseName) {
        $client = new MongoDB\Client("mongodb://localhost:27017");
        $this->database = $client->selectDatabase($databaseName);
    }
    
    public function queryWithAnd($collectionName, $conditions) {
        $collection = $this->database->selectCollection($collectionName);
        
        try {
            $cursor = $collection->find(['$and' => $conditions]);
            $documents = iterator_to_array($cursor);
            
            return [
                'operator' => '$and',
                'conditions' => $conditions,
                'count' => count($documents),
                'documents' => $documents
            ];
            
        } catch (Exception $e) {
            return [
                'error' => 'Query failed',
                'message' => $e->getMessage()
            ];
        }
    }
    
    public function queryWithOr($collectionName, $conditions) {
        $collection = $this->database->selectCollection($collectionName);
        
        try {
            $cursor = $collection->find(['$or' => $conditions]);
            $documents = iterator_to_array($cursor);
            
            return [
                'operator' => '$or',
                'conditions' => $conditions,
                'count' => count($documents),
                'documents' => $documents
            ];
            
        } catch (Exception $e) {
            return [
                'error' => 'Query failed',
                'message' => $e->getMessage()
            ];
        }
    }
    
    public function queryWithNot($collectionName, $condition) {
        $collection = $this->database->selectCollection($collectionName);
        
        try {
            $cursor = $collection->find(['$not' => $condition]);
            $documents = iterator_to_array($cursor);
            
            return [
                'operator' => '$not',
                'condition' => $condition,
                'count' => count($documents),
                'documents' => $documents
            ];
            
        } catch (Exception $e) {
            return [
                'error' => 'Query failed',
                'message' => $e->getMessage()
            ];
        }
    }
    
    public function queryWithNor($collectionName, $conditions) {
        $collection = $this->database->selectCollection($collectionName);
        
        try {
            $cursor = $collection->find(['$nor' => $conditions]);
            $documents = iterator_to_array($cursor);
            
            return [
                'operator' => '$nor',
                'conditions' => $conditions,
                'count' => count($documents),
                'documents' => $documents
            ];
            
        } catch (Exception $e) {
            return [
                'error' => 'Query failed',
                'message' => $e->getMessage()
            ];
        }
    }
    
    public function queryWithComplexLogic($collectionName, $logicalStructure) {
        $collection = $this->database->selectCollection($collectionName);
        
        try {
            $cursor = $collection->find($logicalStructure);
            $documents = iterator_to_array($cursor);
            
            return [
                'logical_structure' => $logicalStructure,
                'count' => count($documents),
                'documents' => $documents
            ];
            
        } catch (Exception $e) {
            return [
                'error' => 'Query failed',
                'message' => $e->getMessage()
            ];
        }
    }
    
    public function demonstrateLogicalCombinations($collectionName) {
        $collection = $this->database->selectCollection($collectionName);
        
        $combinations = [
            'simple_and' => [
                '$and' => [
                    ['age' => ['$gte' => 25]],
                    ['age' => ['$lte' => 35]],
                    ['active' => true]
                ]
            ],
            'simple_or' => [
                '$or' => [
                    ['role' => 'admin'],
                    ['role' => 'moderator']
                ]
            ],
            'nested_logic' => [
                '$and' => [
                    [
                        '$or' => [
                            ['age' => ['$lt' => 25]],
                            ['age' => ['$gt' => 65]]
                        ]
                    ],
                    ['active' => true]
                ]
            ],
            'complex_combination' => [
                '$and' => [
                    ['active' => true],
                    [
                        '$or' => [
                            ['role' => 'admin'],
                            [
                                '$and' => [
                                    ['role' => 'user'],
                                    ['age' => ['$gte' => 18]]
                                ]
                            ]
                        ]
                    ]
                ]
            ]
        ];
        
        $results = [];
        
        foreach ($combinations as $name => $filter) {
            try {
                $cursor = $collection->find($filter);
                $documents = iterator_to_array($cursor);
                
                $results[$name] = [
                    'filter' => $filter,
                    'count' => count($documents),
                    'sample_count' => min(3, count($documents)),
                    'samples' => array_slice($documents, 0, 3)
                ];
                
            } catch (Exception $e) {
                $results[$name] = [
                    'error' => $e->getMessage()
                ];
            }
        }
        
        return $results;
    }
}

// 使用示例
$logicalQuery = new LogicalQueryOperators('testdb');

// AND查询
$andConditions = [
    ['age' => ['$gte' => 25]],
    ['age' => ['$lte' => 35]],
    ['active' => true]
];

$andResult = $logicalQuery->queryWithAnd('users', $andConditions);
print_r($andResult);

// OR查询
$orConditions = [
    ['role' => 'admin'],
    ['role' => 'moderator']
];

$orResult = $logicalQuery->queryWithOr('users', $orConditions);
print_r($orResult);

// NOT查询
$notCondition = ['age' => ['$lt' => 18]];
$notResult = $logicalQuery->queryWithNot('users', $notCondition);
print_r($notResult);

// NOR查询
$norConditions = [
    ['status' => 'deleted'],
    ['status' => 'banned']
];

$norResult = $logicalQuery->queryWithNor('users', $norConditions);
print_r($norResult);

// 复杂逻辑查询
$complexLogic = [
    '$and' => [
        ['active' => true],
        [
            '$or' => [
                ['role' => 'admin'],
                [
                    '$and' => [
                        ['role' => 'user'],
                        ['age' => ['$gte' => 18]]
                    ]
                ]
            ]
        ]
    ]
];

$complexResult = $logicalQuery->queryWithComplexLogic('users', $complexLogic);
print_r($complexResult);

// 演示逻辑组合
$combinations = $logicalQuery->demonstrateLogicalCombinations('users');
print_r($combinations);
?>

常见错误与踩坑点

错误1:查询字段不存在

问题描述:查询不存在的字段导致返回空结果。

php
<?php
// 错误示例 - 查询不存在的字段
try {
    $client = new MongoDB\Client("mongodb://localhost:27017");
    $database = $client->selectDatabase("testdb");
    $collection = $database->selectCollection('users');
    
    // 错误:查询不存在的字段
    $result = $collection->find(['non_existent_field' => 'value'])->toArray();
    
    echo "Found " . count($result) . " documents\n";
    
} catch (Exception $e) {
    echo "错误: " . $e->getMessage() . "\n";
}

// 正确示例 - 检查字段是否存在
$client = new MongoDB\Client("mongodb://localhost:27017");
$database = $client->selectDatabase("testdb");
$collection = $database->selectCollection('users');

// 检查字段是否存在
$sampleDocument = $collection->findOne();
if ($sampleDocument && isset($sampleDocument['email'])) {
    $result = $collection->find(['email' => 'john@example.com'])->toArray();
    echo "Found " . count($result) . " documents\n";
} else {
    echo "Field 'email' does not exist in the collection\n";
}

// 或者使用$exists操作符
$result = $collection->find(['email' => ['$exists' => true]])->toArray();
echo "Found " . count($result) . " documents with email field\n";
?>

错误2:类型不匹配查询

问题描述:查询时数据类型不匹配导致无法找到文档。

php
<?php
// 错误示例 - 类型不匹配查询
try {
    $client = new MongoDB\Client("mongodb://localhost:27017");
    $database = $client->selectDatabase("testdb");
    $collection = $database->selectCollection('users');
    
    // 假设age字段存储为整数
    // 错误:使用字符串查询整数字段
    $result = $collection->find(['age' => '30'])->toArray();
    
    echo "Found " . count($result) . " documents\n";
    
} catch (Exception $e) {
    echo "错误: " . $e->getMessage() . "\n";
}

// 正确示例 - 使用正确的类型
$client = new MongoDB\Client("mongodb://localhost:27017");
$database = $client->selectDatabase("testdb");
$collection = $database->selectCollection('users');

// 使用整数查询
$result = $collection->find(['age' => 30])->toArray();
echo "Found " . count($result) . " documents\n";

// 或者使用类型转换
$result = $collection->find(['age' => ['$toString' => '30']])->toArray();
echo "Found " . count($result) . " documents with type conversion\n";
?>

错误3:投影字段错误

问题描述:投影设置错误导致返回意外的结果。

php
<?php
// 错误示例 - 投影字段错误
try {
    $client = new MongoDB\Client("mongodb://localhost:27017");
    $database = $client->selectDatabase("testdb");
    $collection = $database->selectCollection('users');
    
    // 错误:同时使用包含和排除投影
    $result = $collection->find(
        ['active' => true],
        ['projection' => ['name' => 1, 'email' => 0]]
    )->toArray();
    
    print_r($result);
    
} catch (Exception $e) {
    echo "错误: " . $e->getMessage() . "\n";
}

// 正确示例 - 正确的投影设置
$client = new MongoDB\Client("mongodb://localhost:27017");
$database = $client->selectDatabase("testdb");
$collection = $database->selectCollection('users');

// 只包含指定字段
$result1 = $collection->find(
    ['active' => true],
    ['projection' => ['name' => 1, 'email' => 1, 'age' => 1]]
)->toArray();

echo "Included fields: " . count($result1) . " documents\n";

// 排除指定字段
$result2 = $collection->find(
    ['active' => true],
    ['projection' => ['password' => 0, 'metadata' => 0]]
)->toArray();

echo "Excluded fields: " . count($result2) . " documents\n";

// 使用_id的特殊处理
$result3 = $collection->find(
    ['active' => true],
    ['projection' => ['_id' => 0, 'name' => 1, 'email' => 1]]
)->toArray();

echo "Excluded _id: " . count($result3) . " documents\n";
?>

常见应用场景

场景1:用户搜索系统

实现多条件用户搜索功能:

php
<?php
// 用户搜索系统
class UserSearchSystem {
    private $database;
    
    public function __construct($databaseName) {
        $client = new MongoDB\Client("mongodb://localhost:27017");
        $this->database = $client->selectDatabase($databaseName);
    }
    
    public function searchUsers($searchCriteria, $pagination = []) {
        $collection = $this->database->selectCollection('users');
        
        // 构建查询条件
        $filter = $this->buildSearchFilter($searchCriteria);
        
        // 设置投影
        $projection = $this->buildProjection($searchCriteria);
        
        // 设置排序
        $sort = $this->buildSort($searchCriteria);
        
        try {
            // 分页参数
            $page = $pagination['page'] ?? 1;
            $pageSize = $pagination['page_size'] ?? 20;
            $skip = ($page - 1) * $pageSize;
            
            $options = [
                'projection' => $projection,
                'sort' => $sort,
                'skip' => $skip,
                'limit' => $pageSize
            ];
            
            $cursor = $collection->find($filter, $options);
            $users = iterator_to_array($cursor);
            
            // 获取总数
            $totalCount = $collection->countDocuments($filter);
            $totalPages = ceil($totalCount / $pageSize);
            
            return [
                'success' => true,
                'users' => $users,
                'pagination' => [
                    'page' => $page,
                    'page_size' => $pageSize,
                    'total_count' => $totalCount,
                    'total_pages' => $totalPages,
                    'has_next' => $page < $totalPages,
                    'has_prev' => $page > 1
                ]
            ];
            
        } catch (Exception $e) {
            return [
                'success' => false,
                'error' => 'Search failed',
                'message' => $e->getMessage()
            ];
        }
    }
    
    private function buildSearchFilter($criteria) {
        $filter = [];
        
        // 激活状态
        if (isset($criteria['active'])) {
            $filter['active'] = $criteria['active'];
        }
        
        // 用户名搜索
        if (!empty($criteria['name'])) {
            $filter['name'] = new MongoDB\BSON\Regex($criteria['name'], 'i');
        }
        
        // 邮箱搜索
        if (!empty($criteria['email'])) {
            $filter['email'] = new MongoDB\BSON\Regex($criteria['email'], 'i');
        }
        
        // 年龄范围
        if (isset($criteria['age_min']) || isset($criteria['age_max'])) {
            $ageFilter = [];
            if (isset($criteria['age_min'])) {
                $ageFilter['$gte'] = $criteria['age_min'];
            }
            if (isset($criteria['age_max'])) {
                $ageFilter['$lte'] = $criteria['age_max'];
            }
            $filter['age'] = $ageFilter;
        }
        
        // 角色筛选
        if (!empty($criteria['roles'])) {
            $filter['role'] = ['$in' => $criteria['roles']];
        }
        
        // 注册日期范围
        if (isset($criteria['registered_from']) || isset($criteria['registered_to'])) {
            $dateFilter = [];
            if (isset($criteria['registered_from'])) {
                $dateFilter['$gte'] = new MongoDB\BSON\UTCDateTime($criteria['registered_from']);
            }
            if (isset($criteria['registered_to'])) {
                $dateFilter['$lte'] = new MongoDB\BSON\UTCDateTime($criteria['registered_to']);
            }
            $filter['created_at'] = $dateFilter;
        }
        
        return $filter;
    }
    
    private function buildProjection($criteria) {
        $projection = [
            'name' => 1,
            'email' => 1,
            'age' => 1,
            'role' => 1,
            'created_at' => 1
        ];
        
        // 根据权限添加更多字段
        if ($criteria['include_profile'] ?? false) {
            $projection['profile'] = 1;
        }
        
        if ($criteria['include_metadata'] ?? false) {
            $projection['metadata'] = 1;
        }
        
        return $projection;
    }
    
    private function buildSort($criteria) {
        $sort = [];
        
        // 默认按创建时间倒序
        $sortField = $criteria['sort_field'] ?? 'created_at';
        $sortOrder = $criteria['sort_order'] ?? -1;
        
        $sort[$sortField] = $sortOrder;
        
        return $sort;
    }
    
    public function searchUsersWithAggregation($searchCriteria, $pagination = []) {
        $collection = $this->database->selectCollection('users');
        
        // 构建聚合管道
        $pipeline = [];
        
        // 匹配阶段
        $matchStage = $this->buildSearchFilter($searchCriteria);
        if (!empty($matchStage)) {
            $pipeline[] = ['$match' => $matchStage];
        }
        
        // 排序阶段
        $sort = $this->buildSort($searchCriteria);
        $pipeline[] = ['$sort' => $sort];
        
        // 分页阶段
        $page = $pagination['page'] ?? 1;
        $pageSize = $pagination['page_size'] ?? 20;
        $skip = ($page - 1) * $pageSize;
        
        $pipeline[] = ['$skip' => $skip];
        $pipeline[] = ['$limit' => $pageSize];
        
        // 投影阶段
        $projection = $this->buildProjection($searchCriteria);
        $pipeline[] = ['$project' => $projection];
        
        try {
            $cursor = $collection->aggregate($pipeline);
            $users = iterator_to_array($cursor);
            
            return [
                'success' => true,
                'users' => $users,
                'count' => count($users)
            ];
            
        } catch (Exception $e) {
            return [
                'success' => false,
                'error' => 'Aggregation search failed',
                'message' => $e->getMessage()
            ];
        }
    }
}

// 使用示例
$userSearch = new UserSearchSystem('user_db');

// 基本搜索
$searchCriteria = [
    'name' => 'John',
    'age_min' => 25,
    'age_max' => 40,
    'roles' => ['user', 'admin']
];

$pagination = [
    'page' => 1,
    'page_size' => 10
];

$searchResult = $userSearch->searchUsers($searchCriteria, $pagination);
print_r($searchResult);

// 高级搜索
$advancedCriteria = [
    'name' => 'Jane',
    'email' => 'example.com',
    'active' => true,
    'registered_from' => strtotime('2024-01-01') * 1000,
    'registered_to' => time() * 1000,
    'include_profile' => true,
    'sort_field' => 'created_at',
    'sort_order' => -1
];

$advancedResult = $userSearch->searchUsers($advancedCriteria, $pagination);
print_r($advancedResult);

// 聚合搜索
$aggregationResult = $userSearch->searchUsersWithAggregation($searchCriteria, $pagination);
print_r($aggregationResult);
?>

常见问题答疑

问题1:如何优化查询性能?

回答:通过索引、投影、限制结果等方式优化查询性能:

php
<?php
// 查询性能优化助手
class QueryPerformanceOptimizer {
    private $database;
    
    public function __construct($databaseName) {
        $client = new MongoDB\Client("mongodb://localhost:27017");
        $this->database = $client->selectDatabase($databaseName);
    }
    
    public function analyzeQueryPerformance($collectionName, $filter, $options = []) {
        $collection = $this->database->selectCollection($collectionName);
        
        // 执行查询
        $startTime = microtime(true);
        $cursor = $collection->find($filter, $options);
        $documents = iterator_to_array($cursor);
        $endTime = microtime(true);
        
        $executionTime = $endTime - $startTime;
        
        // 分析结果
        $analysis = [
            'query_time_ms' => round($executionTime * 1000, 4),
            'document_count' => count($documents),
            'filter_complexity' => $this->analyzeFilterComplexity($filter),
            'options_used' => array_keys($options),
            'recommendations' => []
        ];
        
        // 生成优化建议
        if ($executionTime > 0.1) {
            $analysis['recommendations'][] = [
                'type' => 'performance',
                'message' => 'Query execution time is high',
                'suggestion' => 'Consider adding indexes for queried fields'
            ];
        }
        
        if (count($documents) > 1000 && !isset($options['limit'])) {
            $analysis['recommendations'][] = [
                'type' => 'pagination',
                'message' => 'Large result set returned',
                'suggestion' => 'Implement pagination with limit and skip'
            ];
        }
        
        if (!isset($options['projection'])) {
            $analysis['recommendations'][] = [
                'type' => 'network',
                'message' => 'Full documents returned',
                'suggestion' => 'Use projection to limit returned fields'
            ];
        }
        
        return $analysis;
    }
    
    private function analyzeFilterComplexity($filter) {
        $complexity = 0;
        
        if (isset($filter['$and'])) {
            $complexity += count($filter['$and']);
        }
        
        if (isset($filter['$or'])) {
            $complexity += count($filter['$or']) * 2;
        }
        
        foreach ($filter as $key => $value) {
            if (is_array($value) && isset($value['$in'])) {
                $complexity += count($value['$in']);
            }
        }
        
        return $complexity;
    }
}

// 使用示例
$optimizer = new QueryPerformanceOptimizer('testdb');

// 分析查询性能
$filter = [
    'active' => true,
    'age' => ['$gte' => 25, '$lte' => 35],
    'role' => ['$in' => ['user', 'admin']]
];

$options = [
    'sort' => ['created_at' => -1],
    'limit' => 100
];

$performanceAnalysis = $optimizer->analyzeQueryPerformance('users', $filter, $options);
print_r($performanceAnalysis);
?>

实战练习

练习1:实现分页查询系统

实现一个完整的分页查询系统:

php
<?php
// 分页查询系统
class PaginationQuerySystem {
    private $database;
    
    public function __construct($databaseName) {
        $client = new MongoDB\Client("mongodb://localhost:27017");
        $this->database = $client->selectDatabase($databaseName);
    }
    
    public function paginate($collectionName, $filter, $options = []) {
        $collection = $this->database->selectCollection($collectionName);
        
        $page = $options['page'] ?? 1;
        $pageSize = $options['page_size'] ?? 20;
        $sort = $options['sort'] ?? ['_id' => 1];
        $projection = $options['projection'] ?? [];
        
        try {
            // 获取总数
            $totalCount = $collection->countDocuments($filter);
            
            // 计算分页信息
            $totalPages = ceil($totalCount / $pageSize);
            $skip = ($page - 1) * $pageSize;
            
            // 构建查询选项
            $queryOptions = [
                'sort' => $sort,
                'skip' => $skip,
                'limit' => $pageSize
            ];
            
            if (!empty($projection)) {
                $queryOptions['projection'] = $projection;
            }
            
            // 执行查询
            $cursor = $collection->find($filter, $queryOptions);
            $documents = iterator_to_array($cursor);
            
            return [
                'success' => true,
                'data' => $documents,
                'pagination' => [
                    'page' => $page,
                    'page_size' => $pageSize,
                    'total_count' => $totalCount,
                    'total_pages' => $totalPages,
                    'has_next' => $page < $totalPages,
                    'has_prev' => $page > 1,
                    'next_page' => $page < $totalPages ? $page + 1 : null,
                    'prev_page' => $page > 1 ? $page - 1 : null
                ]
            ];
            
        } catch (Exception $e) {
            return [
                'success' => false,
                'error' => 'Pagination failed',
                'message' => $e->getMessage()
            ];
        }
    }
    
    public function getPaginationInfo($collectionName, $filter, $pageSize) {
        $collection = $this->database->selectCollection($collectionName);
        
        try {
            $totalCount = $collection->countDocuments($filter);
            $totalPages = ceil($totalCount / $pageSize);
            
            return [
                'total_count' => $totalCount,
                'page_size' => $pageSize,
                'total_pages' => $totalPages,
                'last_page' => $totalPages
            ];
            
        } catch (Exception $e) {
            return [
                'error' => 'Failed to get pagination info',
                'message' => $e->getMessage()
            ];
        }
    }
}

// 使用示例
$paginationSystem = new PaginationQuerySystem('testdb');

// 分页查询
$filter = ['active' => true];
$options = [
    'page' => 2,
    'page_size' => 10,
    'sort' => ['created_at' => -1],
    'projection' => ['name' => 1, 'email' => 1, 'age' => 1]
];

$paginationResult = $paginationSystem->paginate('users', $filter, $options);
print_r($paginationResult);

// 获取分页信息
$paginationInfo = $paginationSystem->getPaginationInfo('users', $filter, 10);
print_r($paginationInfo);
?>

知识点总结

核心概念

  1. 查询方法findOne()find()aggregate()
  2. 查询语法:简单查询、比较查询、逻辑查询
  3. 查询选项:投影、排序、限制、跳过
  4. 查询性能:索引优化、结果限制、字段投影

查询操作符

  1. 比较操作符$eq$gt$gte$lt$lte$ne$in$nin
  2. 逻辑操作符$and$or$not$nor
  3. 元素操作符$exists$type
  4. 数组操作符$all$elemMatch$size

最佳实践

  1. 索引优化:为常用查询字段创建索引
  2. 结果限制:使用limit()限制返回数量
  3. 字段投影:只返回需要的字段
  4. 分页查询:使用skip()limit()实现分页

常见场景

  1. 用户搜索:多条件组合查询
  2. 数据分页:skip和limit实现分页
  3. 范围查询:使用比较操作符
  4. 逻辑组合:使用逻辑操作符组合条件

拓展参考资料

官方文档

推荐阅读

  • 《MongoDB查询性能优化》
  • 《MongoDB索引设计与优化》
  • 《MongoDB聚合查询实战》

在线资源

  • MongoDB University查询课程
  • MongoDB官方博客查询优化文章
  • Stack Overflow MongoDB查询相关问题