Laravel sub-query - mysql

I have a database that looks like this:πŸ‘‡πŸ‘‡
images πŸŒ…
| id | name | src | status |
| ------------- |---------------| ------------| ----------|
| 1 | nice sun set | 1020288.jpg | published |
| 2 | poor sun set | 1120288.jpg | published |
| 3 | best sun set | 3120288.jpg | deleted |
| ------------- |---------------| ------------| --------- |
image_views πŸ‘€
| id | image_id | browser_id🌎 | created_at |
| ------------- |---------------| ------------ | ------------------ |
| 1 | 2 | 1020288e3221 |2020-02-23 13:55:11 |
| 2 | 1 | 1120288221ww |2020-02-27 13:50:51 |
| ------------- |---------------| ------------ | ------------------ |
Now in my laravel App,
I want to get the most viewed image in the PAST last 7 days.
( i want to have a column of image_views and those views πŸ‘€ should be grouped by browser id ).
so here is what i have tried:πŸ‘‡πŸ‘‡
$image_views = DB::table('image_views')
->selectRaw('count(*) as view_count')
->where(function($query){
$query->where('image_views.image_id', 'images.id');
$query->whereDate('image_views.created_at', '>=', Carbon::now()->subDays(7)->toDateTimeString() );
});
$image = Image::select(['images.*', DB::raw('(' . $image_views->toSql() . ') as views ')])
->limit(1)
->orderBy('views', 'desc')
->where('images.status','published')
->mergeBindings($image_views)
->get();
return $image;
So unfortunately the posted above☝☝ code does not work😩
It only return blank results.
By the way i have lot of views in image_views table starting from 2⃣0⃣1⃣9⃣ to now, just that i couldn't post all here..
THE FUNNY THING IS THAT IF I CONVERT IT TO SQL AND PASTE IT IN PHPMYADMIN IT WORKS LIKE A CHARM
return $image->toSql();
//->mergeBindings($image_views)
//->get();
PLEASE SOMEONE TELL ME WHAT I AM DOING WRONG IN LARAVEL!!πŸ™Œ

Given images & image_views tables
$mostViewdImage = DB::table('image_views')
->join('images', 'image_views.image_id', '=', 'images.id')
->select('browser_id', DB::raw('count(image_id) as occurrence'), 'images.*')
->where('image_views.created_at', '>=', Carbon::now()->subDays(7)->toDateTimeString())
->groupBy('image_id', 'browser_id')
->orderByRaw('occurrence DESC')->first();
dump($mostViewdImage);
//Output
"select `browser_id`, count(image_id) as occurrence, `images`.* from `image_views` inner join `images` on `image_views`.`image_id` = `images`.`id` where `image_views`.`created_at` >= ? group by `image_id`, `browser_id` order by occurrence DESC limit 1" (2.02 s)
{#261 β–Ό
+"browser_id": "1020288e3221"
+"occurrence": 2
+"id": 2
+"name": "poor sun set"
+"src": "1120288.jpg"
+"status": "published"
}

Related

How to multiply and group using laravel eloquent with relation

I'd like to export data from my database but have problems with multiplying and sum using laravel eloquent with relation
So i have 2 tables there (budgets, items)
Budget's:
// Table
+----+---------------+-----------------+------+-----+--------------------+
| id | delivery_plan | item_code | curr | qty | price |
+----+---------------+-----------------+------+-----+--------------------+
| 1 | 2022-08 | 201.0001 | IDR | 1 | 2000.0000000000 |
| 2 | 2022-08 | 201.0001 | IDR | 3 | 2000.0000000000 |
| 3 | 2022-07 | 201.9999 | IDR | 2 | 2000.0000000000 |
+----+---------------+-----------------+------+-----+--------------------+
// Relation
public function item()
{
return $this->belongsTo(Item::class, 'item_code', 'item_code');
}
Items :
// Table
+----+----------------+-----------+
| id | subgroup | item_code |
+----+----------------+-----------+
| 1 | KOMPONEN MESIN | 201.0001 |
| 2 | EQUIPMENT LAIN | 201.9999 |
+----+----------------+-----------+
// Relation
public function budgets()
{
return $this->hasMany(Budget::class, 'item_code', 'item_code');
}
So, the scenario is :
Multiply the "qty" * "price" columns and name them as "total" like so
Group them by "subgroup" column, which came from item() relationship
Group them by "delivery_plan"
I prefer using eloquent because to minimize the complexity because i need that "whereHas" method
This is what i've tried so far and isn't working :
$budgets = Budget::with('item', 'rate')->whereHas('period.term', function (Builder $builder) {
$builder->where('name', '=', Session::get('term-budget'));
})->where('section', Session::get('section-budget'))->getQuery();
$result = $budgets->sum('price * qty')->get();
How can i achieve this ?
This can be solved by a join with SUM(), something like below (untested):
Budget::leftJoin('items', 'budgets.item_code', '=', 'items.item_code')
->addSelect('subgroup')
->addSelect('delivery_plan')
->addselect(\DB::raw('SUM(qty * price) as total'))
->groupBy('subgroup', 'delivery_plan')
->get();

