How to use Postgresql DISTINCT ON in Laravel Eloquent - sql

I am using DISTINCT ON to fetch latest rows from two joined tables. In my case a subscriber belongs to many categories and I need to fetch the latest category a subscriber belongs to. I've got it working in raw query but I need to recreate it using Eloquent.
SELECT DISTINCT ON (subscribers.id) subscribers.*,
subscriber_categories.*
FROM subscribers
LEFT JOIN subscriber_categories
ON subscribers.id = subscriber_categories.subscriber_id
ORDER BY subscribers.id, subscriber_categories.created_at DESC;
I have tried the following but not working. It's fetching all records from both tables:
Subscribers::
->join('subscriber_categories', 'subscribers.id', '=', 'subscriber_categories.subscriber_id')
->distinct('subscribers.id')
->latest('subscribers.id', subscriber_categories.created_at)
->paginate(7);

I finally ended up doing this. (totally abandoned DISTINCT ON):
$subscribers = \DB::table('subscribers AS t1')
->leftJoin(\DB::raw('(SELECT * FROM subscriber_categories A WHERE id in (SELECT MAX(id) FROM subscriber_categories group by subscriber_id)) AS t2'), function($join) {
$join->on('t1.id', '=', 't2.subscriber_id');
})->select('t1.id', 't1.s_phonenumber', 't1.timesubscribed', 't2.category')->latest('t1.created_at')->paginate(7);

I had the same issue and ended up writting a laravel extension to handle it. You can install it using composer, then it just works!
https://packagist.org/packages/datajoe/postgresql-distinct-on
This is what it would look like in your case:
Subscribers::
->join('subscriber_categories', 'subscribers.id', '=', 'subscriber_categories.subscriber_id')
->distinctOn('subscribers.id')
->latest('subscribers.id', subscriber_categories.created_at)
->paginate(7);

Related

how to get total sum of two tables and group responses with eloquent Laravel?

I am trying to create a query using eloquent where I need to know the total points summed by id, username and session_id.
but the result I get is not correct.
my code
$query = DB::table('advisors')
->select('advisors.id','advisors.nombre_comercial','session_details.session_id',
DB::raw('SUM(session_details.spent_points + template_sales.price_points) AS suma_total'))
->join('consultancy_requests','advisors.id','=','consultancy_requests.advisor_id')
->whereBetween('consultancy_requests.created_at',[$from,$to])
->join('receipts','consultancy_requests.id','=','receipts.session_id')
->where('receipts.status',NULL)
->whereBetween('receipts.created_at',[$from,$to])
->join('session_details','consultancy_requests.consultancy_id','=','session_details.session_id')
->whereBetween('session_details.created_at',[$from,$to])
->join('template_sales','session_details.session_id','=','template_sales.session_id')
->whereBetween('template_sales.created_at',[$from,$to])
->groupBy('advisors.id','advisors.nombre_comercial','session_details.session_id')
->get();
code responses
session_details table
template_sales table
this is the correct answer i want to get.
There are a couple of mistakes in your query that I noticed. For instance you don't need to SUM (session_details.spent_points + template_sales.price_points) because this is already performing the addition.
Instead of pointing out all these, let's break your problem down into smaller pieces; when query seems complicated it would be a good idea to break it down for better understanding. There seems to be a couple of tables but I will base my answer on the two tables provided, and that should give you a starting point.
Essentially, what you want is,
Get the sum of spent_points per session_id; so you need to group by session_id and sum(spent_points)
$sumSpentPointsQuery = DB::table('session_details')
->select('session_id', DB::raw('SUM(spent_points) as sum_spent_points'))
->groupBy('session_id');
Get the sum of price_points per session_id; so you need to group by session_id and sum(price_points)
$sumPricePointsQuery = DB::table('template_sales')
->select('session_id', DB::raw('SUM(price_points) as sum_price_points'))
->groupBy('session_id');
Now we need to get the addition of sum_spent_points and sum_price_points. This time our tables would be the results we got from the sub queries. So we can work with Laravel's fromSub and joinSub to get the result we want.
DB::query()
->select('ssp.session_id as session_id', DB::raw('sum_spent_points + sum_price_points as suma_total') )
->fromSub($sumSpentPointsQuery, 'ssp')
->joinSub($sumPricePointsQuery, 'spp', function($join){
$join->on('ssp.session_id', '=', 'spp.session_id');
})->get();
This query should produce the sql that represents this:
select ssp.session_id as session_id, (sum_spent_points + sum_price_points) as suma_total
from
(select session_id, sum(spent_points) as sum_spent_points
from session_details group by session_id) ssp
inner join
(select session_id, sum(price_points) as sum_price_points
from template_sales group by session_id) spp
on ssp.session_id = spp.session_id ;
Hope this kicks you in the right direction.

Laravel sum query using join and distinct

