Order query by subquery. Eloquent vs MySql - mysql

I would like to get lists which are followed by a particular user and order lists by created_at(by date when lists were followed). Not sure why it doesn't work (order by) using Eloquent:
$myQuery = PlaceList::query()
->where('private', 0)
->whereHas('followers', function ($query) use ($userID) {
$query->where('created_by_user_id', $userID);
})
->orderByDesc(
Follow::query()->select('created_at')
->where('created_by_user_id', $userID)
->where('followable_id', 'place_lists.id')
)
->get()
This is what I've got when I use dd($myQuery->toSql()):
select
*
from
`place_lists`
where
`private` = ?
and exists (
select
*
from
`follows`
where
`place_lists`.`id` = `follows`.`followable_id`
and `created_by_user_id` = ?
and `follows`.`deleted_at` is null
)
and `place_lists`.`deleted_at` is null
order by (
select
`created_at`
from
`follows`
where
`created_by_user_id` = ?
and `followable_id` = ?
and `follows`.`deleted_at` is null
) desc
But when I populate MySql with data it does work:
select
*
from
`place_lists`
where
`private` = 0
and exists (
select
*
from
`follows`
where
`place_lists`.`id` = `follows`.`followable_id`
and `created_by_user_id` = 13
and `follows`.`deleted_at` is null
)
and `place_lists`.`deleted_at` is null
order by (
select
`created_at`
from
`follows`
where
`created_by_user_id` = 13
and `followable_id` = `place_lists`.`id`
and `follows`.`deleted_at` is null
) desc
I'm well confused, what I'm doing wrong?

This query, you assert that followalbe_id is equal to 'place_lists.id'.
->orderByDesc(
Follow::query()->select('created_at')
->where('created_by_user_id', $userID)
->where('followable_id', 'place_lists.id')
)
To compare two columns use whereColumn() instead.
->orderByDesc(
Follow::query()->select('created_at')
->where('created_by_user_id', $userID)
->whereColumn('followable_id', 'place_lists.id')
)

I'm not sure if I'm on the right track here but in line 9 of your eloquent builder the query generated will use a string to compare followable_id against and therefore look like this:
and `followable_id` = 'place_lists.id'
Using DB::raw() in your where-clause in line 9 should fix that:
$myQuery = PlaceList::query()
->where('private', 0)
->whereHas('followers', function ($query) use ($userID) {
$query->where('created_by_user_id', $userID);
})
->orderByDesc(
Follow::query()->select('created_at')
->where('created_by_user_id', $userID)
->where('followable_id', DB::raw('`place_lists`.`id`')) // here
)
->get()

Related

Get 2 Previous and Next with Current entry from database in laravel

I'm showing the image on a page. I am getting data with this
$designs = Design::take(10)->where('show', '1')->where('id', $id)->orderBy('hit', 'desc')->get();
Now I want 2 previous and 2 next records so users can select and see them.
Something like
Select 5 entry where the middle should be 'id'
The solution I came up with is the following:
You find the middle design.
Using it's id, you can get the previous 2 and make an union with a similar query that gets the next 2 (along with the original).
$design = Design::select('id')
->where('name', 'Sunset')
->firstOrFail();
$designs = Design::query()
->where('id', '<', $design->id)
->orderByDesc('id')
->limit(2)
->union(
Design::query()
->where('id', '>=', $design->id)
->orderBy('id')
->limit(3)
)
->orderBy('id')
->get();
The SQLs generated by this query are the following:
SELECT `id` FROM `designs`
WHERE `name` = "Sunset"
LIMIT 1
(
SELECT * FROM `designs`
WHERE `id` < ?
ORDER BY `id` DESC
LIMIT 2
)
UNION
(
SELECT * FROM `designs`
WHERE `id` >= ?
ORDER BY `id` ASC
LIMIT 3
)
ORDER BY `id` ASC
... Or if you'd rather have the results more separated
$design = Design::where('name', 'Sunset-Image')->firstOrFail();
$previous = Design::where('id', '<', $design->id)->orderByDesc('id')->limit(2)->get();
$next = Design::where('id', '>', $design->id)->orderBy('id')->limit(2)->get();

