Table-less select using Arel - arel

How would one write the following SQL query using only Arel functions?
select array_to_string(array(select name from tags, taggings where tags.id=taggings.id), ', ')
Caveat: This is an SQL fragment that is supposed to be part of a larger correlated sub-query - it may not make sense in isolation.

Arel (atleast 3-0-stable) doesn't have the greatest support for named function, so it looks pretty ugly. The code below generates a version using a join
tags_tbl = Arel::Table.new("tags")
taggings_tbl = Arel::Table.new("taggings")
arel = Arel::Nodes::NamedFunction.new(:array_to_string, [
Arel::Nodes::NamedFunction.new(:array, [
Arel.sql(
tags_tbl.project(
tags_tbl[:name]
).join(
taggings_tbl
).on(
tags_tbl[:id].eq(taggings_tbl[:id])
).to_sql
)
])
])
arel.to_sql # => array_to_string(array(SELECT "tags"."name" FROM "tags" INNER JOIN "taggings" ON "tags"."id" = "taggings"."id"))

Related

Laravel (Lumen) Eloquent querying with WHERE on a relation (Multi-level)

I have the following DB tables:
Purchase
-id
-workplace_id
Workplace
-id
-client_id
(and obviously some more fields, but for the example these are all the needed ones).
I would like to make a query like this:
SELECT * FROM
purchase
INNER JOIN workplace ON (purchase.workplace_id = workplace.id)
WHERE
(workplace.client_id = 1)
I'm trying to make this work with the Eloquent models, but I can't figure out how to filter on a joined table.
I tried:
$purchases = Purchase::query()
-> workplace()
-> where('client_id', '=', Auth::user() -> client_id)
-> get();
But apparently workplace() is undefined for some reason.
My Purchase.php model file looks like this:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Purchase extends Model
{
public function workplace(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{
return $this->belongsTo(Workplace::class);
}
}
Any pointers on how to make this simple select work?
Thanks!
====EDIT=====
I found a possible solution:
$purchases = Purchase::with('workplace')
-> whereHas('workplace', function($q) {
return $q -> where('client_id', '=', Auth::user() -> client_id);
})
-> get();
But this generates an SQL that seems more complicated and is probably also slower:
select * from `purchases` where exists (select * from `workplaces` where `purchases`.`workplace_id` =
`workplaces`.`id` and `client_id` = ? and `workplaces`.`deleted_at` is null)
So I'm still looking for better alternatives
If you want to do a join, you need to build it with the join method, you can't use a relationship. See here for the docs:
https://laravel.com/docs/9.x/queries#joins
So you should be able to do something like this:
$purchases = Purchase::query()
->join('workplace', 'purchase.workplace_id', '=', 'workplace.id')
->where('workplace.client_id', '=', Auth::user()->client_id)
->get();
The generated SQL that you show in your edit is a sub-query, and you may still want to consider that. Sure, sub-queries are often slower than joins, but unless you're dealing with a massive dataset the performance difference might be negligible, and it allows you to use native Eloquent relationships.
See here for a discussion on this: https://stackoverflow.com/a/2577188/660694

Use "Query Builder" to create a function allowing to take the average of values - Symfony

I need help concerning the use of "Query Builder" in Symfony.
I would like to retrieve the values ​​of an attribute (Note) from one of my tables (Avis) in my database. After that, I would like to average all of his scores for display on my site.
For now I have the SQL query which achieves what I want :
SELECT AVG(avis.note) AS notetotal FROM avis
But afterwards, I don't understand what to do, or at least how "Query Builder" works
With the QueryBuilder you can build your queries step by step in a programmatically way, but its actualy only build the DQL wich you can get with $queryBuilder->getDql(). There are some cases where its easier to use the QueryBuilder instead of concatinate a long DQL query string.
There are two possible ways to build the query with the QueryBuilder. But for most cases its recommended to use plain DQL where ever you can, as its more readable.
First with the Expr Class
$queryBuilder = $this->getDoctrine()->getManager()->createQueryBuilder('a');
$queryBuilder->select($queryBuilder->expr()->avg('a.note'))
->from(Avis::class, 'a');
$avg = $queryBuilder->getQuery()->getSingleResult();
// $avg = [1 => (int) AVG]
or without Expr Class
$queryBuilder = $this->getDoctrine()->getManager()->createQueryBuilder('a');
$queryBuilder->select('AVG(a.note) as notetotal')
->from(Avis::class, 'a');
$avg = $queryBuilder->getQuery()->getSingleResult();
// $avg = ['notetotal' => (int) AVG]
The same in DQL
$dql = 'SELECT AVG(a.note) AS notetotal FROM ' . Avis::class . ' a';
$avg = $this->getDoctrine()->getManager()->createQuery($dql)->execute();
// $avg = [0 => ['notetotal' => (int) AVG]]

Use `AND` in Laravel Query Builder, syntax error (orOn)

I have this sql (MariaDB) query, it works ok:
SELECT
admin_combustibles.nombre,
admin_combustibles.id_combustible,
admin_combustible_unidades.nombre AS unidades,
admin_tipo_combustibles.nombre AS tipo_combustible,
admin_indice_combustibles.valor_combustible
FROM
admin_combustibles
INNER JOIN admin_combustible_unidades ON admin_combustibles.combustible_unidades_id = admin_combustible_unidades.id_combustible_unidad
INNER JOIN admin_tipo_combustibles ON admin_combustibles.tipo_combustible_id = admin_tipo_combustibles.id_tipo_combustible
LEFT JOIN admin_indice_combustibles ON admin_combustibles.id_combustible = admin_indice_combustibles.combustible_id
AND admin_indice_combustibles.anio = 1992
AND admin_indice_combustibles.mes = 6
ORDER BY
admin_combustibles.nombre
But when I try to make it through Laravel, I have some error because Laravel put some on the number 1992, in that way the query doesn't work.
Plus, I don't know how to do the AND on Laravel, I only have orOn but it doesn't work:
$datos = AdminCombustible::select([
'admin_combustibles.nombre',
'admin_combustibles.id_combustible',
'admin_combustible_unidades.nombre AS unidades',
'admin_tipo_combustibles.nombre AS tipo_combustible',
'admin_indice_combustibles.valor_combustible'])
->join('admin_combustible_unidades', 'admin_combustibles.combustible_unidades_id', '=', 'admin_combustible_unidades.id_combustible_unidad')
->join('admin_tipo_combustibles', 'admin_combustibles.tipo_combustible_id', '=', 'admin_tipo_combustibles.id_tipo_combustible')
->leftJoin('admin_indice_combustibles', function ($join) {
$join->on('admin_indice_combustibles.combustible_id', '=', 'admin_combustibles.id_combustible')->orOn('admin_indice_combustibles.anio', '=', 1992);
})
->get();
dd($datos);
I got an sql syntax error.
Any help on how to make that query with query builder of Laravel? (I put the entire query as DB::raw and it works, but I don't want to use raw() )
I think there might be two problems here. Since you are using and in your original query, you might be needing to use or instead of orOn on your join.
Also you will need to use DB::raw() on the parameters, otherwise Laravel will consider them columns....
$join->on('admin_indice_combustibles.combustible_id', '=', 'admin_combustibles.id_combustible')->on('admin_indice_combustibles.anio', '=', DB::raw('1992'));

Can select and from be parametrized in Doctrine's QueryBuilder?

According to Doctrine's documentation it is possible to bind parameters to a query. Example:
$qb->select('u')
->from('User u')
->where('u.id = ?1')
->orderBy('u.name', 'ASC')
->setParameter(1, 100);
Derived from this question I would like to know if it is possible to parametrize the select and the from statement as well? Like
$qb->select('?1')
->from('?2 u')
->where('u.id = 2')
->orderBy('u.name', 'ASC')
->setParameters(array(1 => 'mytable', 2 => 'mycolumn'));
I didn't manage to do so, but maybe I just did not know the proper way. Does anyone?
This is the way prepared statements work with PDO. The query and the statement are being send seperately. This allows your database to calculate the optimal query path for your query. The query path then makes use of the parameters to get the right results. The query path will try to optimize speed for the next time you make the same query. So for select and from you just have to do select($select). Note that the optimization will be lost of you put a new select each time.
Update:
This is a related answer: https://stackoverflow.com/a/182353/1833322
This is an example of how it should look in DQL:
$query = $em->createQuery('SELECT x FROM '.$mappingPlusEntity.' x WHERE x.id = ?1');
$query->setParameter(1, 321);

Binding Variables in Ruby

I currently have a table that is listed as follows:
projects = Project.find(:all, :conditions => [conditions + "AND (name LIKE ? OR description LIKE ?)", "%#{params[:query]}%", "%#{params[:query]}%"])
where
conditions = Project.in_used_projects(:alias => "projects")
However I need to include a 3rd variable which is not from the Project table but from a Tags table. The column I need is Tag - > Names. Is there anyway I can bind variables from another table in Ruby? The Project.find(all) automatically passes the SELECT * FROM Project into MYSQL. Someone has suggested using a join function, but I'm not sure how this would work. Any ideas?
EDIT 1
I have tried the suggested answer of using
projects = Project.find(:all, :joins => "tags", :conditions => [conditions + "AND (projects.name LIKE ? OR description LIKE ? OR tags.name LIKE ?", ["%#{params[:query]}%" * 3]].flatten)
but now I am getting another error
Mysql::Error: Unknown table 'projects': SELECTprojects.* FROMprojectstags WHERE ((projects.status = 2)AND (projects.name LIKE '%%' OR projects.description LIKE '%%' OR tags.name LIKE '%%')
Very strange considering the projects table exists. Why isn't Ruby recognizing it now that i've included another table?
Try this out for size:
projects = Project.find(:all, :joins => "tags", :conditions => [conditions + "AND (projects.name LIKE ? OR description LIKE ? OR tags.name LIKE ?", ["%#{params[:query]}%" * 3]].flatten)
The :joins option tells Active Record to perform an SQL join onto the tags table, which will allow you to perform queries on the tags column. Also take note in this example how I've added the projects. prefix to your original name column. This is so your database doesn't get confused what name column you want; the one from the projects or the tags table.
Do you have any relationship between projects and tags? If the reason for your query is that there is, it should be reflected in the model (for ex with a HABTM), which would also enable you to easily filter based on that relationship without the need to have complex SQL queries crafted.
Turns out that it was just a simple syntax error
projects = Project.find(:all, :joins=>:tags, :conditions => [conditions + "AN rojects.name LIKE ? OR projects.description LIKE ? OR tags.name LIKE ?)", "%# ams[:query]}%", "%#{params[:query]}%", "%#{params[:query]}%"])
is the correct syntax. The :joins needs to be followed by :tags, not by "tags"
Thanks again for everyone's help