{"record":{"id":"a5389f72c53d6808","repo":"yiisoft/yii2","slug":"primary-key-of-class-can-not-be-empty","errorCode":null,"errorMessage":"Primary key of '{$class}' can not be empty.","messagePattern":"Primary key of '(.+?)' can not be empty\\.","errorType":"exception","errorClass":"InvalidConfigException","httpStatus":null,"severity":"error","filePath":"framework/db/ActiveQuery.php","lineNumber":283,"sourceCode":"            // composite primary key\n            foreach ($models as $i => $model) {\n                $key = [];\n                foreach ($pks as $pk) {\n                    if (!isset($model[$pk])) {\n                        // do not continue if the primary key is not part of the result set\n                        break 2;\n                    }\n                    $key[] = $model[$pk];\n                }\n                $key = serialize($key);\n                if (isset($hash[$key])) {\n                    unset($models[$i]);\n                } else {\n                    $hash[$key] = true;\n                }\n            }\n        } elseif (empty($pks)) {\n            throw new InvalidConfigException(\"Primary key of '{$class}' can not be empty.\");\n        } else {\n            // single column primary key\n            $pk = reset($pks);\n            foreach ($models as $i => $model) {\n                if (!isset($model[$pk])) {\n                    // do not continue if the primary key is not part of the result set\n                    break;\n                }\n                $key = $model[$pk];\n                if (isset($hash[$key])) {\n                    unset($models[$i]);\n                } elseif ($key !== null) {\n                    $hash[$key] = true;\n                }\n            }\n        }\n\n        return array_values($models);","sourceCodeStart":265,"sourceCodeEnd":301,"githubUrl":"https://github.com/yiisoft/yii2/blob/66f00d18a29b520f85e8e8f1e32d1e7e7b556cac/framework/db/ActiveQuery.php#L265-L301","documentation":"After a JOIN query, ActiveQuery::populate() calls removeDuplicatedModels() to drop rows duplicated by the join, building a uniqueness hash from the model's primary key values. When $class::primaryKey() returns an empty array (the underlying table declares no primary key), the hash key cannot be computed and InvalidConfigException is thrown. The dedup path only runs when the query has joins and indexBy is null, so plain queries on the same model work fine.","triggerScenarios":"Model::find()->joinWith('relation')->all(), or ->innerJoin(...)->all(), where the model's table or view has no primary key and indexBy is not set; the same via ->leftJoin() with ->with() eager loading.","commonSituations":"AR classes mapped to MySQL/MariaDB views (which cannot declare primary keys); legacy tables created without a PRIMARY KEY constraint; stale schema cache after the key was added; queries that worked until a join was introduced.","solutions":["Add a PRIMARY KEY constraint to the underlying table (preferred)","Override primaryKey() in the AR class to return the column(s) that uniquely identify a row, e.g. public static function primaryKey() { return ['id']; }","Set ->indexBy('unique_column') on the query to bypass the dedup logic entirely","Remove the join if it is not needed, or deduplicate at SQL level (SELECT DISTINCT / GROUP BY)"],"exampleFix":"// before\nclass SalesView extends \\yii\\db\\ActiveRecord\n{\n    public static function tableName() { return 'vw_sales'; }\n}\n$rows = SalesView::find()->joinWith('customer')->all(); // throws\n\n// after\nclass SalesView extends \\yii\\db\\ActiveRecord\n{\n    public static function tableName() { return 'vw_sales'; }\n    public static function primaryKey() { return ['sales_id']; }\n}\n$rows = SalesView::find()->joinWith('customer')->all();","handlingStrategy":"validation","validationCode":"if (!empty($this->join) && $query->indexBy === null && empty($modelClass::primaryKey())) {\n    throw new InvalidConfigException(\"{$modelClass} has no primary key; JOIN deduplication is impossible.\");\n}\n$rows = $query->all();","typeGuard":null,"tryCatchPattern":"try {\n    $rows = Model::find()->joinWith('rel')->all();\n} catch (yii\\base\\InvalidConfigException $e) {\n    if (strpos($e->getMessage(), 'Primary key') === 0) {\n        // fall back to a keyless query without dedup\n        $rows = Model::find()->indexBy('id')->joinWith('rel')->all();\n    } else {\n        throw $e;\n    }\n}","preventionTips":["Ensure every AR-mapped table or view declares (or overrides) a primary key as a project convention","Add a bootstrap check: assert !empty(Model::primaryKey()) for models used in joined queries","Document keyless views as read-only and never join them through AR"],"tags":["primary-key","join","active-record","schema","duplicate-rows"],"backgroundTag":"missing-primary-key","analyzedSha":"66f00d18a29b520f85e8e8f1e32d1e7e7b556cac","analyzedAt":"2026-08-17T05:17:23.470Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}