Laravel raw select with avg - cannot display fetched data

I have Laravel project and I want to fetch avg time. Select statement is correct but I can not display data because I get error: Array to string conversion
This is my code:
$limit = (int)$this->argument('distance');
$avgTimeBetweenBlocks = DB::select('SELECT AVG(b.timediff)
FROM
(
SELECT a.created_at, a.created_at_end, AVG(TIME_TO_SEC(TIMEDIFF(a.created_at, a.created_at_end))) AS timediff
FROM
(
SELECT created_at,
(
SELECT max( created_at)
FROM block_differences bd1
WHERE bd1.created_at < bd.created_at
) as created_at_end
FROM block_differences bd
limit :limit
) a
WHERE a.created_at_end is not null
GROUP BY a.created_at, a.created_at_end
) b', ['limit' => $limit] )->get();
echo json_decode(json_encode($avgTimeBetweenBlocks[0]), true);
$this->info("avgTimeBetweenBlocks {$avgTimeBetweenBlocks[0]} seconds");
I tried to display value in different way, but all the time I get the same result.
You're trying to echo an array here json_decode will convert it an array so echo will not work. use print_r instead of echo
print_r(json_decode(json_encode($avgTimeBetweenBlocks[0]), true));
Edit :- Array ( [AVG(b.timediff)] => 139.38775510 )
you've to change your query. check here I give you an example below.
$avgTimeBetweenBlocks = DB::select('SELECT AVG(b.timediff) as avgtimediff
FROM
(
SELECT a.created_at, a.created_at_end, AVG(TIME_TO_SEC(TIMEDIFF(a.created_at, a.created_at_end))) AS timediff
FROM
(
SELECT created_at,
(
SELECT max( created_at)
FROM block_differences bd1
WHERE bd1.created_at < bd.created_at
) as created_at_end
FROM block_differences bd
limit :limit
) a
WHERE a.created_at_end is not null
GROUP BY a.created_at, a.created_at_end
) b', ['limit' => $limit] )->get();
$response = json_decode(json_encode($avgTimeBetweenBlocks[0]), true);
echo $response['avgtimediff'];

Convert This Sql to Eloquent

How can I create this query with Eloquent?
SELECT
*
FROM
`contents`
WHERE
`id` NOT IN (
SELECT
`id`
FROM
`contents` AS `laravel_reserved_0`
WHERE
`laravel_reserved_0`.`parent_id` IN (
SELECT
`id`
FROM
`contents` AS `laravel_reserved_1`
WHERE
`laravel_reserved_1`.`type` IN ( 'pro' )
)
)
AND `is_active` = 1
AND `type` IN ( 'stars', 'video', 'article' )
GROUP BY
`slug`
ORDER BY
`created_at` DESC
LIMIT 10
You can do multiple queries, separate for nested queries.
$typeIDs = Content::whereIn('type', ['pro'])->pluck('id');
$parentIDs = Content::whereIn('parent_id', $typeIDs)->pluck('id');
$contents = Content::whereNotIn('id', $parentIDs)
->where('is_active', 1)
->whereIn('type', ['stars', 'video', 'article'])
->groupBy('slug')
->orderBy('created_at', 'desc')
->limit(10)
->get();
Beside this, if you wish, you can also replace created variables with the actual queries itself.

How to use $wpdb->prepare with WHERE NOT EXISTS to prevent duplicate entry

In WordPress Development Stack Exchange, I answered my own Question where I mentioned that, with the following code I'm able to insert data into database, but can prevent duplicate entries:
INSERT INTO {$wpdb->prefix}user_req
( user_id, post_id )
VALUES ( %d, %d )
WHERE NOT EXISTS (
SELECT * FROM
{$wpdb->prefix}user_req.user_id = user_id
AND
{$wpdb->prefix}user_req.post_id = post_id
)
I don't know why, it's actually NOT working (I don't know how it worked that time!!). Now I modified my code into this:
$wpdb->query( $wpdb->prepare(
"
INSERT INTO {$wpdb->prefix}user_req
( user_id, post_id )
VALUES ( %d, %d )
WHERE NOT EXISTS (
SELECT *
FROM
{$wpdb->prefix}user_req
WHERE
{$wpdb->prefix}user_req.user_id = VALUES({$user_id})
AND
{$wpdb->prefix}user_req.post_id = VALUES({$post_id[1]})
)
",
$user_id,
$post_id[1]
) );
But still it's not working, even it's failing to insert the data into db.
What am I missing?
It is not allowed to use a where clause with insert ... values .... Try using insert ... select ... where ... instead:
INSERT INTO
{$wpdb->prefix}user_req (
user_id,
post_id
)
SELECT
%d,
%d
FROM
DUAL
WHERE
NOT EXISTS (
SELECT
0
FROM
{$wpdb->prefix}user_req
WHERE
{$wpdb->prefix}user_req.user_id = user_id
AND
{$wpdb->prefix}user_req.post_id = post_id
)
$wpdb->query( $wpdb->prepare(
"
INSERT INTO {$wpdb->prefix}user_req
( user_id, post_id )
VALUES ( %d, %d )
WHERE 0 = (
SELECT count(*)
FROM
{$wpdb->prefix}user_req
WHERE
{$wpdb->prefix}user_req.user_id = VALUES({$user_id})
AND
{$wpdb->prefix}user_req.post_id = VALUES({$post_id[1]})
)
",
$user_id,
$post_id[1]
) );
There are a couple of issues - first using INSERT/SELECT utilizing the DUAL dummy table name is recommended (only returning the values when the result of the subquery is 0), however to properly execute the subquery you will need to pass your parameters in twice. Using a table alias can make things easier to manage. See the example below.
// Set up your query using HEREDOC format
$sql = <<<SQL
INSERT INTO {$wpdb->prefix}user_req
( user_id, post_id )
SELECT %d, %d
FROM DUAL
WHERE 0 = (
SELECT COUNT(*)
FROM {$wpdb->prefix}user_req AS ur
WHERE ur.user_id = %d
AND ur.post_id = %d
SQL;
// Call prepare(), pass values first for INSERT, and again for the sub SELECT
$sql = $wpdb->prepare( $sql, $user_id, $post_id[1], $user_id, $post_id[1] );
// Execute the query
$wpdb->query( $sql );
Ok, I did it at last with two different queries - I agree, not a smarter one, but at least solved my one, as all the other possibilities are a mixture of two queries:
$alreadyGot = $wpdb->get_results(
"SELECT
COUNT(*) AS TOTALCOUNT
FROM {$table}
WHERE ( user_id = $user_id AND post_id = $post_id[1] )
AND ( order_id = '' )"
);
$count = $alreadyGot[0]->TOTALCOUNT;
if( $count > 0 ) {
//do nothing
} else {
$wpdb->insert(
$table,
array(
'user_id' => $user_id,
'post_id' => $post_id[1]
)
);
}
Thanks to my colleague Mr. Ariful Haque.

select last row only if a where condition is met

I have a column for messages and I am trying to figure out how I should display messages in "sent" folder.
I run the following query:
SELECT
`text`,
`created`
FROM
`messages`
WHERE
`status` = 1
ORDER BY `created` DESC
LIMIT 1
I want to introduce a condition so that result is returned only when the last row with status = 1 also has user_from = $user (If the last row has user_to = $user, then nothing should be returned).
What should my query be?
you can use a subquery
select `text`, `created` from T1
( SELECT
`text`,
`created`
FROM
`messages`
WHERE
`status` = 1
ORDER BY `created` DESC
LIMIT 1) T1
where `user_from` = $user and `user_to` != $user
If I understand your question correctly, do you want to do this:
SELECT
`text`,
`created`
FROM
`messages`
WHERE
`status` = 1
AND `user_from` = $user
AND `user_to` != $user
ORDER BY `created` DESC
LIMIT 1
Of course you need to replace $user with your string, or use a prepared statement to insert the variable into the query.
Select the latest message with status=1 and then use it as a nested query and check your conditions to output this one row result or not:
select `text`, `created`
from
(
SELECT
`text`,
`created`,
user_from,
user_to
FROM
`messages`
WHERE
`status` = 1
ORDER BY `created` DESC
LIMIT 1
) t
where (user_from = $user)
and (not (user_to = $user))