How can I convert many statement mysql dinamis to laravel eloquent? - php

Mysql query like this :
SET #sql_dinamis = (
SELECT GROUP_CONCAT(
DISTINCT CONCAT(
'SUM( IF(id_barang=', id_barang, ',jml_bk,0) ) AS br',
id_barang
)
)
FROM barang_keluar
);
SET #SQL = CONCAT(
'SELECT month(tgl_keluar) as m, ',#sql_dinamis,'
FROM barang_keluar
WHERE month(tgl_keluar) and year(tgl_keluar)=2019
GROUP BY month(tgl_keluar)'
);
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
I want to convert it to Laravel Eloquent, but I'm confused. Because there exist many statement. There exist PREPARE, EXECUTE, SET, DEALLOCATE etc. You can see query above.
How can I convert it to Laravel Eloquent?

You don't need to manually prepare statements (PREPARE, EXECUTE, DEALLOCATE) in Laravel since the Query Builder calls PDO::prepare, PDO::bindValue and PDO::execute behind the scenes.
You will be responsable for escaping/sanitizing the input however.
You can achieve this query by using a few raw methods with the query builder.
After some experimentation, I found out the real sql query created by your code is something like this:
SELECT
month(tgl_keluar) as m,
SUM(IF(id_barang=1,jml_bk,0)) AS br42,
SUM(IF(id_barang=2,jml_bk,0)) AS br48,
SUM(IF(id_barang=3,jml_bk,0)) AS br13,
SUM(IF(id_barang=4,jml_bk,0)) AS br14,
.
.
.
SUM(IF(id_barang=n-1,jml_bk,0)) AS brn-1
SUM(IF(id_barang=n,jml_bk,0)) AS brn
FROM barang_keluar
WHERE month(tgl_keluar) AND year(tgl_keluar)=2019
GROUP BY month(tgl_keluar)
To translate this into the query builder, we'll need 2 queries:
/**
* Equivalent to
*
* SELECT
* id_barang
* FROM barang_keluar;
*/
$ids_barang = DB::table('barang_keluar')
->select('id_barang')
->get();
/**
* Equivalent to
*
* SELECT
* month(tgl_keluar) as m,
* FROM barang_keluar
* WHERE month(tgl_keluar) AND year(tgl_keluar)=2019
* GROUP BY `m`;
*/
// Pass year as a variable if you want. You can also hardcode it
$year = 2019;
$query = DB::table('barang_keluar')
->selectRaw('month(tgl_keluar) as m')
->whereRaw('month(tgl_keluar) and year(tgl_keluar)=?', [$year])
->groupBy('m');
Since we didn't call ->get(), we can still add to the query.
// Now, we add all the `SUM()` statements.
foreach ($ids_barang as $row) {
$query->selectRaw("sum(if(eme_id=?,eme_empresa_id,0)) as br{$row->id_barang}", [$row->id_barang]);
}
// And finally, get the query results
$results = $query->get();
You can verify this produces the query by dumping $query->>toSql().

Related

how to execute complex mysql queries in laravel

