Laravel - How to create a query with where clause using subquery? - mysql

I'm trying to generate this query using Laravel:
select mygames.id, mygames.name, mygames.slug, mygames.cover from mygames
left join mygame_mygenre on mygames.id = mygame_mygenre.mygame_id
left join mygame_myplatform on mygames.id = mygame_myplatform.mygame_id
where mygame_mygenre.mygenre_id in (8, 9, 31, 32, 33)
and mygame_myplatform.myplatform_id in (3, 6, 14, 34, 37, 39, 46, 48, 72, 130)
and mygames.id <> 1990
and mygames.summary is not null
and (select count(mygame_id) from mygame_myplatform where mygame_id = mygames.id) > 1
group by mygames.id, mygames.name, mygames.slug, cover
order by RAND()
limit 6
My current code is:
$games = DB::table('mygames')
->leftjoin('mygame_mygenre', 'mygames.id', '=', 'mygame_mygenre.mygame_id')
->leftjoin('mygame_myplatform', 'mygames.id', '=', 'mygame_myplatform.mygame_id')
->select('mygames.id', 'mygames.name', 'mygames.slug', 'cover')
->when($genres_id, function ($query, $genres_id) {
return $query->whereIn('mygame_mygenre.mygenre_id', $genres_id);
})
->when($platforms_id, function ($query, $platforms_id) {
return $query->whereIn('mygame_myplatform.myplatform_id', $platforms_id);
})
->where('mygames.id', '<>', $this->id)
->whereNotNull('mygames.summary')
->where(function ($query) {
$query->selectRaw('count(mygame_id)')
->from('mygame_myplatform')
->where('mygame_id', 'mygames.id');
}, '>', 1)
->groupBy('mygames.id', 'mygames.name', 'mygames.slug', 'cover')
->inRandomOrder()
->take(6)
->get();
This code is not working because inside the closure function I was unable to pass the name of the mygames table with the id field. Laravel is interpreting as a text parameter and not as a table.field
->where(function ($query) {
$query->selectRaw('count(mygame_id)')
->from('mygame_myplatform')
->where('mygame_id', 'mygames.id'); <<<<<<<<<<<<<
}, '>', 1)
I tried to use 'use ()' but it didn't work either.
Could you help me?

Here I assume you're trying to compare 2 columns, right?
->where('mygame_id', 'mygames.id');
In that case, use the whereColumn/orWhereColumn method.
->whereColumn('mygame_id', 'mygames.id')

It's because you must use the whereColumn method to achieve this.
https://laravel.com/docs/8.x/queries#additional-where-clauses
Another solution is to use the whereRaw method.
See :
DB::table('mygames')
->select(['mygames.id', 'mygames.name', 'mygames.slug', 'mygames.cover'])
->leftJoin('mygames_mygenre', 'mygames.id', '=', 'mygame_mygenre.mygame_id')
->leftJoin('mygame_myplatform', 'mygames.id', '=', 'mygame_myplatform.mygame_id')
->whereIn('mygame_mygenre.mygenre_id', [8, 9, 31, 32, 33])
->whereIn('mygame_myplatform.myplatform_id', [3, 6, 14, 34, 37, 39, 46, 48, 72, 130])
->where('mygames.id', '<>', 1990)
->whereNotNull('mygames.summary')
->where(1, '<', function ($query) {
$query->selectRaw('COUNT(mygame_id)')
->from('mygame_myplatform')
->whereColumn('mygame_id', 'mygames.id');
})
->groupBy('mygames.id', 'mygames.name', 'mygames.slug', 'cover')
->inRandomOrder()
->take(6)
->get();

looks similar to #jascar_destin answer, but on the group by, you ll have to specify which table the cover is been picked from except if cover does not exists on other tables rather than mygames.
DB::table('mygames AS mg')
->leftJoin('mygame_mygenre AS mgmg', 'mgmg.mygame_id', '=', 'mg.id')
->leftJoin('mygame_myplatform AS mgmp', 'mgmp.mygame_id', '=', 'mg.id')
->select(['mg.id', 'mg.name', 'mg.slug', 'mg.cover'])
->whereIn('mgmg.my_genre_id', [8, 9, 31, 32, 33])
->whereIn('mgmp.my_platform_id'. [3, 6, 14, 34, 37, 39, 46, 48, 72, 130])
->where('mg.id', '<>', 1990)
->whereNotNull('mg.summary')
->where(1, '<', function ($query) {
$query->selectRaw('COUNT(mygame_id)')
->from('mygame_myplatform')
->whereColumn('mygame_id', 'mg.id');
})
->groupBy('mg.id', 'mg.name', 'mg.slug', 'mg.cover')
->inRandomOrder()
->take(6)
->get();

Related

Query for array elements inside Jsonb type postgreSQL

