Laravel Eloquent grab duplicates (2 or more results) - php

So I have the following query, which is working:
Expense::select('amount', 'date', DB::raw('COUNT(*) as `count`'))
->groupBy('amount', 'date')
->havingRaw('COUNT(*) > 1')
->get();
This is to grab all expenses that are potential duplicates (same amount and same date). However, I want to grab everything, not only the amount and date. The following is not working:
return Expense::select('name', 'amount', 'date', DB::raw('COUNT(*) as `count`'))
->groupBy('amount', 'date')
->havingRaw('COUNT(*) > 1')
->get();
This will give me this error:
SQLSTATE[42000]: Syntax error or access violation: 1055 Expression #1 of the SELECT list is not in the GROUP BY clause and contains a nonaggregated column
I have many more fields for an Expense that I want to list in my view, most importantly of course the name, but also the slug so I can link to each potential duplicate, etc.
Also, the second "issue", but not the most important one, is that with the above query, all expenses are returned in one collection. They are not grouped by date for example.
The most desirable would be to have the results somewhat like this. All potential duplicates should be grouped by the date so I can do something like this in my view:
#foreach($duplicates as $date => $expenses)
#foreach($expenses as $expense)
{{ $date }}
// List all expenses for that date.. etc.
Question 1: How would I make the above query work with all database fields, not only the date and amount?
Question 2: How would I be able to grab the potential duplicates and group them by date so I can loop over the dates and the expenses like in the above example?

Always handle column properly when doing grouping
e.i. you can specify all the fields you want to select in groupBy
// database query
$expenses = Expense::select(['amount', 'date', 'name', 'slug'])
->selectRaw('count(*) AS count')
->havingRaw('count > 1')
->groupBy(['amount', 'date', 'name', 'slug'])
->get();
// Another date grouping done in collection
return $expenses->groupBy('date');
or something like combing columns that belongs to your desired grouping
return Expense::select('date')
->selectRaw('group_concat(name) as names') // comma separated names belongs to the group
->selectRaw('group_concat(amount) as amounts') // comma separated amounts belongs to the group
->selectRaw('group_concat(slug) as slugs')// comma separated slugs belongs to the group
->selectRaw('count(*) AS count')
->havingRaw('count > 1')
->groupBy('date')
->get();
EDIT
If you only want to group them by date and still have the actual rows of duplicate, you can just add another select statement to count for date duplicate without grouping them by date in your query, and do the date grouping in collection.
e.i.
$expenses = Expense::select( ['amount', 'date', 'name', 'slug'] )
->selectRaw('(SELECT COUNT(date) FROM expenses t1 WHERE t1.date = expenses.date ) as duplicates')
->havingRaw('duplicates > 1')
->get();
return $expenses->groupBy('date');
Then the output should have another column duplicates which has the number of duplicate each date has and still has all the rows for duplicate dates

Related

Laravel OrderBy by related table column