I have 2 tables to query using join, namely loans and amortizations table.
Loans table has many amortizations, so loans.id is a reference to amortizations.loan_id in amortizations table.
Loan id:3 exist twice in amortizations table, each with a value=10,800.
Loan id:5 also exist twice in amortizations table, each with a value=11,100 and the same goes with Loan id:11 that also exist twice in amortizations table each with a value=5400.
When you add this up, the total is 54,600. But I wanted the ids to be distinct so that the values won't add twice. So the total would be equal to 27,300. My existing query below returns a 54,600 value instead of 27,300 even using distinct() in Laravel. How would I achieve this?
Controller query
$oneToThirtyDue = Amortization::distinct('amortizations.loan_id')
->join('loans', 'loans.id', '=', 'amortizations.loan_id')
->select('loans.loan_type',\DB::raw('SUM(loans.loan_balance) as total_val'))
->where('amortizations.payment_status',0)
->where('amortizations.schedule', '<', date('Y-m-d'))
->where('amortizations.past_due', '<=', 30)
->groupBy('loans.loan_type')
->orderBy('loans.loan_type')
->get();
Please help. Thanks a lot.
$idsDue = Amortization::distinct()
->select('loan_id')
->where('payment_status',0)
->where('schedule', '<', date('Y-m-d'))
->where('past_due', '<=', 30)
->get();
$oneToThirtyDue = Loan::select('loan_type',\DB::raw('SUM(loan_balance) as total_val'))
->whereIn('id',$idsDue)
->groupBy('loan_type')
->orderBy('loan_type')
->get();
I believe this problem is that the max will be calculated for all rows in the group by before the distinct happens. Meanwhile distinct will run on the whole row, after the max so i does not help.
Therefor i believe a sub query can solve most of your troubles. The approach is to cut down the duplicate in the Amortization before joining. This will make the join be a 1 to 1, instead of it sometimes being 2-1 when you have duplicates. Reversing the join for ease and using a group by for avoiding duplicates and getting extra columns with max as they are not in the group by. Got a similar query working on local testing on some test tables, so this should be possible to achieve.
$subQuery = Amortization::select(
'loan_id',
DB::raw('max(amortizations.id) as id'),
DB::raw('max(amortizations.payment_status) as payment_status'),
DB::raw('max(amortizations.schedule) as schedule'),
DB::raw('max(amortizations.past_due) as past_due')
)->groupBy('loan_id');
Loan::join(DB::raw("($subQuery->toSql()) as amortizations"), 'amortizations.loan_id', '=', 'loans.id')
->select('loans.loan_type',\DB::raw('SUM(loans.loan_balance) as total_val'))
->where('amortizations.payment_status',0)
->where('amortizations.schedule', '<', date('Y-m-d'))
->where('amortizations.past_due', '<=', 30)
->orderBy('loans.loan_type');
This has to be done with a DB::raw, but i can not see any better approach as of now, sub queries outside of select statements is a grey area in Laravel.

How to create a special query with doctrine?

I try to resolve my problem since two days but I my code fails... Do you know how I can conert my sql query in doctrine ?
Sql query (the query work fine in phpmyadmin) :
SELECT distinct ms.ytId, ms.title FROM(
SELECT l.log_yt_id AS ytId, d.download_title AS title
FROM t_log l LEFT JOIN t_download d ON d.download_yt_id = l.log_yt_id WHERE l.log_creator = 'n' ORDER BY l.log_id DESC LIMIT 100
) ms
Thanks you all for your help !!
Best regards,
You can use doctrine dql.
First:
Check this page:
http://docs.doctrine-project.org/en/2.1/reference/dql-doctrine-query-language.html
Then remember:
If you want to use mysql functions like "RAND" etc. you have to define them in your config file after install a doctrine extensions bundle.

Rails query - latest Post for each day

A User has_many Posts. I want to retrieve the latest Post for each day (using created_at), ignoring other posts that may have been written earlier. Another way to pose this question might to ask for a each top salary earning employee by department - same thing I think.
How do I write this query in Rails (4.0 preferably)? I think it has something to do with group and maximum but I can't seem to get it. Is there a way to do it without resorting to SQL?
To clarify, what I'd like returned is an array of post objects that are the last ones written on their respective date.
Thanks!
Something like this. You can convert this to AREL syntax as needed:
SELECT posts.created_at, *
FROM posts
INNER JOIN (
SELECT MAX(created_at) AS max_order_date FROM posts
GROUP BY DATE(posts.created_at)
) AS last_postings ON last_postings.max_order_date = posts.created_at
ORDER BY DATE(created_at) DESC
LIMIT 10
AREL syntax might be:
join_sql = <<-SQL
INNER JOIN (
SELECT MAX(created_at) AS max_order_date FROM posts
GROUP BY DATE(posts.created_at)
) AS last_postings ON last_postings.max_order_date = posts.created_at
SQL
Post.joins(join_sql).order('DATE(created_at) DESC')
Remove the LIMIT as it suits you.
It's not very clean, but this works in Rails 3 (taken from a Book model in my case) using PostgreSQL syntax for truncating the created_at to the date:
max_created_at_list = Book.select("max(created_at) as created_at").group("date_trunc('day',created_at)")
last_books = Book.where(:created_at => max_created_at_list)
... or just:
Book.where(:created_at =>Book.select("max(created_at) as created_at").group("date_trunc('day',created_at)"))
You'd want an index on created_at for large data sets, and either created_at to be constrained to not null at the database level or an "is not null" predicate if the RDBMS you use does not index nulls (eg. Oracle)
Try this
Post.select("user_id, max(created_at) as created_at").group(:user_id)

Group by query in Rails 3

I have the (working) code
counts = Registration.select('regulator_id').group('regulator_id').count
#regulators.each {|r| r.registration_count=counts[r.id]}
which allows me to show how many Registrations there are per Regulator. The query it generates is:
SELECT COUNT("registrations"."regulator_id") AS count_regulator_id, regulator_id AS regulator_id FROM "registrations" GROUP BY regulator_id
I would like to restrict my count to those registrations from the last scrape only, with a query like:
select
regulator_id, count(*)
from
registrations inner join
regulators on regulators.id = registrations.regulator_id
where
registrations.updated_at > regulators.last_scrape_start
group by
regulator_id
but I cannot get the syntax to work either using arel or find_by_sql. I am sure this is simple when you know the answer but it has cost me ages so far.
Thanks in advance.
Just add 'joins' and 'where'
Registration.joins(:regulator).where('registrations.updated_at > regulators.last_scrape_start').select('regulators.id').group('regulators.id').count