I have a psql table where one of the jsonb data is extracted over it.
{
"SrcRcs": [4, 0, 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 158],
"reason": "",
"result": "Success",
"InitTech": 1
}
This column is named Data and is of type jsonb.
I am extracting the SrcRcs data from the jsonb:
select Data->'SrcRcs' from table_name;
Output:
[4, 0, 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 158]
But which is in unsorted order as from the jsonb.
I want it in the sorted order like this:
[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,158]
Can someone please help me out?
I have tried the psql sort() but wasn't able to achieve the desired result.
You need to unnest the array elements and then aggregate them back in a sorted way:
SELECT (select jsonb_agg(i::int order by i::int)
from jsonb_array_elements(data -> 'SrcRcs') as t(i))
from the_table
If you want, you can create a function for this to make the SQL queries easier.

Boolen check if datetime.now() is between the values of any tuple in a list of tuples

I have a list of tuples looking like this:
import datetime as dt
hours = [(dt.datetime(2019,3,9,23,0), dt.datetime(2019,3,10,22,0)),
(dt.datetime(2019,3,10,23,0), d.datetime(2019,3,11,22,0))]
The list has a variable length and I just need a boolean if datetime.now() is between the first and second element of any tuple in the list.
In NumPy I would do:
((start <= now) & (end >= now)).any()
what is the most efficient way to do this in a pythonic way? Sorry about the beginners question.
this works but I don't like the len():
from itertools import takewhile
len(list(takewhile(lambda x: x[0] <= now and now <= x[1], hours ))) > 0
any better suggestions?
any(map(lambda d: d[0] <= now <= d[1], hours))
any: Logical OR across all elements
map: runs a function on every element of the list
As #steff pointed out map is redundant, because we cause list enumeration directly.
any(d[0] <= now <= d[1] for d in hours)
It would be way better if we can avoid indexing into tuple and use tuple unpacking somehow (this was the reason I started with map)
A more verbose alternative. (But more readable in my eyes)
import datetime as dt
def in_time_ranges(ranges):
now = dt.datetime.now()
return any([r for r in ranges if now <= r[0] and r[1] >= now])
ranges1 = [(dt.datetime(2019, 3, 9, 23, 0), dt.datetime(2019, 3, 10, 22, 0)),
(dt.datetime(2019, 3, 10, 23, 0), dt.datetime(2019, 3, 11, 22, 0)),
(dt.datetime(2019, 4, 10, 23, 0), dt.datetime(2019, 5, 11, 22, 0))]
print(in_time_ranges(ranges1))
ranges2 = [(dt.datetime(2017, 3, 9, 14, 0), dt.datetime(2018, 3, 10, 22, 0)),
(dt.datetime(2018, 3, 10, 23, 0), dt.datetime(2018, 3, 11, 22, 0)),
(dt.datetime(2018, 4, 10, 23, 0), dt.datetime(2018, 5, 11, 22, 0))]
print(in_time_ranges(ranges2))
Output
True
False

MySQL - group_concat pulling in additional incorrect data

