CakePHP Query Builder 4.x for SQL INSERT INTO IF NOT EXISTS - sql-server

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';

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
Cookbook > Database Access & ORM > Database Basics > Interacting with Statements

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

Yii2 using ArrayHelper with another database

I am working on Yii2. I am using mysql and mssql databases. The mssql is on a remote site and I am able to access it. Now I am trying to add a dropdown list.
Controller
public function actionCreate()
{
$model = new AllowArea();
$sds = Yii::$app->sds->createCommand("Select * from Area")->queryAll();// mssql Database
if ($model->load(Yii::$app->request->post()) && $model->save()) {
return $this->redirect(['view', 'id' => $model->id]);
}
return $this->render('create', [
'model' => $model,
'sds' => $sds
]);
}
View
<?= $form->field($model, 'salesman_code')->dropDownList(\common\models\AllowArea::toArrayList(), ['prompt' => 'Select a Booker']) ?>
Model
In my model, I have a function
public static function toArrayList(){
$sds = Yii::$app->sds->createCommand("Select * from Salesmen")->queryAll();
return ArrayHelper::map($sds::find()->all(),'SalesmanCode',function($sds, $defaultValue){
return $sds['SalesmanCode'].' - '.$sds['SalesmanNameFull'];
});
}
Previously I was using self in place of $sds. With $sds I am getting error
Class name must be a valid object or a string
Any help would be highly appreciated
You are not using Model/Class. So, in ArrayHelper
return ArrayHelper::map($sds, 'SalesmanCode', function($sds) {
return $sds['SalesmanCode'].' - '.$sds['SalesmanNameFull'];
});

Sorty by calculated field with active record yii2

