How can I add condition in db raw laravel? - mysql

My query db raw laravel like this :
public function getTopProduct($price = null) {
$products = DB::select(DB::raw('SELECT *
FROM (
SELECT a.*, b.name AS store_name, b.address
FROM products a
JOIN stores b ON b.id = a.store_id
WHERE a.status = 1
) AS product
GROUP BY store_id')
);
return $products;
}
I want to add condition
If price is not null, it will add condition price on the where
For example, price = 1000, then the query on the where like this :
WHERE a.status = 1 AND a.price < 1000
If price = null, the condition AND a.price < 1000 not executed
How can I do it?
Update
I slightly change my code flow
I try like this :
public function getTopProduct($price)
{
if($price == 1)
$price_condition = 'WHERE price > 1000';
else if($price == 2)
$price_condition = 'WHERE price >= 500 AND a.price <= 1000';
else if($price == 3)
$price_condition = 'WHERE price < 500';
else
$price_condition = '';
$products = DB::select('SELECT *
FROM (
SELECT a.*, b.name AS store_name, b.address
FROM products a
JOIN stores b ON b.id = a.store_id
WHERE a.status = 1
) AS product
GROUP BY store_id
'.$price_condition
);
return $products;
}
And it works
How do you think?
Whether my solution is correct? Or you have a better solution?

public function getTopProduct($price = null) {
if($price==null){
$products = DB::select(DB::raw('SELECT *
FROM (
SELECT a.*, b.name AS store_name, b.address
FROM products a
JOIN stores b ON b.id = a.store_id
WHERE a.status = 1
) AS product
GROUP BY store_id')
);
}else{
$products = DB::select(DB::raw('SELECT *
FROM (
SELECT a.*, b.name AS store_name, b.address
FROM products a
JOIN stores b ON b.id = a.store_id
WHERE a.status = 1 AND a.price='.$price.'
) AS product
GROUP BY store_id')
);
}
return $products;
}

You may use Advanced Join Clauses in Query Builder
DB::table('products')
->join('stores', function ($join) use ($price) {
$query = $join->on('stores.id', '=', 'products.store_id')
->where('products.status', '=', 1);
if (!empty($price)) {
$query->where('products.price', '<', 1000);
}
})
->select('products.*', 'stores.name AS store_name', 'stores.address')
->groupBy('products.store_id')
->get();

Try something like below code with your where condition:
$price = array_column($request->get('price'), 'id');
OR
$price = $request->get('price');
$query = DB::select(DB::raw('SELECT *
FROM (
SELECT a.*, b.name AS store_name, b.address
FROM products a
JOIN stores b ON b.id = a.store_id
WHERE a.status = 1
) AS product
GROUP BY store_id'))
->when($price == 1, function($query) use ($price) {
$query->where('price', '>', '1000');
})
->when($price == 2, function($query) use ($price) {
$query->where('price', '>=', '500')
->where('a.price', '<=', '1000');
})
->when($price == 3, function($query) use ($price) {
$query->where('price', '<', '500');
})
->when($price == '', function($query) use ($price) {
$query->where('your_where_when_price_empty');
})
->get();
Hope this helps you!

Related

How To Select And Join two table with last record in codeigniter

i want join last row is_read column in ticket_message table to tickets table.
i used this sample code and the results are not sorted by id.
SELECT t.*,m.is_read
FROM tickets AS t
LEFT JOIN ticket_message AS m ON t.id = m.ticket_id
LEFT JOIN ticket_message AS m1 ON m.ticket_id = m1.ticket_id
AND m.id < m1.id
WHERE m1.id IS NULL
$this->db->select('t.*,m.is_read');
$this->db->from('tickets as t');
$this->db->join('ticket_message as m', 't.id = m.ticket_id', 'left');
$this->db->join('ticket_message as m1', 'm.ticket_id = m1.ticket_id AND m.id < m1.id', 'left');
$this->db->where('m1.id IS NULL', null, false);
$query = $this->db->get();
return $query;
what do i do.
Sorted by ID? I assume you mean ordered by ID.. in which case you just add this to your build query:
$this->db->order_by('t.id', 'ASC');
Your whole query:
$this->db->select('t.*,m.is_read');
$this->db->from('tickets as t');
$this->db->join('ticket_message as m', 't.id = m.ticket_id', 'left');
$this->db->join('ticket_message as m1', 'm.ticket_id = m1.ticket_id AND m.id < m1.id', 'left');
$this->db->where('m1.id IS NULL', null, false);
$this->db->order_by('t.id', 'ASC');
$query = $this->db->get();
return $query;

Trying to convert a query to eloquent

I have the following SQL:
SELECT arv.*
FROM article_reference_versions arv
INNER JOIN (SELECT `order`,
Max(`revision`) AS max_revision
FROM article_reference_versions
WHERE `file` = '12338-230180-1-CE.doc'
GROUP BY `file`,
`order`) AS b
ON arv.order = b.order
AND arv.revision = b.max_revision
WHERE arv.file = '12338-230180-1-CE.doc'
I need to convert this to Eloquent, so that I can properly access the data in object form. I tried doing it as such,
$s = Models\EloArticleReferenceVersion::select(
'SELECT arv.*
FROM article_reference_versions arv
INNER JOIN (
SELECT `order`, max(`revision`) as max_revision
FROM article_reference_versions
WHERE file = ? group by `file`, `order`) AS b
ON
arv.order = b.order AND arv.revision = b.max_revision
WHERE arv.file = ?',
[
'12338-230180-1-CE.doc',
'12338-230180-1-CE.doc'
])->get();
dd($s);
But I'm running into a plethora of issues, one after another. I figured it'd be easier to just convert this into an eloquent query, looking for some help with this.
DB Query to Query using Eloquent.
$query = EloArticleReferenceVersion::query()
->join(DB::raw('( SELECT `order`,Max(`revision`) AS max_revision FROM article_reference_versions WHERE `file` = '12338-230180-1-CE.doc' GROUP BY `file`, `order`) as sub_table'), function($join) {
$join->on('sub_table.order', '=', 'article_reference_versions.order');
$join->on('sub_table.max_revision ', '=', 'article_reference_versions.revision');
})
->where('article_reference_versions.file', '=', '12338-230180-1-CE.doc' )
->get();
Not Tested

Query return empty result but data exist

I'm using Slim with PDO with MySql for return a specific list of matches available in my database. My query is this:
SELECT m.*,
t.name AS home_team_name,
t2.name AS away_team_name
FROM `match` m
LEFT JOIN team t ON m.home_team_id = t.id
LEFT JOIN team t2 ON m.away_team_id = t2.id
WHERE (home_team_id = 117 OR away_team_id = 117) AND round_id = 488
if I execute this query I'll get a list of matches:
but inside the API developed with Slim I get an empty array. This is the method structure:
$app->get('/match/get_matches_by_team/{round_id}/{team_id}/{type}', function (Request $request, Response $response, array $args)
{
$query = "SELECT m.*,
t.name AS home_team_name,
t2.name AS away_team_name
FROM `match` m
LEFT JOIN team t ON m.home_team_id = t.id
LEFT JOIN team t2 ON m.away_team_id = t2.id
WHERE ";
switch($args["type"])
{
case "home":
$query .= "home_team_id = :team_id AND ";
break;
case "away":
$query .= "away_team_id = :team_id AND ";
break;
default:
$query .= "(home_team_id = :team_id OR away_team_id = :team_id) AND ";
break;
}
$query .= "round_id = :round_id";
$sql = $this->db->prepare($query);
$sql->bindParam("team_id", $args["team_id"]);
$sql->bindParam("round_id", $args["round_id"]);
$sql->execute();
$result = $sql->fetchAll();
return $response->withJson($result);
});
what I did wrong?
Thanks in advance for any help.
UPDATE
If I do echo $query; return; I'll get:
SELECT m.*,
t.name AS home_team_name,
t2.name AS away_team_name
FROM `match` m
LEFT JOIN team t ON m.home_team_id = t.id
LEFT JOIN team t2 ON m.away_team_id = t2.id
WHERE away_team_id = :team_id AND round_id = :round_id
supposing to pass away, if instead I pass all I'll get:
SELECT m.*,
t.name AS home_team_name,
t2.name AS away_team_name
FROM `match` m
LEFT JOIN team t ON m.home_team_id = t.id
LEFT JOIN team t2 ON m.away_team_id = t2.id
WHERE (home_team_id = :team_id OR away_team_id = :team_id) AND round_id = :round_id
UPDATE 2
Method updated with proposed hints
$app->get('/match/get_matches_by_team
/{round_id}/{team_id}/{type}', function (Request $request, Response $response, array $args)
{
$query = "SELECT m.*,
t.name AS home_team_name,
t2.name AS away_team_name
FROM `match` m
LEFT JOIN team t ON m.home_team_id = t.id
LEFT JOIN team t2 ON m.away_team_id = t2.id
WHERE ";
switch($args["type"])
{
case "home":
$query .= "home_team_id = :home_team_id
AND ";
break;
case "away":
$query .= "away_team_id = :away_team_id AND ";
break;
default:
$query .= "(home_team_id = :home_team_id OR away_team_id = :away_team_id) AND ";
break;
}
$query .= "round_id = :round_id";
$sql = $this->db->prepare($query);
$sql->bindParam("home_team_id", $args["team_id"]);
$sql->bindParam("away_team_id", $args["team_id"]);
$sql->bindParam("round_id", $args["round_id"]);
$sql->execute();
$result = $sql->fetchAll();
return $response->withJson($result);
});
yours:
$sql->bindParam("team_id", $args["team_id"]);
$sql->bindParam("round_id", $args["round_id"]);
try this, the params might be needed to be formatted differently
$sql->bindParam(":team_id", $args["team_id"], PDO::PARAM_INT);
$sql->bindParam(":round_id", $args["round_id"], PDO::PARAM_INT);
or
$sql->bindParam(":team_id", $args["team_id"]);
$sql->bindParam(":round_id", $args["round_id"]);
When the default switch is selected, you are trying to bind a value with the same parameter marker (in your case :team_id) twice. In order for this to work you have to turn on emulation mode in PDO.
http://www.php.net/manual/en/pdo.prepare.php
You must include a unique parameter marker for each value you wish to pass in to the statement when you call PDOStatement::execute(). You cannot use a named parameter marker of the same name more than once in a prepared statement, unless emulation mode is on.

How can I add condition on the laravel query without may allow an SQL injection attack?

My method like this :
public function getTopProduct($num, $category_id, $price)
{
if($category_id) {
$cache_category_key = 'top-product-category-'.$category_id;
if(cache($cache_category_key))
return cache($cache_category_key);
$category_condition = 'AND d.category_id = :category_id';
$price_condition = '';
$param = ['category_id'=>$category_id, 'num'=>$num];
}
else {
if($price)
$price_cache = $price['min'].$price['max'];
else
$price_cache = '';
$cache_price_key = 'top-product-price-'.$price_cache;
if(cache($cache_price_key))
return cache($cache_price_key);
$category_condition = '';
if($price) {
$price_condition = 'AND x.price >= :price_min AND x.price <= :price_max';
$param = ['price_min'=>$price['min'], 'price_max'=>$price['max'], 'num'=>$num];
}
else{
$price_condition = '';
$param = ['num'=>$num];
}
}
$query_top_product = '
SELECT x.id, x.store_id, x.name, x.photo, x.price, x.total_sold, y.name AS store_name, y.address
FROM products x
JOIN stores y ON y.id = x.store_id
WHERE x.id = (
SELECT a.id
FROM products a
JOIN stores b ON b.id = a.store_id
JOIN users c ON c.id = b.user_id
JOIN products_categories d ON d.product_id = a.id
WHERE a.status = 1 AND a.stock > 0 AND a.deleted_at IS NULL AND b.status = 1 '.$category_condition.' AND a.store_id = x.store_id AND c.updated_at >= DATE(NOW()) - INTERVAL 3 DAY
ORDER BY a.created_at DESC, a.updated_at DESC
LIMIT 1
)
'.$price_condition.'
LIMIT :num
';
//see for protection in db raw http://fideloper.com/laravel-raw-queries
$products = DB::select(DB::raw($query_top_product),$param);
if($category_id)
Cache::forever('top-product-category-'.$category_id,$products);
else {
if($price)
$price_cache = $price['min'].$price['max'];
else
$price_cache = '';
Cache::forever('top-product-price-'.$price_cache,$products);
}
return $products;
}
The method above works. But when I check my project using https://insight.sensiolabs.com, there exist error like this :
If provided by the user, the value of $category_condition may allow an
SQL injection attack. Avoid concatenating parameters to SQL query
strings, and use parameter binding instead.
Whereas I had using parameter binding
I'm still confused to solve my problem. Because from my case need code like that. I want to using laravel eloquent. But from my query, seems that's difficult
From my code above, I also use cache. So if its cache already exists, it will grab from the cache
How can I solve the error?
Laravel comes with Mysql Injection protection by default, you just need to use Eloquent or Query Builder, for example:
$users = User::select('*');
if ($request->has('only_active')) {
$users->where('active', true);
}
if ($request->has('search')) {
$users->where('name', 'like', '%'.$request->search.'%');
}
// a lot of more filters you can add...
return $users->get();
Laravel also provides support for joins you can apply using Eloquent or Query Builder
Your code is just fine. No need to add anything special because $category_condition is part of your query and is not provided by user.
Laravel's Query Builder (I mean DB class you already used) is protected against SQL Injection - even for raw queries - as long as you use parameter bindings.
You may Read documentation for more information.
Anyway your code will be much readable if you use Laravel's standard query builder instead of raw queries.
Your query is safe, because although you interpolate PHP variables $category_condition and $price_condition into the final query, those variable do not contain any unsafe content. In all of your code paths, those variables are set to fixed strings, not any content depending on user input or other unsafe data.
So the sensiolabs check is being overly cautious. They wrote a naive attempt to find unsafe variables, and they didn't get it quite right. I don't mean to disparage their efforts — to do it right, you really need the language to support Taint Checking, but PHP has no support for this.
If you want to pass the sensiolabs test, I assume (though I have not tried it) that you have to write the entire query as a single string with no variables. This means repeating most of the query in each of your code paths.
if($category_id) {
$cache_category_key = 'top-product-category-'.$category_id;
if(cache($cache_category_key))
return cache($cache_category_key);
$query_top_product = '
SELECT x.id, x.store_id, x.name, x.photo, x.price, x.total_sold, y.name AS store_name, y.address
FROM products x
JOIN stores y ON y.id = x.store_id
WHERE x.id = (
SELECT a.id
FROM products a
JOIN stores b ON b.id = a.store_id
JOIN users c ON c.id = b.user_id
JOIN products_categories d ON d.product_id = a.id
WHERE a.status = 1 AND a.stock > 0
AND a.deleted_at IS NULL AND b.status = 1
AND d.category_id = :category_id
AND a.store_id = x.store_id
AND c.updated_at >= DATE(NOW()) - INTERVAL 3 DAY
ORDER BY a.created_at DESC, a.updated_at DESC
LIMIT 1
)
LIMIT :num
';
$param = [
'category_id'=>$category_id,
'num'=>$num
];
}
else {
if($price)
$price_cache = $price['min'].$price['max'];
else
$price_cache = '';
$cache_price_key = 'top-product-price-'.$price_cache;
if(cache($cache_price_key))
return cache($cache_price_key);
if($price) {
$query_top_product = '
SELECT x.id, x.store_id, x.name, x.photo, x.price, x.total_sold, y.name AS store_name, y.address
FROM products x
JOIN stores y ON y.id = x.store_id
WHERE x.id = (
SELECT a.id
FROM products a
JOIN stores b ON b.id = a.store_id
JOIN users c ON c.id = b.user_id
JOIN products_categories d ON d.product_id = a.id
WHERE a.status = 1 AND a.stock > 0
AND a.deleted_at IS NULL AND b.status = 1
AND a.store_id = x.store_id
AND c.updated_at >= DATE(NOW()) - INTERVAL 3 DAY
ORDER BY a.created_at DESC, a.updated_at DESC
LIMIT 1
)
AND x.price >= :price_min AND x.price <= :price_max
LIMIT :num
';
$param = [
'price_min'=>$price['min'],
'price_max'=>$price['max'],
'num'=>$num
];
}
else{
$query_top_product = '
SELECT x.id, x.store_id, x.name, x.photo, x.price, x.total_sold, y.name AS store_name, y.address
FROM products x
JOIN stores y ON y.id = x.store_id
WHERE x.id = (
SELECT a.id
FROM products a
JOIN stores b ON b.id = a.store_id
JOIN users c ON c.id = b.user_id
JOIN products_categories d ON d.product_id = a.id
WHERE a.status = 1 AND a.stock > 0
AND a.deleted_at IS NULL AND b.status = 1
AND a.store_id = x.store_id
AND c.updated_at >= DATE(NOW()) - INTERVAL 3 DAY
ORDER BY a.created_at DESC, a.updated_at DESC
LIMIT 1
)
LIMIT :num
';
$param = ['num'=>$num];
}
}
This might be more repetition than you want, and it makes the code less maintainable, because if you ever change the SQL query you'll have to change it in three places.
So you must decide how important it is to pass the sensiolabs check.

Options to update PDO Query

How i update this query?
I try get all category with more than 1 product, counting this products to show in her side, but my query is too slow (2s). How i do the same, if possible, but more faster?
SELECT
C.id, C.name, C.id_dept, C.cat_type, C.num_prod
FROM
(SELECT
C.id, C.name, C.id_dept, C.cat_type,
COALESCE((SELECT count(P.id) FROM Products P WHERE P.status = 1 AND P.promo = 1 AND P.id_cat = C.id ), 0) AS num_prod
FROM
Products_Cat C
) C
WHERE
C.num_prod > 0 AND C.cat_type = 1 AND C.id_dept = '{IDD}'
ORDER BY C.name ASC
Update PHP PDO
$sql = $db->prepare("UPDATE `Products` set `name` = :name, `id_dept` = :id_dept, `cat_type` = :cat_type WHERE status = 1 AND promo = 1);
//bind all parameters
$sql->bindParam(':name',$name);
.
.
.
$sql->execute();