Why ->latest method in model method read all rows from related table? - laravel

In laravel 9 I got last value in related CurrencyHistory table
$currencies = Currency
::getByActive(true)
->withCount('currencyHistories')
->with('latestCurrencyHistory')
->orderBy('ordering', 'asc')
->get();
In model app/Models/Currency.php I have :
public function latestCurrencyHistory()
{
return $this->hasOne('App\Models\CurrencyHistory')->latest();
}
But checking generated sql I see lines like :
SELECT *
FROM `currency_histories`
WHERE `currency_histories`.`currency_id` in (8, 13, 16, 19, 27, 30)
ORDER BY `created_at` desc
I suppose this code is raised by latestCurrencyHistory method and wonder can
I set some limit 1 condition here, as resulting data are too big.
Thanks!

Query is correct. As you eager load your relation for the collection of currencies using with method, you load currency_histories for all of your Currency models in collection.
If you dump the result, you will have currencies with IDs: 8, 13, 16, 19, 27, 30 and one latestCurrencyHistory (if present) for each.

Related

Query using whereIn() not working to generate collection

I have this table store_photos and I am trying to get the photos with complete raffle_date_id upto 12 and then group them by user_id. The query works but then it still generate collection even if its not complete upto 12. How can I achieve this using whereIn() or other similar eloquent?
public function collection(): Collection
{
$months = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
return StorePhoto::with('user.location')
->whereIn('raffle_date_id', $months)
->groupBy('user_id')
->get();
}
You can try constructing the RAW SQL query, to find what you are trying to achieve.
Then you can slowly build your way in Laravel.
You can try to find those users who have count of 12 (i.e. have 12 records = have 12 months). Then you can get only those users.
SELECT user_id FROM store_photos AS sp
GROUP BY sp.user_id
HAVING COUNT(sp.user_id) = 12;
Then in Laravel you can
DB::table('store_photos')
->groupBy('user_id')
->having(DB::raw('count(user_id)'), '=', 12)
->select('user_id');
*I do not know that much about your app and its business logic / requirement. But you get the idea :)

Laravel QueryBuilder multiple columns match in the same WhereIn