MySQL query to fetch rows from different table with multiple rows into respective column

We have two tables as below:
Table-1 Name: apprecord
---------------------------------------
appid | desc | status
ALT01 | this is first | Open
ALT02 | this is second | Open
ALT03 | this is third | Closed
---------------------------------------
Table-2 Name: question
-----------------------------------------------
appid | questionseq | ques | ans
ALT01 | 1 | how are you | good
ALT01 | 2 | are you fine | yes
ALT02 | 1 | how was your day | great
ALT02 | 2 | are you coming today | yes
ALT03 | 1 | where are you | at home
ALT03 | 2 | are you fine | yes
--------------------------------------------------
How can I write a MySQL query so that I can get the result as below:
----------------------------------------------------------------------------------------
appid | desc | status| QUES1 | ANS1 | QUES2 | ANS2
ALT01 | this is first | Open | how are you | good | are you fine | yes
ALT02 | this is second| Open | how was your day| great | are you coming today | no
ALT03 | this is third | Closed| where are you | at home| are you fine | yes
---------------------------------------------------------------------------------------
Here QUES1, ANS1, QUES2, ANS2 are hardcoded/fixed column headers.
Try this:
Sample data:
create table apprecord(appid varchar(10),desc varchar(100),status varchar(10));
insert into apprecord values
('ALT01','this is first','Open'),
('ALT02','this is second','Open'),
('ALT03','this is third','Closed');
create table question(appid varchar(10),questionseq int,ques varchar(100),ans varchar(10));
insert into question values
('ALT01',1,'how are you','good'),
('ALT01',2,'are you fine','yes'),
('ALT02',1,'how was your day','great'),
('ALT02',2,'are you coming today','yes'),
('ALT03',1,'where are you','at home'),
('ALT03',2,'are you fine','yes');
T-SQL:
select ar.appid,
`desc`,
`status`,
q1.ques ques1,
q1.ans ans1,
q2.ques ques2,
q2.ans ans2
from apprecord ar
left join (select appid, ques, ans from question where questionseq = 1) q1
on ar.appid = q1.appid
left join (select appid, ques, ans from question where questionseq = 2) q2
on ar.appid = q2.appid
This is standard pivoting, although it can be dane as above, using two joins :)

One to Many Count with one query?

I haven't touched the backend in a while.. so forgive me if this is super simple. I'm working with Lumen v.5.6.1.
| table.sets | | table.indexed_items |
|----------------| |---------------------------------|
| ID | SET | | ID | setId | itemId | have |
|----|-----------| |----|-------|--------|-----------|
| 1 | set name 1| | 1 | 3 | 1 | 2 |
| 2 | set name 2| | 2 | 3 | 2 | 1 |
| 3 | set name 3| | 3 | 3 | 3 | 4 |
| 4 | 2 | 4 | 1 |
| 5 | 2 | 5 | 3 |
| 6 | 2 | 6 | 1 |
How would I return in one query, groupedBy/distinct by setId (with set name as a left join?) to have a return like this:
[
setId: 2,
name: 'set name 2',
haveTotal: 5,
],
[
setId: 3,
name: 'set name 3',
haveTotal: 7,
]
Here is a raw MySQL query which should work. To convert this to Laravel should not be too much work, though you might need to use DB::raw once or twice.
SELECT
s.ID AS setId,
s.`SET` AS name,
COALESCE(SUM(ii.have), 0) AS haveTotal
FROM sets s
LEFT JOIN indexed_items ii
ON s.ID = ii.setId
GROUP BY
s.ID;
Demo
If you don't want to return sets having no entries in the indexed_items table, then you may remove the call to COALESCE, and you may also use an inner join instead of a left join.
Note that using SET to name your tables and columns is not a good idea because it is a MySQL keyword.
If you are using or want to use eloquent, you can do something like:
$sets = App\Sets::withCount('indexed_items')->get();
This will return a collection with a column name indexed_items_count
Obviously you will need to change depending on your model names.
Here are the docs
I always use in my project for count relation ship record.
$sets->indexed_items->count();

