Home > database >  CakePHP Query Builder 4.x for SQL INSERT INTO IF NOT EXISTS
CakePHP Query Builder 4.x for SQL INSERT INTO IF NOT EXISTS

Time:08-12

This CakePHP Query isn't using the conditional, $subQuery for some reason:

$subQuery = $this->queryFactory->newSelect('table_name')
    ->select(['id'])
    ->where(['id' => $id]);

$query = $this->queryFactory->newQuery()
    ->insert(
        ['id', 'machine', 'logfile', 'updated', 'time']
    )
    ->into('table_name')
    ->values([
        'id' => $id,
        'machine' => $machine['id'],
        'logfile' => $logFile,
        'updated' => $updateDate,
        'time' => $updateTime
    ])
    ->where(function (QueryExpression $exp) use ($subQuery) {
        return $exp->notExists($subQuery);
    });
$query->execute();

...it just inserts record even when it exists, but why?

The above code is only part of the required SQL that looks like this:

IF NOT EXISTS(
    SELECT 1
    FROM table_name
    WHERE id = '$id'
)
    INSERT INTO table_name (id, machine, logfile, updated, time)
    VALUES (?,?,?,?,?)
ELSE
    UPDATE table_name
    SET updated = '$var1', time = ' $var2'
    WHERE id = '$id';

CodePudding user response:

There is no API that would allow to generate such a statement directly, the query builder isn't ment to generate (and execute) such SQL constructs, it can only compile SELECT, INSERT, UPDATE, and DELETE queries, and while the query expression builder can be used to stitch together arbitrary expressions, it will wrap itself and query objects into parentheses (as it is meant for use in query objects), which would be incompatible with what you're trying to build.

So if you want to run such constructs on SQL level, then you either have to write the SQL manually, or create custom expression classes that can build such constructs. In any case you would have to run the SQL manually then.

Here's a very basic quick & dirty example of such a custom expression class:

namespace App\Database\Expression;

use Cake\Database\ExpressionInterface;
use Cake\Database\ValueBinder;
use Closure;

class IfElseExpression implements ExpressionInterface
{
    protected $_if;
    protected $_then;
    protected $_else;
    
    public function if(ExpressionInterface $expression)
    {
        $this->_if = $expression;

        return $this;
    }
    
    public function then(ExpressionInterface $expression)
    {
        $this->_then = $expression;

        return $this;
    }
    
    public function else(ExpressionInterface $expression)
    {
        $this->_else = $expression;

        return $this;
    }

    public function sql(ValueBinder $binder): string
    {
        $if = $this->_if->sql($binder);
        $then = $this->_then->sql($binder);
        $else = $this->_else->sql($binder);

        return "IF $if $then ELSE $else";
    }

    public function traverse(Closure $callback)
    {
        $callback($this->_if);
        $this->_if->traverse($callback);
        
        $callback($this->_then);
        $this->_then->traverse($callback);
        
        $callback($this->_else);
        $this->_else->traverse($callback);

        return $this;
    }

    public function __clone()
    {
        $this->_if = clone $this->_if;
        $this->_then = clone $this->_then;
        $this->_else = clone $this->_else;
    }
}

It could then be used something like this:

$notExists = (new \Cake\Database\Expression\QueryExpression())
    ->notExists($subQuery);

$insertQuery = $this->queryFactory->newQuery()
    ->insert(/* ... */)
    //...
    ;

$updateQuery = $this->queryFactory->newQuery()
    ->update(/* ... */)
    //...
    ;

$ifElse = (new \App\Database\Expression\IfElseExpression())
    ->if($notExists)
    ->then($insertQuery)
    ->else($updateQuery);

$binder = new \Cake\Database\ValueBinder();
$sql = $ifElse->sql($binder);

$statement = $connection->prepare($sql);
$binder->attachTo($statement);
$statement->execute();

See also

CodePudding user response:

Yes, thanks. My own preference is to avoid the requirement to code the value binding explicitly. Using where(), I can do something like this:

    $subQuery = $this->queryFactory->newSelect('table_name')
    ->select(['id'])
    ->where(['id' => $id])
    ->limit(1);
$find = $subQuery->execute()->fetchAll('assoc');

if (!empty($find)) {
    $values = [
        'id' => $id,
        'machine' => $machine,
        'logfile' => $logFile,
        'updated' => $var1,
        'time' => $var2
    ];
    $query = $this->queryFactory->newInsert('table_name', $values);
} else {
    $query = $this->queryFactory->newUpdate('table_name')
    ->set([
        'updated' => $someVar,
        'time' => $someVar2
    ])
    ->where(['id' => $id]);
}
$query->execute();
  • Related