alestro
@alestro

В чем может быть проблема?

Суть в следующем.
Есть таблица с тегами на 11 строк.
Запросом
SELECT `tags`.`id`, `tags`.`name`, `tags`.`slug`, count(`pt`.`tag_id`) as mentions
FROM `tags` 
LEFT JOIN `publications_tags` AS `pt`
    ON `pt`.`tag_id` = `tags`.`id`  
GROUP BY `tags`.`id` 
ORDER BY `mentions` DESC

Получаю все строки отсортированные по количеству упоминай в статьях.
654398de9b546705606174.png
При работе непосредственно с базой через heidiSql все запросы возвращают ожидаемый результат.
А вот при получении данных через php запросы возвращают неверные данные - нарушается порядок сортировки.
В частности, запрос вида
SELECT `tags`.`id`, `tags`.`name`, `tags`.`slug`, count(`pt`.`tag_id`) as mentions
FROM `tags` 
LEFT JOIN `publications_tags` AS `pt`
    ON `pt`.`tag_id` = `tags`.`id`  
GROUP BY `tags`.`id` 
ORDER BY `mentions` DESC
LIMIT 10 OFFSET 10

Должен вернуть одну строку с id 10 (последняя строка в выборке на скриншоте).
Однако возвращается запись с id 4 (6 строка в выборке).
Для получения записей использую SelectQuery из пакета Cycle\Database\Query
Код моего фетчера.
use Cycle\Database\Injection\Fragment;
use Cycle\Database\Query\SelectQuery;
use Cycle\ORM\ORMInterface;
use Cycle\ORM\SchemaInterface;
use Cycle\ORM\Select\SourceInterface;

abstract class DataFetcher implements OrmAwareInterface
{
    /**
     * @var Selectable[]
     */
    protected array $applies = [];

    protected array $columns = [];
    
    protected ?RowFetcher $rowFetcher = null;

    /**
     * @param Selectable[] $applies
     */
    public function __construct(
        protected ORMInterface $orm,
        array $applies = []
    ) {
        $this->init();
        foreach ($applies as $name => $apply) $this->add($name, $apply);
    }

    public function add(string $name, Selectable $apply): void
    {
        if ($apply instanceof OrmAwareInterface) $apply->setOrm($this->orm);
        $this->applies[$name] = $apply;
    }

    public function setOrm(ORMInterface $orm): OrmAwareInterface
    {
        $this->orm = $orm;
        return $this;
    }

    protected function select(SourceInterface $source): SelectQuery
    {
        return $source->getDatabase()
            ->select($this->columns)
            ->from($source->getTable());
    }

    protected function doFetch(?QueryInterface $query):? Result
    {
        $source = $this->orm->getSource($this->getRole());

        $pk = $this->orm->getSchema()->define($this->getRole(), SchemaInterface::PRIMARY_KEY);
        if (is_array($pk)) $pk = "{$source->getTable()}.$pk[0]";
        else $pk = "{$source->getTable()}.$pk";

        $select = $this->select($source);
        $select = $this->apply($query, $select);

        if (($count = $this->countRows($select, $pk)) > 0) {
            $results = [];
            foreach ($select as $row) $results[] = $this->getRowFetcher()->fetch($row);
            return new Result($results, $count);
        }

        return null;
    }

    protected function getRowFetcher(): RowFetcher
    {
        if (!$this->rowFetcher) $this->rowFetcher = new RowFetcher($this->getRole(), $this->orm);
        return $this->rowFetcher;
    }

    protected function apply(?QueryInterface $query, SelectQuery $select): SelectQuery
    {
        if (!$query) return $select;
        foreach ($query->toArray() as $name => $value) {
            if (isset($this->applies[$name])) {
                $select = $this->applies[$name]->apply($select, $value);
                if ($this->applies[$name] instanceof RowFetcherInterface) {
                    $this->getRowFetcher()->extend($this->applies[$name]);
                }
            }
        }

        return $select;
    }

    protected function countRows(SelectQuery $select, string $primaryKey): int
    {
        // клонирует SelectQuery и выполняет запрос на подсчет строк
        return Counter::countDistinct($select, $primaryKey);
    }

    protected function init(): void
    {
        $this->initColumns();
        $this->add('limit', new ApplyLimit);
        $this->add('offset', new ApplyOffset);
    }

    abstract protected function getRole(): string ;
    
    protected function initColumns(): void
    {
        if ($this->columns == []) {
            $source = $this->orm->getSource($this->getRole());
            $columns = $this->orm->getSchema()->define($this->getRole(), SchemaInterface::COLUMNS);

            foreach ($columns as $alias => $column) {
                if (is_int($alias)) $this->columns[] = "{$source->getTable()}.$column" ;
                else $this->columns[] = "{$source->getTable()}.$column as $alias" ;
            }
        }
    }
}

Все данные биндятся правильно. Проверял вплоть до PDOStatement драйвера базы данных.
При этом, если сделать параллельный запрос через PDO (новый инстанс).
$pdo = new \PDO('mysql:host=localhost;dbname=dbname', 'user', 'psd');
        $stmt = $pdo->prepare('SELECT `tags`.`id`, `tags`.`name`, `tags`.`slug`, count(`pt`.`tag_id`) as mentions
FROM `tags` 
LEFT JOIN `publications_tags` AS `pt`
    ON `pt`.`tag_id` = `tags`.`id`  
GROUP BY `tags`.`id` 
ORDER BY `mentions` DESC
LIMIT 10 OFFSET 10');
        $stmt->execute();

        dd($select->fetchAll(), $stmt->fetchAll(\PDO::FETCH_ASSOC));

То PDO вернет ожидаемый результат, а $select описанное поведение.
65439dfc82b64256148076.png
При этому данное поведение возникает только для пары limit:10; offset: 10;
Если задать лимит, например, 11 или отступ 9, то работает так, как ожидается.
В чем может быть проблема?
  • Вопрос задан
  • 106 просмотров
Решения вопроса 1
Rsa97
@Rsa97
Для правильного вопроса надо знать половину ответа
Должен вернуть одну строку с id 10 (последняя строка в выборке на скриншоте)
Кому должен? Программа должна делать только то, что вы в ней написали. Строки по колонке mentions отсортированы, десятая строка выбрана. То, что нет других правил сортировки означает, что строки с одинаковым значением mentions можно выдавать в любом порядке.
Ответ написан
Комментировать
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы