Need a SQL to Laravel Eloquent Translation - mysql

I need to translate the following SQL query into Laravel's Eloquent syntax:
SELECT a.*
FROM applications a
LEFT JOIN ratings r
ON a.id = r.application_id
AND r.admin_id = 1
WHERE r.admin_id IS NULL
The only way I've been able to get it to work is running the query using DB::raw, but I'm curious how it should look when using the Eloquent syntax. Ideally, I'd like to be able to migrate to any other database without a hiccup.

You can pass a closure to a join. You need to use DB::raw() as you're specifying a variable, rather than another database column to join on.
DB::table('applications')
->select('applications.*')
->leftJoin('ratings', function($join)
{
$join->on('ratings.application_id', '=', 'applications.id');
$join->on('ratings.admin_id', '=', DB::raw('1'));
})
->whereNull('ratings.admin_id')
->get()

Related

How to correctly use Query Builder Laravel in my situation?

I have a part of the SQL-code that needs to be converted correctly for Query Builder (Laravel).
select
p.*
from posts p, comments c
where c.post_id = p.post_id
group by p.post_id
order by avg(c.mark_first) desc
Can anyone halp me? :/
Using query builder you could write it as
$posts = DB::table('posts as p')
->join('comments as c', 'p.post_id', '=', 'c.post_id')
->groupBy('p.post_id')
->orderByRaw('avg(c.mark_first) desc')
->select('p.*')
->get();
Its better if you could add all the columns in groupby which you want in select part because above query will become invalid if mysql's full_group_by mode is enabled which is enabled by default in 5.7+

Laravel DB Join with asterisk and alias select

I am trying to get following query on Laravel:
SELECT
table1.* ,
table1.colb as aaa
FROM
table1
LEFT jOIN table2 on table2.cola = table1.colb
This is the Laravel DB Query code :
DB::table('table1')
->leftJoin('table2','table2.cola', '=', 'table1.colb')
->select('table1.* , table1.colb as aaa')
->get();
But it doesn't work and I get SQL Syntax error.
This is the SQL which Laravel is making using above code which is wrong :
select `table1`.* as `table1.colb`
from `table1`
left join `table2` on `table2`.`cola` = `table1`.`colb`
How can I fix this using laravel way?
You need to pass multiple parameters to the select method for each one to get prepared correctly:
DB::table('TABLE1')
->leftJoin('TABLE2','TABLE2.COLA', '=', 'TABLE1.COLB')
->select('TABLE1.*', 'TABLE1.COLB AS AAA') // <- 2 separate params here
->get();

How do I write an AREL UpdateManager query for MySQL that uses a subquery