I'm having trouble with a JOIN and a GROUP_CONCAT. The query is concatenating additional data that should not be associated with the join.
Here's my table structure:
linkages
ID table_name tag_id
1 subcategories 6
2 categories 9
music
ID artwork
1 5
2 4
artwork
ID url_path
1 /some/file/path
2 /some/file/path
And here's my query:
SELECT music.*,
artwork.url_path AS artwork_url_path,
GROUP_CONCAT( linkages.tag_id ) AS tag_ids,
GROUP_CONCAT( linkages.table_name ) AS table_name
FROM music
LEFT JOIN artwork ON artwork.id = music.artwork
LEFT JOIN linkages ON music.id = linkages.track_id
WHERE music.id IN( '1356',
'1357',
'719',
'169',
'170',
'171',
'805' )
ORDER BY FIELD( music.id,
1356,
1357,
719,
169,
170,
171,
805 )
This is the result of the GROUP_CONCAT :
[tag_ids] => 3, 6, 9, 17, 19, 20, 26, 49, 63, 64, 53, 57, 63, 65, 67, 73, 79, 80, 85, 96, 98, 11, 53, 67, 3, 6, 15, 17, 26, 38, 50, 63, 74, 53, 56, 57, 62, 63, 65, 66, 67, 72, 85, 88, 98, 24, 69, 71, 3, 6, 15, 17, 26, 38, 50
The first portion of the result is correct:
[tag_ids] => 3, 6, 9, 17, 19, 20, 26, 49, 63, 64, 53, 57, 63, 65, 67, 73, 79, 80, 85, 96, 98, 11, 53, 67
Everything after the correct values seems random and most of the values don't exist in the result in the database, but it's still pulling it in. It seems to repeat a portion of the correct result (3, 6, 15, 17 - the 3, 6, 17 are correct, but 15 shouldn't be there, similar with a bunch of other numbers - 71, etc. I can't use DISTINCT because I need to match up the tag_ids and table_name results as a multidimensional array from the results.
Any thoughts as to why?
UPDATE:
I ended up solving it with the initial push from Gordon. It needed a GROUP_BY clause, otherwise it was putting every results tag id's in each result. The final query ended up becoming this:
SET SESSION group_concat_max_len = 1000000;
SELECT
music.*,
artwork.url_path as artwork_url_path,
GROUP_CONCAT(linkages.tag_id, ':', linkages.table_name) as tags
FROM music
LEFT JOIN artwork ON artwork.id = music.artwork
LEFT JOIN linkages ON music.id = linkages.track_id
WHERE music.id IN('1356', '1357', '719', '169', '170', '171', '805')
GROUP BY music.id
ORDER BY FIELD(music.id,1356,1357,719,169,170,171,805);
Your join is generating duplicate rows. I would suggest that you fix the root cause of the problem. But, a quick-and-dirty solution is to use group_concat(distinct):
GROUP_CONCAT(DISTINCT linkages.tag_id) as tag_ids,
GROUP_CONCAT(DISTINCT linkages.table_name) as table_name
You can put the columns in a single field using GROUP_CONCAT():
GROUP_CONCAT(DISTINCT linkages.tag_id, ':', linkages.table_name) as tags

How to use SUM IF() in MySQL without hard coding values in the query

I have a query that uses SUM IF() to do a cross-tab result set. In this query I have the value sin the SUM IF() hard coded. The problem is that new values are added to the database. Is there a way to write the query without hard coding the values in the SUM IF()? Here is the query:
select storeid, sum(if(marketsegmentid = 6, 1, 0)) as 6,
sum(if(marketsegmentid = 7, 1, 0))
as 7, sum(if(marketsegmentid = 12, 1, 0)) as 12, sum(if(marketsegmentid = 17, 1, 0)) as 17,
sum(if(marketsegmentid = 22, 1, 0)) as 22, sum(if(marketsegmentid = 27, 1, 0)) as 27,
sum(if(marketsegmentid = 32, 1, 0)) as 32, sum(if(marketsegmentid = 37, 1, 0)) as 37,
sum(if(marketsegmentid = 42, 1, 0)) as 42, sum(if(marketsegmentid = 47, 1, 0)) as 47,
sum(if(marketsegmentid = 52, 1, 0)) as 52, sum(if(marketsegmentid = 97, 1, 0)) as 97,
sum(if(marketsegmentid = 102, 1, 0)) as 102, sum(if(marketsegmentid = 107, 1, 0)) as 107,
sum(if(marketsegmentid = 112, 1, 0)) as 112, sum(if(marketsegmentid = 117, 1, 0)) as 117,
sum(if(marketsegmentid = 122, 1, 0)) as 122, sum(if(marketsegmentid = 127, 1, 0)) as 127,
sum(if(marketsegmentid = 132, 1, 0)) as 132, sum(if(marketsegmentid = 137, 1, 0)) as 137,
sum(if(marketsegmentid = 142, 1, 0)) as 142
from storemarketsegments
group by storeid;
The query is used in a report and the results are exported to CSV. The 1's are used as flags in the result set.
The table I am querying is set up like this:
CREATE TABLE storemarketsegments(id INT NOT NULL, marketsegmentid INT NOT NULL);
The marketsegments are kept in a separate table:
CREATE TABLE marketsegment(ID INT NOT NULL AUTO_INCREMENT, PRIMARY
KEY(id), name VARCHAR(45), description VARCHAR(45));
Any help would be greatly appreciated. I am not sure if there is a way to write the query without hard coding the values and I don't mind updating the query in the report whenever new marketsegments are added but thought I would check. Thank you in advance for your assistance.
You can return each count as a separate row, and then filter as needed in the application layer:
select storeid, marketsegmentid, count(*) as Count
from storemarketsegments
group by storeid, marketsegmentid;

CakePHP Sub Querys, How To?

I have a post model (mysql table), and the structure is something like:
id, parent, user_id, title, desc, created, modified
A single thread of conversation consists of a post, followed by all subsequent posts where post.parent matches the original post_id.
Sample Conversation between user_32 and user_40:
110, 0, 32, thread_title, sample_description_here, created_time, modified_time
121, 110, 40, comment_title, sample_comment_here, created_time, modified_time
130, 110, 32, comment_title, sample_comment_here, created_time, modified_time
140, 110, 32, comment_title, sample_comment_here, created_time, modified_time
166, 110, 40, comment_title, sample_comment_here, created_time, modified_time
290, 110, 32, comment_title, sample_comment_here, created_time, modified_time
With plain PHP I simply do the outer query, followed by the inner query and then echo the conversation to the screen, but how do you achieve this with CakePHP??
QUESTION:
How do I construct the query in CakePHP, to display a single conversation composed of (see above) post_id_110, followed by all subsequent posts where post.parent == 110, ordered by created_time DESC.??
In Post model:
var $hasMany = array(
'Children'=>array(
'className' => 'Post',
'foreignKey' => 'parent_id'
)
);
(and change 'parent' to 'parent_id' in DB, just a convention). Then in posts controller:
$this->Post->find('first',array(
'conditions'=>array(...),
'contain'=>array('Children')
));
oh yeah, and use Containable. You don't really need that for the purpose, but it makes the find() clearer and definitely helpful later on.
in post model:
var $belongsTo = array(
'ParentPost' => array(
'className' => 'Post',
'foreignKey' => 'parent_id',
),
)
and then use as every other associate model
btw - parent_id is more cakeish than parent and works with scaffolds