MySQL/MariaDB/SQLite: DISTINCT CONCAT

This question isn't necessarily just Laravel related, but I'm trying to fetch records, which are distinct by concatenated fields. I need this to work with both MySQL/MariaDB as well as SQLite for testing purposes.
While doing my research, I've found out that SQLite does not have CONCAT function - instead you're using || operator to concatenate items. MySQL on the other hand will not interpret || the same way, but I can always use the conditional statement just to cover both cases.
However, I still cannot get records I'm after - my table consists of:
| id | tagable_id | tagable_type | name | title | description | url | image | hits |
| 1 | 1 | App\Models\Article | a.. | A.. | A.. descr.. | https://localhost | https://localhost... | 0 |
| 2 | 1 | App\Models\Article | b.. | B.. | B.. descr.. | https://localhost | https://localhost... | 2 |
| 3 | 1 | App\Models\Article | c.. | C.. | C.. descr.. | https://localhost | https://localhost... | 3 |
| 4 | 1 | App\Models\Page | a.. | A.. | C.. descr.. | https://localhost | https://localhost... | 0 |
I need get only 4 records that are sorted ASC by number of hits and which are unique using CONCAT(table_id, tagable_type).
What the statement should return in this case would be records with id 1 and 4 - because 2 and 3 have the same tagable_id and tagable_type as record with id 1, which has lowest number of hits - effectively only returning only 2 records:
| id | tagable_id | tagable_type | name | title | description | url | image | hits |
| 1 | 1 | App\Models\Article | a.. | A.. | A.. descr.. | https://localhost | https://localhost... | 0 |
| 4 | 1 | App\Models\Page | a.. | A.. | C.. descr.. | https://localhost | https://localhost... | 0 |
I tried already:
DB::table('tags')
->selectRaw("DISTINCT CONCAT(`tagable_id`, '-', `tagable_type`), `id`, `name`, `title`, `description`, `url`, `image`")
->whereIn('name', $tags->toArray())
->orderBy('hits');
This however does not return distinct records - it will return all records regardless of the distinct concatenation - that is in MySQL / MariaDB - in SQLite it will tell me no such function: CONCAT.
I also tried:
DB::table('tags')
->selectRaw("CONCAT(`tagable_id`, '-', `tagable_type`) as `identifier`, `id`, `name`, `title`, `description`, `url`, `image`")
->whereIn('name', $tags->toArray())
->groupBy('identifier')
->orderBy('hits');
This time MySQL/MariaDB tells me that I need to include other fields in the group by as well tags.id' isn't in GROUP BY, but when I use it with SQLite and replace CONCAT function with (tagable_id || '-' || tagable_type) as identifier - it seem to work.
So at this stage I'm: MySQL: 0 | SQLite: 1
Any help would be much appreciated.
UPDATE
After hours of trying to get it resolved I've decided to add a new column
identifier to the table to overcome issue of the non available concat function - my code now looks like this:
Tag::with('tagable')->whereIn('id', function($query) use ($tags) {
$query->selectRaw('min(`id`) from `tags`')
->whereIn('name', $tags->toArray())
->groupBy('identifier');
})
->orderBy('hits')
->take(4)
->get();
This is still not quite what I'm after as it relies on the lowest id min(id) of the given identifier and if the record with the lowest id for the same identifier has higher number of hits then its sibling then the sibling will not be returned.
You can use DISTINCT to get distinct combinations, but not whole rows:
DB::table('tags')->distinct()->get(['tagable_id', 'tagable_type']);
You have to use a more complex query for that:
$join = DB::table('tags')
->select('tagable_id', 'tagable_type')->selectRaw('MIN(hits) as hits')
->whereIn('name', $tags->toArray())
->groupBy('tagable_id', 'tagable_type');
$sql = '(' . $join->toSql() . ') as grouped';
$tags = DB::table('tags')
->join(DB::raw($sql), function($join) {
$join->on('tags.tagable_id', '=', 'grouped.tagable_id')
->on('tags.tagable_type', '=', 'grouped.tagable_type')
->on('tags.hits', '=', 'grouped.hits');
})->mergeBindings($join)
->whereIn('name', $tags->toArray())
->get();
A solution that guarantees one record per unique combination:
$join = DB::table('tags')
->select('tagable_id', 'tagable_type')->selectRaw('MIN(hits) as hits')
->whereIn('name', $tags->toArray())
->groupBy('tagable_id', 'tagable_type');
$sql = '(' . $join->toSql() . ') as grouped';
$ids = DB::table('tags')
->selectRaw('MIN(id) as id')
->join(DB::raw($sql), function($join) {
$join->on('tags.tagable_id', '=', 'grouped.tagable_id')
->on('tags.tagable_type', '=', 'grouped.tagable_type')
->on('tags.hits', '=', 'grouped.hits');
})->mergeBindings($join)
->whereIn('name', $tags->toArray())
->groupBy('tags.tagable_id', 'tags.tagable_type')
->pluck('id');
$tags = DB::table('tags')->whereIn('id', $ids)->get();

