Need guidance on how to build a Laravel database query - laravel

In Laravel 6.18 I'm trying to figure out how to recreate the following Postgres query.
with data as (
select date_trunc('month', purchase_date) as x_month, date_trunc('year', purchase_date) AS x_year,
sum (retail_value) AS "retail_value_sum"
from coins
where user_email = 'user#email.com' and sold = 0
group by x_month, x_year
order by x_month asc, x_year asc
)
select x_month, x_year, sum (retail_value_sum) over (order by x_month asc, x_year asc rows between unbounded preceding and current row)
from data
I know how to build the main part of the query
$value_of_all_purchases_not_sold = DB::table('coins')
->select(DB::raw('date_trunc(\'month\', purchase_date) AS x_month, date_trunc(\'year\', purchase_date) AS x_year, sum(retail_value) as purchase_price_sum'))
->where('user_email', '=', auth()->user()->email)
->where('sold', '=', 0)
->groupBy('x_month', 'x_year')
->orderBy('x_month', 'asc')
->orderBy('x_year', 'asc')
->get();
but how do you build out the with data as ( and the second select?
I need the data to be cumulative and I'd rather do the calculation in the DB than in PHP.

Laravel doesn't have built-in method(s) for common table expression. You may use a third party package such as this - it has a very good documentation. If you don't want to use an external library, then you need use query builder's select method with bindings such as
$results = DB::select('your-query', ['your', 'bindings']);
return Coin::hydrate($results); // if you want them as collection of Coin instance.

Related

How to get the default price of the product with different prices from different date periods from the period closest to today?

my sqlfiddle eample
Hello there,
according to the above sqlfiddle example;
I have a table A where the products are listed and a table B with different prices for different periods associated with these products.
Here I show these prices according to the date the user has chosen. There is no problem.
However, if the user has not selected a date, I cannot show the price of the period closest to today by default.
In the example I gave, the sql query does this successfully, but I cannot write it successfully in the form of laravel query. Or as an Eloquent orm query
How can I do that?
$query->select(['tableA.*', 'tableB.start_date', 'tableB.end_date', 'tableB.price'])
->join('tableB', function($join) {
$join->on('tableA.id', '=', 'tableB.pro_id');
})->where(function($sq) use ($postFrom) {
$sq->when($postFrom[0]=='0', function ($syq) {
$syq->whereRaw('DAYOFYEAR(curdate()) <= DAYOFYEAR(tableB.end_date)');
}, function ($stq) use ($postFrom) {
$stq->whereDate('tableB.start_date', '<=', $postFrom[0])
->whereDate('tableB.end_date', '>=', $postFrom[0]);
});
})->orWhere(function($ssq) use ($postTo) {
$ssq->whereDate('tableB.start_date', '<=', $postTo[0])
->whereDate('tableB.end_date', '>=', $postTo[0]);
})->groupBy('tableA.id')->orderBy('tableB.price', $sortDirection);
note1: $postFrom and $postTo are the start and end dates from the user. If the user did not submit a date, $postFrom is displayed as 0.
note2: I show the default price when the $postFrom[0] == '0' condition is met.
note3: The '2021-03-07' value in the sqlfiddle example is used for example instead of the dynamic present value.
note4: According to this query, it takes the price value of the first period as default. But that's not what I want.
note5: I can't use 'joinSub' because Laravel version is 5.5.
note6:In the example I want to convert to Laravel Query form, the sql query that works without any problems:
select `tableA`.*, `tableB`.`start_date`, `tableB`.`end_date`, `tableB`.`price`
from `tableA`
right join(
SELECT id, start_date, end_date, pro_id, price, DATEDIFF(`tableB`.`end_date`, '2021-03-07') diff
FROM `tableB` GROUP BY id order by diff asc
) `tableB` on `tableA`.`id` = `tableB`.`pro_id` where (date(`end_date`) >= '2021-03-07')
group by `tableA`.`id` order by `price` desc
This is an equivalent query of your query. I haven't executed.
If Laravel Version is greater then 5.5
$query1 = DB::table('tableB')
->selectRaw("id, start_date, end_date, pro_id, price, DATEDIFF(end_date, '2021-03-07') AS diff")
->groupBy('id')->orderBy('diff','ASC');
TableA::select('tableA.*', 'tableB.start_date', 'tableB.end_date', 'tableB.price')
->joinSub($query1, 'tableB', function ($join)
{
$join->on('tableA.id', '=', 'tableB.pro_id');
})
->whereDate('tableB.end_date','>=','2021-03-07')
->groupBy('tableA.id')->orderBy('price','DESC')->get();
For Laravel 5.5
TableA::select('tableA.*', 'tableB.start_date', 'tableB.end_date', 'tableB.price')
->join(DB::raw("(SELECT id, start_date, end_date, pro_id, price,
DATEDIFF(`tableB`.`end_date`, '2021-03-07') diff
FROM `tableB` GROUP BY id order by diff asc) table2 "), function ($join)
{
$join->on('tableA.id', '=', 'table2.pro_id');
})
->whereDate('table2.end_date','>=','2021-03-07')
->groupBy('tableA.id')->orderBy('price','DESC')->get();

How to link subquery in Laravel QueryBuilder to outer table

I'm looking on how I can have a subquery in Laravel QueryBuilder join with the outer table. For instance, we have a table with Exchange Rates, with a currency and a value_date.
For each currency, we want to be able to fetch the valid exchange rate on a certain valuedate. Since exchange rates do not change on non working days, that means that for saturday and sunday, the value of friday is still valid. A bit of an obvious and standard case.
In SQL, I would query this as follows:
SELECT currency, value_date, rate
FROM exchange_rates er
WHERE value_date = (
SELECT max(value_date)
FROM exchange_rates
WHERE currency = er.currency
AND value_date <= '2019-02-03'
)
This would return a list of all exchange rates (one record for each currency), with the rate that is valid on 2019-02-03, and the associate value date (which would probably be 2019-02-01, since 2019-02-03 is a sunday...)
I have no idea however how I can do this with the Eloquent QueryBuilder without falling back to doing raw sql queries...
$value_date = '2019-02-03';
App\ExchangeRate::where('value_date', function($query) use ($value_date) {
... ??? ...
});
where() can also accept a closure that would allow you to do this:
return ExchangeRate::select('currency', 'value_date', 'rate')
->where('value_date', function($query) {
$query->from('exchange_rates AS er')
->selectRaw('max(value_date)')
->whereColumn('exchange_rates.currency', 'er.currency')
->where('value_date', '<=', '2019-02-03');
})->get();
Here's the output of the query.
Edit: To assign an alias to the outer table, you could do that with the DB Query Builder.
return DB::table('exchange_rates AS alias')
->select('currency', 'value_date', 'rate')
->where('value_date', function($query) {
$query->from('exchange_rates AS er')
->selectRaw('max(value_date)')
->whereColumn('alias.currency', 'er.currency')
->where('value_date', '<=', '2019-02-03');
})->get();

Laravel Eloquent - Eager Loading Query is Being Restricted by limit()

I have the following query:
MyTable::where('my_column', '=', 25)->with('myOtherTable')
->orderBy('id', DESC)->limit(5);
I would like the above to bring me results analogoous to the following raw SQL query:
SELECT *
FROM myTable AS ABB1
LEFT JOIN myOtherTable AS ABB2 ON ABB1.id = ABB2.myTable_id
WHERE my_column = 25
ORDER BY myTable.id DESC
LIMIT 5;
The above will find everything in myTable along with corresponding info from myOtherTable and then limit the results to 5 rows.
When I run the eloquent statement above, two SQL queries are processed. The first looks something like:
SELECT *
FROM myTable
WHERE my_column = 25
ORDER BY id DESC;
If this query returns say 7 result items, but I pass in a smaller number into the limit() function (ie. limit(5)), then the corresponding eager loading query will look like:
SELECT *
FROM myOtherTable
WHERE id IN(1, 2, 3, 4, 5);
The eager loading query is itself limited to 5 items. There should be no limit here. The number of items in the IN conditional above should be 7 (or whatever the count returned from the first query is). The limit should only be applied after the second query runs.
How would I do this with Eloquent?
You can use eloquent's join.
MyTable::join('myOtherTable', 'column_id', '=', 'id')
->where('my_column', '=', 25)
->with('myOtherTable')
->orderBy('id', DESC)
->limit(5);
The with is only if you want to eager load the relation on MyTable.

Converting a raw query to Laravel query builder

I have the following MySQL query which fetches a list of the last 9 authors to write a post and lists them in order of the date of the last post they wrote.
It's working properly but I'd like to re-write it using the Laravel Query Builder. Here is the query at the moment:
$authors = DB::select("
SELECT
`a`.`id`,
`a`.`name`,
`a`.`avatar`,
`a`.`slug` AS `author_slug`,
`p`.`subheading`,
`p`.`title`,
`p`.`slug` AS `post_slug`,
`p`.`summary`,
`p`.`published_at`
FROM
`authors` AS `a`
JOIN
`posts` AS `p`
ON `p`.`id` =
(
SELECT `p2`.`id`
FROM `posts` AS `p2`
WHERE `p2`.`author_id` = `a`.`id`
ORDER BY `p2`.`published_at` DESC
LIMIT 1
)
WHERE
`a`.`online` = 1
ORDER BY
`published_at` DESC
LIMIT 9
");
I understand the basics of using the query builder, but there doesn't appear to be anything in the Laravel docs that allows for me to JOIN a table ON a SELECT.
Can anyone suggest a way that I can write this query using the Laravel Query builder, or perhaps suggest a way that I can rewrite this query to make it easier to structure with the query builder?
Try to do like this
$data = DB::table('authors')
->select(
'a.id',
'a.name',
'a.avatar',
'a.slug AS author_slug',
'p.subheading',
'p.title',
'p.slug AS post_slug',
'p.summary',
p.published_at')
->from('authors AS a')
->join('posts AS p', 'p.id', '=', DB::raw("
(
SELECT p2.id FROM posts AS p2
WHERE p2.author_id = b.id
ORDER BY p2.published_at
DESC LIMIT 1
)"))
->where('a.online', 1)
->limit(9)
->orderBy('p.published_at', 'desc')
->get();

Eloquent query alternative

How can I write the following in Laravel's Eloquent?
SELECT *
FROM
( SELECT real_estate.property_id,
real_estate.amount_offered,
payee.summa
FROM real_estate
LEFT JOIN
(SELECT property_id,
SUM(amount) AS summa
FROM payments
GROUP BY property_id) payee ON payee.property_id = real_estate.property_id ) yoot
WHERE summa = 0.05 * amount_offered
Been on this for a while now and really can't get around it. Lemme explain the whole cause for the panic.
I have two tables, one for property and another for payments made on those properties. Now at any given time I will like to query for what properties have been paid for to a certain percentage hence the 0.05 which reps 5%. As it is the query works but I need an Eloquent alternative for it. Thanks
Anywhere you have subqueries in your SQL you'll need to use DB::raw with Eloquent. In this case you have a big subquery for the FROM statement, so the easiest way would be to do this:
DB::table(
DB::raw('SELECT real_estate.property_id, real_estate.amount_offered, payee.summa FROM real_estate LEFT JOIN (SELECT property_id, SUM(amount) AS summa FROM payments GROUP BY property_id) payee ON payee.property_id = real_estate.property_id)')
)
->where('summa', DB::raw('0.05 * amount_offered'))->get();
Notice I used DB::raw for the WHERE statment value as well. That's because you are doing a multiplication using a column name, and the value would otherwise be quoted as a string.
If you want to go a step further and build each subquery using Eloquent, then convert it to an SQL string and injecting it using DB::raw, you can do this:
$joinQuery = DB::table('payments')
->select('property_id', 'SUM(amount) AS summa')
->groupBy('property_id')
->toSql();
$tableQuery = DB::table('real_estate')
->select('real_estate.property_id', 'real_estate.amount_offered', 'payee.summa')
->leftJoin(DB::raw('(' . $joinQuery . ')'), function ($join)
{
$join->on('payee.property_id', '=', 'real_estate.property_id');
})
->toSql();
DB::table(DB::raw('(' . $tableQuery . ')'))->where('summa', DB::raw('0.05 * amount_offered'))->get();
In this case, the second approach doesn't have any benefits over the first, except perhaps that it's more readable. However, building subqueries using Eloquent, does have it's benefitfs when you'd need to bind any variable values to the query (such as conditions), because the query will be correctly built and escaped by Eloquent and you would not be prone to SQL injection.

Resources