I need to write an ActiveRecord query where I get all fields of rows without duplicates in one field.
Example: Here is a table of books. I want to get all data of the rows with
distinct isbn. From duplicates the first row should be taken. The result should be the rows with id 1,2,4
id | title | isbn
--- | ------- | ------
1 | hello | 1001
2 | world | 1002
3 | this | 1002
4 | is | 1003
5 | funny | 1003
My first attempt was
$books = Books::find()->select('isbn')->distinct()->all();
but that fills only the isbn field in $books[0], $books[1], ....
Is this possible using Yii2 ActiveRecord?
You may use groupBy() for this:
$books = Books::find()->groupBy(['isbn'])->all();
But this returns random row with matching isbn value and may not work in some DBMS. Your requirements are too ambiguous to handle it in predictable way. But if you want always first row, you may use subquery to fetch matching isbn and id:
$query = (new Query())
->select(['MIN(id) as id, isbn'])
->from(Books::tableName())
->groupBy(['isbn']);
return Books::find()
->alias('book')
->innerJoin(['subquery' => $query], 'book.id = subquery.id AND book.id = subquery.id')
->all();
The description given in Yii2 for the distinct property is as follows:
Whether to select distinct rows of data only. If this is set true, the SELECT clause would be changed to SELECT DISTINCT.
-- https://www.yiiframework.com/doc/api/2.0/yii-db-query#$distinct-detail
therefore you should pass true with distinct property if you need to select the distinct values of 'isbn' as follows:
$books = Books::find()->select('isbn')->distinct(true)->all();
Related
Im trying the following:
I have a model (HbwerllMargins) with the following fields:
id| userId | clinicId | payMethodId | percentage | createdTime | updatedTime | deletedTime
Im trying the following query:
$hbwellMargins = HbwellMargins::find()->andWhere(['clinicId' => $id])->all();
Where result is associated to a certain clinicId value, but what I need is also to group the result by the same userId values without specifying a value.
For example, for clinicId=1, userId = 1 has 6 registers, userId = 2, has 1 register, etc
Im trying to find out in ActiveQuery Documentation but I havent found yet a solution...what I would need to do that? Is there any way to do that?
Thans a lot for your help!!
You can group the result by the same userId values like this:
If you just want to fetch all records and group the results:
$hbwellMargins = HbwellMargins::find()->orderBy('userId')->all();
If you want to fetch results with your condition and group the results:
$hbwellMargins = HbwellMargins::find()->where(['clinicId' => $id])->orderBy('userId')->all();
I have Two tables in ManyToMany relation:
Table Molécules:
id | main_name | others …
--- | --------- | ------
1 | caféine | others …
Table jsonTextMining:
id | title | molecule_name | others …
---|------- |-------------------------------------|------
1 | title1 | colchicine, cellulose, acid, caféine| others …
and 1 intermediate table:
Table json_text_mining_molecule (it's an exemple i don't succeed to fill it):
json_text_mining_id | molecule_id
------------------------ | ---------------
1 | corresponding molecule id's
1 | corresponding molecule id's
2 | corresponding molecule id's
My problem is that molecule_name in jsonTextMining are a string, i need to separate them before anything.
I tried this :
$molecules = explode (', ', $jsonTextMining→getMoleculeName());
foreach ($molecules as $molecule) {
$jsonTextMining->setMolecule($molecule);
}
$em->persist($jsonTextMining);
$em->flush;
But i think i should loop on jsonTexMining too and to be honnest i'm not sure where to put this part of code. Is it on a random page and the code will execute, should i do a button ?
I know exactly how to fill table with id's when there is a OneToMany relation, i use sql like this :
UPDATE table1 SET id_relation1 = table2.id
FROM table2
WHERE table1.main_name = table2.main_name
But this code fill only one column with id and there's always the matter of string. Is there really a way to get these id's linked so every molecule will have several jsonTextMining ?
You can first split the string using regexp_split function:
select id, regexp_split_to_table(molecule_name,', ') as m_name from jsonTextMining
That will give you a table of ids and names:
id | name
----+------------
1 | acid
1 | caffeine
1 | cellulose
1 | colchicine
Next, you can read from the above, match the names to the ids in the molecule table and aggregate the ids. All put together would result in this:
select s.id, string_agg(m.id::text, ', ')
from (select id, regexp_split_to_table(molecule_name,', ') as m_name
from jsonTextMining) as s, molecules m
where m.main_name = s.m_name group by s.id;
Which gives this result:
id | string_agg
----+------------
1 | 4, 1, 3, 2
(1 row)
If you don't want to aggregate the results and display them one row per molecule then just get rid of string_agg and the group by:
select s.id, m.id
from (select id, regexp_split_to_table(molecule_name,', ') as m_name
from jsonTextMining) as s, molecules m
where m.main_name = s.m_name;
I'm building Laravel 5.4 web application and I have below database table:
==================================
product_id|attribute_id|option_id
==================================
1 | 1 | 1
1 | 2 | 3
1 | 4 | 10
2 | 1 | 1
2 | 2 | 4
... etc
So i submit form with attributes id and options id so i can build array from it or whatever.
What i want to achieve that I select from the database the product_id which match exact combination for example:
[
attribute_id => 1,
option_id => 1
attribute_id => 2,
option_id => 3
attribute_id => 4,
option_id => 10
]
This condition only apply to product with product_id = 1
Don't know if i can do it using database query or by php.
First, make a model that reflects your data. Then use the Eloquent query builder to get the data you're looking for. If you need just one number returned that matches, make sure to add on to the end the query "->distinct()".
You may also pass an array of conditions to the 'where' clause.
Your code may look something like this:
$match = DB::table('products')
->where('attribute_id', 1)
->where('option_id', 1)
->distinct()
->get();
https://laravel.com/docs/5.4/queries#introduction
If you just want the product with product_id = 1
Assumed you have stored this in "product_attribute_option" table
and its fields are product_id |attribute_id | option_id as you shown.
$query = DB::table('product_attribute_option as pao');
$query->where('pao.product_id', 1);
$results = $query->get();
I have two tables in Laravel of which I am seeking to merge them together, however, I want to return every single value of the first table (without duplicates) along with only values from the second table that have a FK value of 2. If there is no entry with a FK of 2, it joins with a value of null.
To make my question a little more clear, lets say we have the following tables:
TV Shows Table
ID | Show
1 | First Show
2 | Second Show
3 | Third Show
Favorites Table
Show_ID | Member_ID
1 | 1
3 | 1
1 | 2
2 | 2
I am looking to merge them into a resultant set like the following when I join the tables with a member ID of 2(disregarding the joined 'Show_ID' column):
Merged Table
ID | Show | Member_ID
1 | First Show | 2
2 | Second Show | 2
3 | Third Show | null
Thanks.
Not 100% I understood, so hope this is what you're looking for.
I've renamed some of the column names to make things a little clearer.
DB::table('shows')
->select(
'shows.id as show_id',
'shows.name as show_name',
'member.id as member_id'
)
->leftJoin('favourites', 'shows.id', '=', 'favourites.show_id')
->get();
Left join will allow there to be null in member_id if it isn't present on the join.
You can add this to restrict to member ID of two:
DB::table('shows')
->select(
'shows.id as show_id',
'shows.name as show_name',
'member.id as member_id'
)
->leftJoin('favourites', 'shows.id', '=', 'favourites.show_id')
->where('member.id', 2)
->get();
I solved it myself. I needed to do a functional join like so:
DB::table('shows')
->leftJoin('favorites', function($q) use $memberID)
{
$q->on('shows.ID', '=', 'favorites.show_ID')
->where('favorites.member_ID', '=', $memberID);
})->get();
Use table with structure:
id | count
/string/id1 | 3
/string/id1/r1 | 2
/string/id1/r2 | 1
/string/id2/r1 | 2
/string/id2 | 3
/string/id2/r1 | 2
/string/id3/r1 | 5
and I want to select all rows which have needed substring in id.
i.e.
I need all rows which have substring in id: /string/id1 and /string/id2
The query should be easy - plain sql:
select * from table_name where id LIKE '/string/id1%' OR id LIKE '/string/id2%';
Result should be:
id | count
/string/id1 | 3
/string/id1/r1 | 2
/string/id1/r2 | 1
/string/id2/r1 | 2
/string/id2 | 3
/string/id2/r1 | 2
Unfortunately, if you try to use the same query in symfony and doctrine2:
$ids = array('/string/id1', '/string/id2');
$query = $this->createQueryBuilder('r')
->select('r');
foreach ($ids as $id) {
$query->orWhere("r.linkedId LIKE :id ")
->setParameter('id', $id."%");
}
$plainQuery = $query->getQuery()->getSQL();
$results = $query->getQuery()->getResult();
Plain query looks the same like select * from table_name where id LIKE '/string/id1%' OR id LIKE '/string/id2%';, but results are not.
results contains only rows of last item in ids - /string/id2
id | count
/string/id2/r1 | 2
/string/id2 | 3
/string/id2/r1 | 2
How to solve it? Where is my mistake? Do you have any suggestion?
I cannot be sure but this seems to me like a conflict with parameter identifiers.
This is what you're trying to do:
Construct the basic SELECT * FROM table_name statement
Append WHERE r.linkedId LIKE :id
set the value of id parameter to /string/id1%
Append OR r.linkedId LIKE :id
set the value of id parameter to /string/id2% (override the previous value) <-- AN ERROR
Basically, you are telling Doctrine to override previously defined value of id parameter with new one.
You could easily overcome this issue. Just add $i to parameter name
foreach ($ids as $i => $id) {
// $i here has the value of 0,1,2, etc...
$query->orWhere("r.linkedId LIKE :id$i" ) // append $i
->setParameter("id$i", $id."%"); // but also append it here
}
Be sure to use double quotes, or concatenate ("id" . $i) instead ;)