I have one below mysql query that is working fine but i want to run it laravel using prepare statement.
SET #sql = NULL;
SELECT GROUP_CONCAT(CONCAT("SELECT '",colname,":' AS 'Label',GROUP_CONCAT(JSON_UNQUOTE(JSON_EXTRACT(attr_details,'$.", colname,"'))) AS 'val' FROM mytable GROUP BY Label") SEPARATOR " UNION ")
INTO #sql
FROM
(WITH RECURSIVE data AS (
SELECT attr_details,JSON_VALUE(JSON_KEYS(attr_details), '$[0]') AS colname, 0 AS idx FROM mytable
UNION
SELECT attr_details,JSON_VALUE(JSON_KEYS(attr_details), CONCAT('$[', d.idx + 1, ']'))
AS colname, d.idx + 1 AS idx FROM data AS d
WHERE d.idx < JSON_LENGTH(JSON_KEYS(attr_details)) - 1
) SELECT colname
FROM data
GROUP BY colname) V;
PREPARE stmt FROM #sql;
EXECUTE stmt;;
Now i have tried to convert in larvel like below
$PDO=DB::connection('mysql')->getPdo();
$stmt = $PDO->prepare(<<<_OUT
SET #sql = NULL;
SELECT GROUP_CONCAT(CONCAT("SELECT '",colname,"' AS 'Label',GROUP_CONCAT(JSON_UNQUOTE(JSON_EXTRACT(attr_details,'$.", colname,"'))) AS 'val' FROM product_attributes GROUP BY Label") SEPARATOR " UNION ")
INTO #sql
FROM
(WITH RECURSIVE data AS (
SELECT attr_details,JSON_VALUE(JSON_KEYS(attr_details), '$[0]') AS colname, 0 AS idx FROM product_attributes
UNION
SELECT attr_details,JSON_VALUE(JSON_KEYS(attr_details), CONCAT('$[', d.idx + 1, ']'))
AS colname, d.idx + 1 AS idx FROM data AS d
WHERE d.idx < JSON_LENGTH(JSON_KEYS(attr_details)) - 1
) SELECT colname
FROM data
GROUP BY colname) V;
_OUT
);
$stmt->execute();
$result = $stmt->fetchAll();
echo "<pre>"; print_r($result); die;
I am getting this error "syntax error, unexpected 'SELECT' (T_STRING), expecting ')'",
Can anyone help me what i am doing wrong
Please check your quotes at first. In the code "SELECT GROUP_CONCAT(CONCAT("SELECT PHP recognizes that as complete string "SELECT GROUP_CONCAT(CONCAT(" and something undefined SELECT ' with the next string, without concatenation.
At least for me my IDE highlights your code as incorrect. To deal with various quotes try to use that approach
$stmt = $PDO->prepare(<<<_OUT
SELECT * FROM `table` WHERE "1";
_OUT
);
Try to write the request without #sql variable, without PREPARE stm and without EXECUTE stm. I think, PDO will deal with preparing and executing by itself.
$stmt = $PDO->prepare(<<<_OUT
SELECT GROUP_CONCAT() ...
FROM data
GROUP BY colname) V;
_OUT
);
$stmt->execute();
$stmt->fetchAll();
Try to use Laravel approach: DB::select(DB::raw($sql));
SELECT GROUP_CONCAT(CONCAT("SELECT
^-- this quote must be escaped like this: \"
PHP thinks that your SQL string ends there.
Check the other quotes as well.
Edit: Other option might be to wrap the whole SQL to single quotes (') and then just escape those inside the query (by \')

PHP mysqli Prepared Statement with anonymous join

I am currently frustrated with preparing one statement - culprit is not preparing it properly, but staying inline with my current function to bin a query with a variable number of parameters. It works in all cases, but this query gives me an unsolveable problem (in one single query) for mysql:
SELECT studios.name,
studios.phone,
locations.zip_code,
locations.location_name,
addresses.street_name,
addresses.stree_nr,
persons.first_name,
persons.last_name,
p.distance_unit
* DEGREES(ACOS(COS(RADIANS(p.latpoint))
* COS(RADIANS(addresses.geo_lat))
* COS(RADIANS(p.longpoint) - RADIANS(addresses.geo_long ))
+ SIN(RADIANS(p.latpoint))
* SIN(RADIANS(addresses.geo_lat)))) AS distance
FROM studios
JOIN ( /* these are the query parameters */
SELECT ? AS latpoint, ? AS longpoint,
? AS radius, 111.045 AS distance_unit
) AS p ON 1=1
CROSS JOIN addresses
ON studios.address = addresses.id
CROSS JOIN locations
ON addresses.location = locations.id
CROSS JOIN persons
ON studios.owner = persons.id
CROSS JOIN studio_types
ON studios.studio_type = studio_types.id
WHERE addresses.geo_lat
BETWEEN p.latpoint - (p.radius / p.distance_unit)
AND p.latpoint + (p.radius / p.distance_unit)
AND addresses.geo_long
BETWEEN p.longpoint - (p.radius / (p.distance_unit * COS(RADIANS(p.latpoint))))
AND p.longpoint + (p.radius / (p.distance_unit * COS(RADIANS(p.latpoint))))
ORDER BY distance
LIMIT 25
The query itself is fully functional - here is a gist of it:
SELECT *
FROM studios
JOIN ( SELECT ? AS latpoint, ? AS longpoint,
? AS radius, 111.045 AS distance_unit
) AS p ON 1=1
now binding it is pretty straight forward:
$query = $this->conn->prepare($query);
if(!$query) throw new Exception($this->conn->error);
$query->bind_params("ddd", $val1, $val2, $val2);
$query->execute();
$query->get_result();
The error that occures by preparing already happens in the first line: Unknown column 'p.latpoint' in 'field list'
Now this is a huge problem. Going around that would probably work with preparing statements with SET #variable = ? - but thats not a preferred situation, as it would break up my current scheme of dispatching my requests.
Is there any oversight or is this simply not possible, due to undefined joins while preparing?
Edit: third method would involve replacing the join and placing values - this would make a very confusuing amount of bind parameters - which would be hard to maintain later.
So, I finally come around for anyone who seeks the same answer like me and I gotta extend where I left with it:
Imagine a class that does all the heavy lifting for you, minimizing issues provides you a simple interface between you and your database. There is a method that takes a statement string, type binding and like this:
protected function __dispatch($query, $types, $arguments) {
error_reporting(0);
$query = $this->conn->prepare($query);
if(!$query) throw new Exception($this->conn->error);
if($types !== "") call_user_func_array(array(&$query, 'bind_param'),
array_merge(array($types), $arguments));
$query->execute();
return $query->get_result();
}
Since placeholders cannot possibly any identifiers (thanks Jon), I can however bind SQL variables just fine. Then I am going to execute something like this:
SELECT *
FROM studios
JOIN ( SELECT #lat AS latpoint, #lon AS longpoint,
#dist AS radius, 111.045 AS distance_unit
) AS p ON 1=1
This gives the possibility to bind variables before - and then use it in the statement, just like this:
public function search_in_range($lat, $long, $distance) {
$this->__dispatch("SET #lat = ?", "d", array(&$lat));
$this->__dispatch("SET #lon = ?", "d", array(&$long));
$this->__dispatch("SET #dist = ?", "d", array(&$distance));
// And finally:
return $this->__dispatch($query, $types, [])->fetch_all();
}
This does the trick to create to have a very simple join with an anonymous set of data and use it in a more complicated statement later on.

How to correctly prepare parameters with DoctrineDBAL on the following query?

I need to get the percentage of each possible values in the field column, over the total value of my table.
I found two way to get my result in SQL:
SELECT m.field, sum(m.value) * 100 / t.total
FROM my_table AS m
CROSS JOIN (
SELECT SUM(value) AS total FROM
WHERE year = 2000) t
WHERE m.year = 2000
GROUP BY m.field, t.total
And
SELECT m.field, sum(m.value) * 100 / (SELECT SUM(value) AS total FROM WHERE year = 2000)
FROM my_table AS m
WHERE m.year = 2000
GROUP BY m.field
But both are nested queries, and I don't know how to prepare statments with the Doctrine's QueryBuilder into a nested queries.
Is there a way to do it?
I have been trying to do so using querybuilder and DQL with no success. As it seems, DQL doesn't allow operations with subqueries in SELECT. What I've achieved so far:
$subQuery = $em->createQueryBuilder('m')
->select("SUM(m.value)")
->where("m.year = 2000")
->getDQL();
The following query works though doesn't calculate the percentage:
$query = $em->createQueryBuilder('f')
->select("f.field")
->addSelect(sprintf('(%s) AS total', $subQuery))
->addSelect('(SUM(f.value)*100) AS percentage')
->where("f.year = 2000")
->groupBy("f.field")
->getQuery()
->getResult();
However, if you try to add the division in the select in order to get the percentage and you use the subquery, it simply doesn't work. Looks like the construction it's not allowed in DQL. I've tried with an alias and with the subquery directly and neither of them worked.
Doesn't work:
$query = $em->createQueryBuilder('f')
->select("f.field")
->addSelect(sprintf('(%s) AS total', $subQuery))
->addSelect('(SUM(f.value)*100)/total AS percentage')
->where("f.year = 2000")
->groupBy("f.field")
->getQuery()
->getResult();
Doesn't work either:
$query = $em->createQueryBuilder('f')
->select("f.field")
->addSelect(sprintf('(SUM(f.value)*100)/(%s) AS percentage', $subQuery))
->where("f.year = 2000")
->groupBy("f.field")
->getQuery()
->getResult();
I'd suggest using SQL directly (Doctrine allows it). Using native sql queries and mapping the results would do the trick. There is no disadvantage in doing so.
Documentation
If you find a way of doing it using queryBuilder or DQL, please let me know.
Hope it helps.
yeah! the solution is:
$qs = $this
->createQueryBuilder('h');
$d = $qs ->select($qs->expr()->count('h'));
$e = $d->getQuery()->getScalarResult();
$qs->addSelect('(COUNT(h.id)*100 / :t) AS percentage')->setParameter('t', $e);
$qs->addGroupBy(sprintf('h.%s', $type));
return $qs->getQuery()->getResult();

pdo prepared statements do not work, but an ordinary query does. What am I missing?

For some reason I cannot get this to work:
/**
* $_COOKIE['MyShoppingList'] is a serialized array with intergers.
*/
if($_COOKIE['MyShoppingList']){
foreach(unserialize($_COOKIE['MyShoppingList']) as $recipe){
$recipes_ids .= $recipe.',';
}
$sql_WHERE_NOT_IN = 'WHERE r.id NOT IN (:ids)';
}
$qry_recipes = $dbh->prepare('
SELECT r.id drink_id, r.name drink_name
FROM recipes r
'.$sql_WHERE_NOT_IN.'
');
if($_COOKIE['MyShoppingList']){
$qry_recipes->execute(array(':ids'=>rtrim($recipes_ids,','))); // I've verified that this is in fact a string with all the intergers sepparated with a comma.
} else {
$qry_recipes->execute();
}
This does work like a charm:
if($_COOKIE['MyShoppingList']){
/* the $recipes_id is the same as before */
$sql_WHERE_NOT_IN = 'WHERE r.id NOT IN ('.rtrim($recipes_ids,',').')';
}
$qry_recipes = $dbh->query('
SELECT r.id drink_id, r.name drink_name
FROM recipes r
'.$sql_WHERE_NOT_IN.'
');
The only difference is that the former is using prepared statements, and the latter is a pure query.
What happens is that it looks like the former, prepared, is not detecting the $recipes_ids-string..
Is there something about the $recipes_ids I'm overlooking?
rtrim(...) string (13) "12,1,2,3,9,10" // it's like this in both scenarios
I've tried bindParam() as well, but that resulted in this error message:
"Strict Standards: Only variables should be passed by reference"
I'm not sure what that means, but it might be telling me what I should be doing..
So please let me know..
Also; I've tried putting rtrim($recipes_ids,',') into a variable before sending it to the prepared query - but with no luck..
You cannot bind multiple values to a single named parameter in, for example, the IN() clause of an SQL statement.
Try this way:
/**
* $_COOKIE['MyShoppingList'] is a serialized array with intergers.
*/
$recipes_ids = array();
if($_COOKIE['MyShoppingList']){
foreach(unserialize($_COOKIE['MyShoppingList']) as $recipe){
$recipes_ids[] = $recipe;
}
$sql_WHERE_NOT_IN = 'WHERE r.id NOT IN (' . str_repeat('?, ', count($recipe_ids) - 1) . '?)';
}
$qry_recipes = $dbh->prepare('
SELECT r.id drink_id, r.name drink_name
FROM recipes r
'.$sql_WHERE_NOT_IN.'
');
if($_COOKIE['MyShoppingList']){
$qry_recipes->execute($recipe_ids);
} else {
$qry_recipes->execute();
}

Cumulative DQL with Doctrine

Im having a hard time working out a proper DQL to generate cumulative sum. I can do it in plain SQL but when it comes to DQL i cant get hold of it.
Here is how it looks in SQL:
SELECT s.name, p.date_short, p.nettobuy,
(select sum(pp.nettobuy) as sum from price pp where pp.stock_id = p.stock_id and p.broker_id = pp.broker_id and pp.date_short <= p.date_short) as cumulative_sum
FROM price p
left join stock s on p.stock_id = s.id
group by p.stock_id, p.date_short
order by p.stock_id, p.date_short
Thanks
Hey, I have check the documentation for Doctrine 1.2, and the way to create the query is (put attention on the alias):
$query = Doctrine_Query::create();
$query->addSelect('AVG(price) as price');
$query->addSelect('AVG(cost) as cost');
// as many addSelect() as you need
$query->from('my_table');
To output the SQL query created:
echo $query->getSqlQuery();
To execute the statement:
$product = $query->fetchOne();
And to access the retrieved data is:
echo $product->getPrice();
echo $product->getCost();
Read the rest of the documentation at Group By Clauses.
You just specify the sum in your select part of the DQL:
$query = Doctrine_Query::create()
->select('sum(amount)')
->from('some_table');
Check out this page in the Doctrine documentation for more info.

Categories