I would have liked to convert a SQL query like this one in Laravel Eloquent.
This query works on MariaDb but i don't know about other engines (might be the reason why it isn't implented):
id
year
country
enabled
0
2000
"France"
0
1
2001
"Spain"
0
2
2002
"France"
1
3
2003
"Germany"
1
SELECT id FROM my_db.countries WHERE (name, enabled) IN (("France", 1), ("Spain", 0));
This returns 1 and 2.
This is possible with Eloquent (as suggested here: laravel whereIn multiple columns but it wouldn't return the expected results:
DB::table('countries')->select('id')->whereIn('name', ["france", "Spain"])->whereIn('enabled', [0, 1])->all();
This returns 0, 1 and 2.
I gave a shot at adapting the Illuminate/Database library to fit my needs but the databinding started to get really complexe.
I managed to make it with a whereRaw query but it isn't really clean enough for production code as there are no data binding (values shows up with ->toSql()).
Does anyone have an idea?
The query syntax you want to get:
SELECT id FROM my_db.countries WHERE (name, enabled) IN (("France", 1), ("Spain", 0));
is exclusive to Oracle. The whereIn method of Laravel supports (String, Array), so I think you only have the options to do it through raw queries or using the solution proposed by V-K
I suppose you wanna get records that satisfy two conditions. whereIn checks if the column contains one of the values in the array.
`DB::table('countries')->select('id')->whereIn('name', ["france", "Spain"])->whereIn('enabled', [0, 1])->all();`
that code returns combinations france 0, france 1, Spain 0, Spain 1.
TO get combinations france 0 and Spain 1 you can use this code
DB::table('countries')
->select('id')
->where(function(Builder $builder) {
$builder
->where('name', 'france')
->where('enabled', 0);
})
->orWhere(function(Builder $builder) {
$builder
->where('name', 'Spain')
->where('enabled', 1);
})
->get();
It checks the conditions name = france and enabled = 0 work together
If you add a new column that will keep the concatenation of the country and enabled fields, then you can use a simple whereIn method.
id
year
country
enabled
country_enabled
0
2000
"France"
0
"France-0"
1
2001
"Spain"
0
"Spain-0"
2
2002
"France"
1
"France-1"
3
2003
"Germany"
1
"Germany-1"
DB::table('countries')->select('id')->whereIn('country_enabled ', ["France-1", "Spain-0"])->get();
You may even add an index to that column to speed up the search.
Of course, the provided solution will add some slight overhead to the writing in that table.

how to select several rows from database(eloquent) order by rand in laravel

i want to select several random rows from database, something like this
select * from table order by rand limit 10
how to do it in Laravel eloquent model?
Do something like this:
User::orderBy(DB::raw('RAND()'))->take(10)->get();
It's simple than it looks like, you just have to use the suffle() collections method.
The shuffle method randomly shuffles the items in the collection:
$collection = collect([1, 2, 3, 4, 5]);
$shuffled = $collection->shuffle();
$shuffled->all();
// [3, 2, 5, 1, 4] // (generated randomly)
For further methods and info you should check the laravel eloquent docs, there are methods almost for everything.
Cheers.

Apache Pig: Easier way to filter by a bunch of values from the same field

Say I want select a subset of data by their values of from the same field. Right now I have to do something like this
TestLocationsResults = FILTER SalesData by (StoreId =='17'
or StoreId =='85'
or StoreId =='12'
or StoreId =='45'
or StoreId =='26'
or StoreId =='75'
or StoreId =='13'
)
in SQL, we can simply do this :
SELECT * FROM SalesData where StoreID IN (17, 12, 85, 45, 26, 75, 13)
Is there a similiar shortcut in Pig that I am missing?
It looks like Pig 0.12 added an IN operator.
So you can do
FILTER SalesData BY StoreID IN (17, 12, 85, 45, 26, 75, 13);
There is no IN keyword in Pig to do this sort of set membership detection.
One suggestion if to write a UDF (as seen in this question / answer).
Another could be to create a relationship with values for each StoreId you want to filter by and then perform an inner join on the two relationships.
My solution to this, when the data type is chararray, is to use a regular expression:
TestLocationsResults = FILTER SalesData by StoreID MATCHES '(17|12|85|45|26|75|13)';
When the data type is an int, you could try casting to a chararray.
One work around could be to use a built in function "INDEXOF"
Ex:
TestLocationsResults = FILTER SalesData by INDEXOF(',17,12,85,45,26,75,13,', CONCAT(CONCAT(',', StoreId), ',')) > -1;
Amended to take into account the comment, introduce the ',' symbols around StoreId to have the exact match and not a partial
The way you're currently doing it is the best way to do it in Pig. All of the alternatives to what you're doing now are either hacky, slow, or both. Hopefully, Pig adds an "in" query in a future version, but for now you're doing it the best way available.

Two (seemingly) identical queries, one is faster, why?

Two seemingly identical queries (as far as a newbie like me can tell, but the first is faster overall in the partial template rendering time (nothing else changed but the ids statement). Also, when testing through rails console, the latter will visibly run a query, the former will not. I do not understand why - and why the first statement is a few ms faster than the second - though I can guess it is due to the shorter method chaining to get the same result.
UPDATE: My bad. They are not running the same query, but it still is interesting how a select on all columns is faster than a select on one column. Maybe it is a negligible difference compared to the method chaining though.
ids = current_user.activities.map(&:person_id).reverse
SELECT "activities".* FROM "activities" WHERE "activities"."user_id" = 1
SELECT "people".* FROM "people" WHERE "people"."id" IN (1, 4, 12, 15, 3, 14, 17, 10, 5, 6) Rendered activities/_activities.html.haml (7.4ms)
ids = current_user.activities.order('id DESC').select{person_id}.map(&:person_id)
SELECT "activities"."person_id" FROM "activities" WHERE "activities"."user_id" = 1 ORDER BY id DESC
SELECT "people".* FROM "people" WHERE "people"."id" IN (1, 4, 12, 15, 3, 14, 17, 10, 5, 6) Rendered activities/_activities.html.haml (10.3ms)
The purpose of the statement is to retrieve the foreign key reference to people in the order in which they appeared in the activities table, (on its PK).
Note: I use Squeel for SQL.
In the first query, you've chained .map and .reverse, while in the second query, you've used .order('id DESC') .select(person_id) which were unnecessary, if you added .reverse

Resources