Issue with UNION in MySQL

I have two tables.
rp_format
+-----+--+--------------+
| fid | | recordformat |
+-----+--+--------------+
| 1 | | CD |
| 2 | | Vinyl |
| 3 | | DVD |
+-----+--+--------------+
rp_records
+----+--+--------+
| id | | format |
+----+--+--------+
| 1 | | 1 |
| 2 | | 2 |
| 3 | | 3 |
+----+--+--------+
What I would like to achieve is to display everything from "rp_format". But I would also like make a check to see if there is a "fid"-value found in "format".
Example that should be displayed on page like this:
fid recordformat
1 CD Remove this format
2 Vinyl Remove this format
3 DVD Remove this format
But let's say an "fid" value is found in "format" then I would like it to be displayed like this on page:
fid recordformat
1 CD Remove this format
2 Vinyl Can't remove this format
3 DVD Remove this format
"Remove this format / Can't remove this format" is text that will be displayed by checking if "fid" = "format" using PHP.
Here is my SQL query so far:
global $wpdb;
$rpdb = $wpdb->prefix . 'rp_format';
$rpdb2 = $wpdb->prefix . 'rp_records';
$sql = "
SELECT *
FROM $rpdb
LEFT OUTER JOIN $rpdb2 ON $rpdb.fid = $rpdb2.format
UNION
SELECT *
FROM $rpdb
RIGHT OUTER JOIN $rpdb2 ON $rpdb.fid = $rpdb2.format
WHERE $rpdb.fid IS NOT NULL
";
The issue I have with this query is that when "fid" is found in "format" (let's say it's found 10 times) every of these 10 values will be outputed also.
How can this be fixed?
Kind regards
Johan
If I understand correctly you want to display some message depending on if the data exists on rp_records or not and avoid multiple display.
Consider the following
mysql> select * from rp_format;
+------+--------------+
| fid | recordformat |
+------+--------------+
| 1 | CD |
| 2 | Vinyl |
| 3 | DVD |
| 4 | Test |
+------+--------------+
4 rows in set (0.00 sec)
mysql> select * from rp_records;
+------+--------+
| id | format |
+------+--------+
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
| 4 | 2 |
| 5 | 1 |
+------+--------+
So the query is
select
f.*,
case
when r.format is not null then 'Can\'t remove' else 'Remove this' end
as message
from rp_format f
left join rp_records r on r.format = f.fid
group by f.fid ;
+------+--------------+--------------+
| fid | recordformat | message |
+------+--------------+--------------+
| 1 | CD | Can't remove |
| 2 | Vinyl | Can't remove |
| 3 | DVD | Can't remove |
| 4 | Test | Remove this |
+------+--------------+--------------+
Not sure that i correctly understand your logic with found and not found format, if i wrong - add to if condition r.format IS NOT NULL instead r.format IS NULL. And i think you no need to use union, you should use join:
SELECT
r.fid,
f.recordformat,
IF(r.format IS NULL, "Can't remove this format", "Remove this format")
FROM rp_format f
LEFT JOIN rp_records r ON f.fid = r.format
GROUP BY f.fid
;
I'm sure that something like this will help you!