I have threads and messages on thread
I want to return all threads with the last message time, so I added a new field like this on thread model
public function fields()
{
$fields= ['idThread', 'idUser', 'title', 'unread', 'username','lastMesageTime'];
return $fields;
}
now with this method I get the calculated value lastMessageTime
public function getLastMessageTime()
{
return $this->hasMany(Messages::className(), ['idThread' => 'idThread'])
->select('time')->orderBy('time DESC')->limit(1)->scalar();
}
on my index method using active record like this
return Thread::find()->select('idThread, title, idUser')->all();
this works and I get lastMessageTime with the right value, but I want to order by so I can get the thread with the most recent lastMessageTime the first one, I tried with the following code
public function scopes() {
return array(
'byOrden' => array('order' => 'lastTimeMessage DESC'),
);
}
any idea?
Edit:
this workaround works, but I think this is not a good way because I'm not using active record so fields like username that I had defined on Thread model I had to fetch it again
$query = (new \yii\db\Query());
$query->select('*, (SELECT max(time) as lastMessageTime from messages where messages.idThread = thread.idThread ) lastMessageTime,
(SELECT name from users where users.idUser = thread.idUser) as name ')
->from('threads')
->where(['idUser'=>$idUser])
->orderBy('lastMessageTime DESC');
$rows = $query->all();
return $rows;
You can define extra fields as model properties, then override find method to load data for them.
class Thread extends \yii\db\ActiveRecord
{
public $lastMessageTime;
public static function find()
{
$q = parent::find()
->select('*')
->addSelect(
new \yii\db\Expression(
'(SELECT max(time) FROM messages WHERE messages.idThread = thread.idThread) AS lastMessageTime'
);
return $q;
}
}
Then you can load and order models like this:
$rows = Thread::find()->orderBy(['lastMessageTime' => SORT_DESC])->all();

How to insert into a table based on an Eloquent relationship an array of foreign keys

I have two models TeamMember and ProjectRequest.
A TeamMember can have one ProjectRequest, that is why I created the following Eloquent relationship on TeamMember:
class TeamMember extends Model {
//
protected $table = 'team_members';
protected $fillable = ['project_request_id'];
// Relations
public function projectTeam() {
return $this->hasOne('\App\Models\ProjectRequest', 'project_request_id');
}
}
In my Controller I want to query both tables, however it returns the failure message.
What is important to know is that $request->projectTeam is an array of emails, looking like this:
array:2 [
0 => "mv#something.com"
1 => "as#something.com"
]
Meaning that I need to bulk insert into team_members table the project_request_ id for each team member where the emails are in the array.
How can I do that in the right way? The following is my attempt:
public function createProjectTeam(Request $request){
try {
$title = $request->projectTitle;
$TeamMember = $request->projectTeam;
$projectRequest = ProjectRequest::create(['project_title' => $title]);
$projectRequestId = $projectRequest->id;
$projectTeam = $this->teamMembers->projectTeam()->create(['project_request_id'=> $projectRequestId])->where('email', $TeamMember);
//$projectTeam = TeamMember::createMany(['project_request_id' => $projectRequestId])->where($TeamMember);
//dd($projectTeam);
return $projectRequest.$projectTeam;
} catch(\Exception $e){
return ['success' => false, 'message' => 'project team creation failed'];
}
}
There are a few things you can do.
Eloquent offers a whereIn() method which allows you to query where a field equals one or more in a specified array.
Secondly, you can use the update() method to update all qualifying team members with the project_request_id:
public function createProjectTeam(Request $request)
{
try {
$projectRequest = ProjectRequest::create(['project_title' => $request->projectTitle]);
TeamMember::whereIn('email', $request->projectTeam)
->update([
'project_request_id' => $projectRequest->id
]);
return [
'success' => true,
'team_members' => $request->projectTeam
];
} catch(\Exception $e) {
return [
'success' => false,
'message' => 'project team creation failed'
];
}
}
I hope this helps.

Update records of database table : Laravel

I need to update the database table according to the edited data.
controller
public function update(Request $request)
{
$subscriptionplan = SubscriptionPlan::find($request->id);
$subscriptionplan->update($request->all());
return back();
}
But nothing happens when I submit the form. When I use dd($request->all()); at the beginning of the function, it correctly shows the edited data as follows.
array:10 [▼
"_method" => "patch"
"_token" => "gOCL4dK6TfIgs75wV87RdHpFZkD7rBpaJBxJbLHF"
"editname" => "SUP_EVA_001"
"editdesc" => "des"
"editprice" => "1000.050"
"editlimit" => "1"
"editperunit" => "20.000"
"editexceedunit" => "30.000"
"productid" => "1"
"id" => "1"
]
But database has not been updated.
My table name is Table: subscription_plans and model is SubscriptionPlan
These are the table columns:
protected $fillable = [
'name',
'description',
'price',
'usage_limit',
'charge_per_unit',
'charge_per_unit_exceed',
'is_limit_exceed_considered',
'product_id'
];
Any idea on how to solve it or what I have done wrong?
If your solution did not work, try the 1by1 like this.
public function update(Request $request)
{
$subscriptionplan = SubscriptionPlan::find($request->id);
$subscriptionplan->_method = $request->_method;
$subscriptionplan->_token = $request->_token;
$subscriptionplan->editname = $request->editname;
$subscriptionplan->editdesc = $request->editdesc;
$subscriptionplan->editprice = $request->editprice;
$subscriptionplan->editlimit = $request->editlimit;
$subscriptionplan->editperunit = $request->editperunit;
$subscriptionplan->editexceedunit = $request->editexceedunit;
$subscriptionplan->productid = $request->productid;
$subscriptionplan->save();
return back();
}
In order for Laravel to automatically fill the model attributes, the indexes of the array passed to the fill method must correspond to your model attributes names.
Also, instead of
$subscriptionplan->update($request->all());
Use
$subscriptionplan->fill($request->all());
Then save the subscription plan with $subscriptionplan->save();

Which of Database::<QUERY_TYPE> I should use in Kohana 3.3?

I need perform query "LOCK TABLE myTable WRITE"
<?php
$db = Database::instance();
$query = 'LOCK TABLE `myTable` WRITE';
$db->query(Database::WHAT_IS_THE_TYPE_SHOULD_SPECIFY_HERE_?, $query);
In framework Kohana 3.3
file ~/modules/database/classes/Kohana/Database.php
implements folowing types:
const SELECT = 1;
const INSERT = 2;
const UPDATE = 3;
const DELETE = 4;
but none of them fit in my case.
Any idea. Thanks.
Google works wonders. http://forum.kohanaframework.org/discussion/6401/lockunlock/p1
You can pass NULL as the first argument:
$db = Database::instance();
$query = 'LOCK TABLE `myTable` WRITE';
$db->query(NULL, $query);
From Kohana: http://kohanaframework.org/3.3/guide-api/Database_MySQL#query
if ($type === Database::SELECT)
{
// Return an iterator of results
return new Database_MySQL_Result($result, $sql, $as_object, $params);
}
elseif ($type === Database::INSERT)
{
// Return a list of insert id and rows created
return array(
mysql_insert_id($this->_connection),
mysql_affected_rows($this->_connection),
);
}
else
{
// Return the number of rows affected
return mysql_affected_rows($this->_connection);
}
As you can see that's why you're able to pass NULL.

Resources