I am tying to run an update query with a subquery against a MySQL database using ruby. I am using ruby 1.9.3 and rails 4.1.
The query I am trying to create is as below:
UPDATE `items`
SET
`items`.`status_id` = 12
WHERE
`items`.`id` IN (SELECT DISTINCT
`items`.`id`
FROM
`items`
LEFT OUTER JOIN
`statuses` ON `items`.`status_id` = `statuses`.`id`
LEFT OUTER JOIN
`resources` ON `items`.`resource_id` = `resources`.`id`
WHERE
`statuses`.`title` LIKE 'On Loan'
AND `items`.`duedate` < '2015-04-24'
AND `items`.`return_date` IS NULL
ORDER BY `items`.`duedate`)
I can produce this query in ruby using AREL with the code shown below:
# Declare Arel objects
i = Item.arel_table
s = Status.arel_table
r = Resource.arel_table
# This is the AREL query that returns the data
overdues = i.project(i[:id]).
join(s, Arel::Nodes::OuterJoin).on(i[:status_id].eq(s[:id])).
join(r, Arel::Nodes::OuterJoin).on(i[:resource_id].eq(r[:id])).
where(s[:title].matches("On Loan").
and(i[:duedate].lt(DateTime.now.to_date)).
and(i[:return_date].eq(nil))
).
order(i[:duedate])
# Note: You can't chain distinct, otherwise "overdues" becomes a string with the value "DISTINCT".
overdues.distinct
# This creates the update...
u = Arel::UpdateManager.new i.engine
u.table(i)
u.set([[i[:status_id], 10]]).where(i[:id].in(overdues))
This does not work and returns an error message:
ActiveRecord::StatementInvalid: Mysql2::Error: You can't specify target table 'items' for update in FROM clause:
I tried using AR "update_all" but it produces the same SQL and hence the same error.
Item.where(i[:id].in(overdues)).update_all(:status_id => (Status.find_by(:title => "Overdue").id))
Having done some research I have found that you cannot run a update with a subquery that references the table you want to update in MySQL. I have seen a number of posts on this site and the wider internet that detail work arounds.
One suggestion says that the update should use a join instead of a sub query. Having looked at the code behind the update manager it has no "join" so I can't do that.
Another says run this in two parts but I can't see how to because AREL and AciveRecord both chain actions.
The only way I can see of doing this is by aliasing the table and adding an additional select (see below). This isn't great but it would be useful to see if it is possible to do.
UPDATE `items`
SET `status_id` = 10
WHERE `items`.`id` IN (
SELECT x.id
FROM
(SELECT DISTINCT `items`.`id`
FROM `items`
LEFT OUTER JOIN `statuses` ON `items`.`status_id` = `statuses`.`id`
LEFT OUTER JOIN `resources` ON `items`.`resource_id` = `resources`.`id`
WHERE `statuses`.`title` LIKE 'On Loan'
AND `items`.`duedate` < '2015-04-24'
AND `items`.`return_date` IS NULL
ORDER BY `items`.`duedate`) x
);
If I can't get this to work I could adopt two other approaches:
1) I could just hard-code the SQL but I want to use ActiveRecord and reference the models to keep it database agnostic.
2) The other way is to return an instance of all the records and loop through them doing individual updates. This will have a performance issue but I can accept this because its a background job that won't be updating more than a handful of records each day.
Update
I have the AREL query below that produces the subquery in the format I need.
x = Arel::Table.new('x')
overdues = Item.select(x[:id]).from(
Item.select(Item.arel_table[:id]).where(
Status.arel_table[:title].matches("On Loan").and(
Item.arel_table[:duedate].lt(DateTime.now.to_date).and(
Item.arel_table[:return_date].eq(nil))
)
).joins(
Item.arel_table.join(Status.arel_table, Arel::Nodes::OuterJoin).on(
Item.arel_table[:status_id].eq(Status.arel_table[:id])
).join_sources
).joins(
Item.arel_table.join(Resource.arel_table, Arel::Nodes::OuterJoin).on(
Item.arel_table[:resource_id].eq(Resource.arel_table[:id])
).join_sources
).order(Item.arel_table[:duedate]).uniq.as('x')
)
Sadly it returns an error when I use it in my update statement.
TypeError: Cannot visit Item::ActiveRecord_Relation
Having revisited this question I am at the conclusion that it's not possible to do this because of a limitation with MySQL:
ActiveRecord::StatementInvalid: Mysql2::Error: You can't specify target table 'items' for update in FROM clause:
It should be possible to do with other databases (although I haven't tested that).
I could create a temporary table, which is the copy of the original table, reference that and then drop the temporary table like this post suggests:
http://richtextblog.blogspot.co.uk/2007/09/mysql-temporary-tables-and-rails.html. That seems a lot of overhead to do a simple subquery.
What I am going to do is find all the ID's and loop through them and update the records that way (using a simple find and update). This has an overhead but it should only be updating a handful of records each run (no more than 100). The update will be running as a scheduled job outside user working hours so it won't impact performance.
I still find it bizarre that in all other flavours of SQL I have never encountered this problem before. Still you live and learn.
UPDATE:
Since updating my version of MySQL the select statement now works. I had to take out the order by for it to work.
ORDER BY `items`.`duedate`
I am now using version: 5.7.19.

Symfony2 - subquery within a join error

I would like to use a subquery inside a join, however Symfony2 throws the following error:
Here is my failed attempt:
$query = $em->createQuery(
'SELECT
sc.id AS id,
u.id AS userId,
u.username AS username,
sc_count.upvotes
FROM
myBundle:SuggestedCar sc
INNER JOIN myBundle:User u WITH sc.user_id = u.id
INNER JOIN ( SELECT sc1.user_id, COUNT(sc1.id) AS upvotes
FROM myBundle:SuggestedCar sc1
GROUP BY sc1.user_id
) sc_count WITH u.id = sc_count.user_id'
);
Basically I'm just joining 3 tables and the third one has a count. The query worked when executing it inside the database.
How would it be possible to use a SELECT statement inside a join? Is it a good idea to use raw SQL at this point?
The $em->createQuery() function is expecting DQL as the parameter, not SQL. If you want to execute a raw SQL statement, the syntax is different. You can do it like this:
$sql = "SELECT * FROM my_table";
$em = $this->getDoctrine()->getManager();
$stmt = $em->getConnection()->prepare($sql);
$stmt->execute();
$result = $stmt->fetchAll();
for more on DQL or querying for objects, see Querying for Object. The biggest difference is DQL will return an object (based on your entity classes in Symfony). The method I posted above will just give you a PDO result. So if you execute raw SQL, don't expect to be able to use the result as an object.
If you want to use raw SQL and still have the result mapped to an object, you can look at the doctrine docs about Result set mapping. In my opinion, this is more work than necessary.

MySQL Error Code: 1349. View's SELECT contains a subquery in the FROM clause

I am trying to create a view but get the following error:
View's SELECT contains a subquery in the FROM clause
I am running the following command. I can't seem to figure out how to substitute the nested selects with joins. Any help would be much appreciated!
create view student_fee_basic as
select fsbc.*, ffp.name, ffp.amount 'fee'
from
(select sbc.*, ffc.name 'fname', ffc.id 'fid'
from (select s.admission_no, s.first_name, bc.id 'bid', bc.code, bc.name
from (select b.id, b.name, c.code
from batches b, courses c
where b.name = '2014-2015'
and b.course_id = c.id) bc
left join students s on bc.id = s.batch_id) sbc
left join finance_fee_categories ffc on ffc.batch_id = sbc.bid
where ffc.name = 'Basic Monthly') fsbc
left join finance_fee_particulars ffp on ffp.finance_fee_category_id = fsbc.fid;
MySQL does not support subqueries in views:
Subqueries cannot be used in the FROM clause of a view.
The documentation is here.
The easiest fix is to use a series of different views for each level.
You can probably rewrite this query to remove the subqueries. However, I find it very hard to help without explicit joins.
Version 5.7 supports it.
So one way to fix it is to migrate your database to newer version
upgrade to mysql-8 and your problem is solved.