I have a table (A) that has a One to Many relation with another table (B).
I want to query Table A and eager load Table B with the Table A results - but I also want to sort Table A by a value in Table B.
I have tried using OrderBy in the query and also trying SortBy on the resultant collection but cannot get the Table A data to be sorted by the value found in Table B.
Example of what I have tried:
$query = ModelA::with("ModelB"])->get()->sortByDesc('ModelB.sortValue');
Keep in mind, I am only interested in the LATEST record from Table B. So I need to query Table A and sort by a value in the LATEST records of Table B.
How can I achieve this?
EDIT:
The below (as suggested by #ljubadr) works pretty close, but the issue is that there are many record in Table B which means that it doesn't reliably sort as it doesn't seem to sortby the latest records in Table B. Can I have the join return ONLY the latest record for each ID?
$query = ModelA::select('TableA.*')
->join('TableB', 'TableA.id', '=', 'TableB.col_id')
->groupBy('TableA.id')->orderBy('TableB.sortCol', 'desc')
->with(['x'])
->get();
EDIT 2:
#Neku80 answer has gotten me closest but it seems to not sort the column with the greatest accuracy.. I'm sorting a Decimal column and for the most part it is in order but in some places the items are out of order..
$latestTableB = ModelB::select(['TableA_id', 'sortByColumnName'], DB::raw('MAX(created_at) as created_at'))
->groupBy('TableA_id');
$query = ModelA::select('TableA.*')
->joinSub($latestTableB, 'latest_TableB', function ($join) {
$join->on('TableA.id', '=', 'latest_TableB.TableA_id');
})
->orderBy('latest_TableB.sortByColumnName')
->get();
For example, the ordering is like:
0.0437
0.0389
0.0247 <-- -1
0.025 <-- +1
0.0127
When I delete all rows except for the 'latest' rows, then it orders correctly, so it still must be ordering with old data...
I have found a solution:
ModelA::select('TableA.*', 'TableB.sortByCol as sortByCol')
->leftJoin('TableB', function ($query) {
$query->on('TableB.TableA_id', '=', 'TableA.id')
->whereRaw('TableB.id IN (select MAX(a2.id) from TableB as a2 join TableA as u2 on u2.id = a2.TableA_id group by u2.id)');
})
->orderBy('TableB.sortByCol')
->get();
Another alternative to order is like this:
$users = User::orderBy(
Company::select('name')
->whereColumn('companies.user_id', 'users.id'),
'asc'
)->get();
Here we are ordering in asc order by company name field.
In this article it is explained in detail.
You can simply execute a left join query:
ModelA::query()->leftJoin('model_b_table', 'model_a_table.primary_key', '=', 'model_b_table.foreign_key')->orderBy('model_a_table.target_column')->get();
This should work if you only need TableB's ID and created_at columns:
$latestTableB = ModelB::select('TableA_id', DB::raw('MAX(created_at) as created_at'))
->groupBy('TableA_id');
$query = ModelA::select('TableA.*')
->joinSub($latestTableB, 'latest_TableB', function ($join) {
$join->on('TableA.id', '=', 'latest_TableB.TableA_id');
})
->orderBy('latest_TableB.created_at')
->get();

SQL query in Laravel DB::raw() to order column and get first result

I'm trying to order a specific column in my DB::raw() select statement, I need to order the items for each day and get the first one for each day, so I'd end up with 5 results for 5 months but my error is
Illuminate\Database\QueryException with message 'SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax
What am I missing?
GoogleAnalytics::where('event_category', 'Category')
->where('event_action', 'Action')
->whereDate('period_from', '>=', '2021-06-08')
->whereDate('period_to', '<=', '2021-06-08')
->select(DB::raw('DATE_FORMAT(created_at, "%Y-%m") as created_at'), DB::raw('FIRST(event_count ORDER BY created_at DESC ) as sessions'))
->groupByRaw('DATE_FORMAT(created_at, "%Y-%m")')
->orderBy('created_at', 'desc')
->get();
Some more context here, each day might have 100 entries, so there could be 3,000 entries per month, I need to get just a single entry in each day so I end up with 30 entries, and then add up those 30 result to produce a single result for that one month, so I'd end up with 5 items in my returned collection, but right now I can't seem to get DB::raw('FIRST(event_count ORDER BY created_at DESC ) as sessions') to grab the column, order it and just get the first result.
I don't think FIRST is a function at all, hence the error. If we break your problems into two you want:
One row for each day with the event_count column present. As per your comment above the column increments and you want the last value, thus you want the max value per day.
Sum the values of the event_count column, grouping by the month and year
1:
A select statement for this would look like
select date(created_at), max(event_count) from google_analytics group by date(created_at) where ...
Using the Eloquent builder it would look something like this:
$query = GoogleAnalytics::select(
DB::raw('date(created_at) as created_at'),
DB::raw('max(event_count) as sessions')
)
->where('event_category', 'Category')
->where('event_action', 'Action')
->whereDate('period_from', '>=', '2021-06-08')
->whereDate('period_to', '<=', '2021-06-08')
->groupByRaw('date(created_at)');
Note:
your where clause only looks at one date (2021-06-08)
this query will not return complete GoogleAnalytics models
2:
We can use the above $query to group the results per month:
$result = DB::table($query, 'per_day')->select(
DB::raw('date_format(created_at, "%Y-%m") as created_at'),
DB::raw('sum(sessions) as sessions')
)
->groupByRaw('date_format(created_at, "%Y-%m")')
->orderByRaw('date_format(created_at, "%Y-%m") desc')
->get();

How to Get All Records and Group Them by create_at (Date Only)

I have a table named shopping_apps, and that table has different entries on different dates. I want to have all records but group all the records on a date basis (not the time part). Suppose I have 20 entries on date1, 12 entries on date2. I want to have only two values extracted from the database: date1 and date2. The controller is below, and Shopping_app is my model name.
<?php
// Use substr() to retrieve date part 10 chars in length.
$shoppingApp = Shopping_app::orderBy('created_at', 'DESC')
->groupBy(substr('created_at', 0, 10))
->get();
ERROR:
SQLSTATE[42000]: Syntax error or access violation: 1055
'laravelpro4.shopping_apps.id' isn't in GROUP BY (SQL: select * from
shopping_apps group by created_at order by created_at desc)
$shoppingApp = Shopping_app::where('created_at', '>=', \Carbon\Carbon::now->subMonth())
->groupBy(DB::raw('Date(created_at)'))
->orderBy('created_at', 'DESC')->get();
try this maybe it will help you
So with the help of #manish I get the correct answer (what I was trying to get)
Error solved by
Adding false
In config\database.php --> "mysql" array
Set 'strict' => false to disable all.
Final code :
$shoppingApp = Shopping_app::where('created_at', '>=', \Carbon\Carbon::now()->subMonth())
->groupBy(DB::raw('Date(created_at)'))
->orderBy('created_at', 'DESC')->get();

Group by month or week with Laravel eloquent

I have a project which has management of my investments, that is, it takes my daily statement and saves it in my project so that I can see how much it is yielding, in summary I have a table that are these values daily, I can already generate the information separated by day and it works perfectly (in fact it is a little more complex than this what I do but the basis is this);
But now it's getting bad to see when I get a very large date range for example 1 year, it lists every day, I wanted a way to group by month or week,
Thanks in advance for any help.
$rows = AtivosExtrato::select('titulo_id', DB::raw('SUM(valor_bruto_atual) AS valor'), 'data_imports.data_import as created_at')
->join('titulos','titulo_id', '=', 'titulos.id' )
->join('representantes','representante_id', '=', 'representantes.id' )
->join('data_imports','data_import_id', '=', 'data_imports.id' )
->where('user_id', Auth::user()->id)
->whereBetween('data_imports.data_import', [$request->input('start_date'), $request->input('end_date')])
->groupBy('titulos.nome_titulo')
->groupBy('data_imports.data_import')
->orderBy('data_import')
->orderBy('titulos.nome_titulo')
->get();
Briefly explaining what each eloquent information is:
AtivosExtrato: model where the daily income information is;
1) join: foreign table for the names of the titles
2) join: foreign table for the broker's name
3) join: table that saves the date of the import and relates to the id in the asset tableExtrato, it has a function to reduce the weight in the time of the searches and to gain performance.
where: limiting to the user in question
WhereBetween: limiting to date range
1) groupBy: Grouping by titles
2) groupBy: grouping by date of import
Table structure:
ativos_extrato
ativos_extratos foreign key
data_imports
Solution:
$rows = AtivosExtrato::select(
'titulo_id',
DB::raw('SUM(valor_bruto_atual) AS valor'),
'data_imports.data_import as created_at',
DB::raw('WEEK(data_imports.data_import) AS weeknumber')
)
->join('titulos','titulo_id', '=', 'titulos.id' )
->join('representantes','representante_id', '=', 'representantes.id' )
->join('data_imports','data_import_id', '=', 'data_imports.id' )
->where('user_id', Auth::user()->id)
->whereIn('ativos_extratos.data_import_id',
DataImport::Select(DB::raw('max(ID)'))
->whereBetween('data_import', [$request->input('start_date'), $request->input('end_date')])
->groupBy(db::raw('Week(data_import)')) )
->whereBetween('data_imports.data_import', [$request->input('start_date'), $request->input('end_date')])
->groupBy('titulos.nome_titulo')
->groupBy('weeknumber')
->orderBy('data_import')
->orderBy('titulos.nome_titulo')
->get();
Add DB::raw(WEEK(data_imports.data_import) AS weeknumber) and then replace ->groupBy('data_imports.data_import') with ->groupBy('weeknumber') and the same with MONTH() function if you want to group by month: add another select column DB::raw(MONTH(data_imports.data_import) AS monthnumber) and replace ->groupBy('data_imports.data_import') with ->groupBy('monthnumber'). So the whole Eloquent query with week grouping would be:
$rows = AtivosExtrato::select('titulo_id', DB::raw('SUM(valor_bruto_atual) AS valor'), 'data_imports.data_import as created_at', DB::raw('WEEK(data_imports.data_import) AS weeknumber'))
->join('titulos','titulo_id', '=', 'titulos.id' )
->join('representantes','representante_id', '=', 'representantes.id' )
->join('data_imports','data_import_id', '=', 'data_imports.id' )
->where('user_id', Auth::user()->id)
->whereBetween('data_imports.data_import', [$request->input('start_date'), $request->input('end_date')])
->groupBy('titulos.nome_titulo')
->groupBy('weeknumber')
->orderBy('data_import')
->orderBy('titulos.nome_titulo')
->get();

Laravel join and select from multiple tables

So I have Laravel 5.2 and in my SQL database I have several tables:
books with fields title, author and year,
journals with fields title, year and price
newspapers with fields title, town, year
I need to get a list of all titles from all three tables, where the year is 1994. I've tried to do the following
$titles = DB::table('books')->where('books.year', 1994)->leftjoin('journals as journals', 'books.year', '=', 'journals.year')->leftjoin('newspapers as newspapers', 'books.year', '=', 'newspapers.year')->select('books.title', 'journals.title', 'newspapers.title')->get();
But with this query I get entries full of nulls, and only newspapers are filled in. What am I doing wrong?
In this case (if the tables are not related) you should use union, no join.
$books = DB::table('books')->select('title')->where('year', 1994);
$journals = DB::table('journals')->select('title')->where('year', 1994);
$titles = DB::table('newspapers')->select('title')->where('year', 1994)->union($books)->union($journals)->get();
You need to specify year column :
$titles = DB::table('books')
->where('books.year', 1994)
->leftjoin('journals as journals', 'books.year', '=', 'journals.year')
->leftjoin('newspapers as newspapers', 'books.year', '=', 'newspapers.year')
->select('books.title', 'journals.title', 'newspapers.title')
->get();

Categories