for the sake of example:
i have the following schema:
users(id, name)
groups(id,name)
user_groups(user_id, group_id)
i want to fetch users that belong to a certain group. problem is, i know the group id, so i don't want to join the groups table - user_groups is sufficient.
the result should be
SELECT * FROM `users` AS u
LEFT JOIN `user_groups` AS ug ON (ug.`user_id` = u.`id)
WHERE ug.`group_id` = X
// i know the value of X
my attempts in doctrine2 resulted in another join of the groups table.
the closest thing i got is:
SELECT u FROM models\User u WHERE ?1 MEMBER OF u.groups
but it will also LEFT JOIN it inside the "WHERE EXISTS(...)"
can this be done without native query (using DQL/query builder)?
One of the advantages of ORM over ActiveRecord pattern is that, you don't need to create a new entity for join tables.
So here when you use Doctrine, the user_groups table will be mapped on both sides.
That means you cannot access the user_groups table directly. Unless you use native sql, which i strongly advice against.
So the best way to tackle your situation is to use findOneBy($group_id) on group repo and get the users from it. That the theoretically correct way to implement it.
-- EDIT --
For your comment:
yes, i agree that from the pure db POV its more efficient.
But you won't be able to do it without native query as DQL, query builder and everything related to doctrine uses Entities to perform action and since you are using an ORM by design, you won't have an entity for Join tables.
Else the only way is to change your mapping from a many to many relation from user to groups using join table to a one-to-many from users to user_groups and a many-to-one from user_groups to groups. That way you will have an entity for user_groups.
Then you can directly use that entity to get the users.
Even though its a hack, its not technically & theoretically correct to use like that. Unless of course the join table has other responsibilities also.
Related
I'm trying to build a basic query using symfony and doctrine. The query will return a User and all the jobs they are working on. From the two tables 'User' and UserDetails (Contains User_id and Job_id). User is mapped to userdetails correctly as one- many.
my query is
SELECT userdetails, u FROM TestBundle:User
join userdetails.u
As user is a field in userdetails, but userdetails isn't a member of users the following query doesn't work. Is there any way to write this so the result will look like User.userDetails.
Try something like
SELECT
u, ud
FROM
TestBundle:User u
JOIN //LEFT JOIN if you want also users without UserDetails
TestBundle:UserDetails ud
WITH
u.id = ud.user_id
of course your variables (like ud.user_id) could vary but we don't have enough informations to work on so we have to guess
I am using Yii2 for my project. I need to "translate" this DAO query into ActiveRecord one. Let me first show you my DAO query, and then my try with Active Record, and then I will specify what I am trying to do. I believe that everything will be clear to you, once you see DAO version.
Here is the DAO version:
return $db->createCommand("SELECT c.id, c.naziv, c.naziv_bih, c.naziv_hr, c.illustration_url,
coc.cpv_id, COUNT(coc.club_offer_id) AS offersCount, TRIM(TRAILING '0' FROM c.id) AS cpvRoot
FROM club_offer_cpv coc
JOIN cpv c ON coc.cpv_id = c.id
JOIN club_offer co ON coc.club_offer_id = co.id
JOIN club_territories ct ON co.club_id = ct.club_id
WHERE ct.teritorije_id = $territory_id
GROUP BY c.id
ORDER BY offersCount DESC
LIMIT 9 ;
")->queryAll();
And this is my try with AR:
return self::find()->select("cpv_id, COUNT(club_offer_id) AS offersCount, TRIM(TRAILING '0' FROM cpv_id) AS cpvRoot")
->with([
'cpv',
'clubOffer',
'clubOffer.clubTerritories' => function ($query) {
$query->andWhere("teritorije_id = $territory_id");
},
])
->groupBy(['cpv_id'])
->limit(9)
->orderBy(['offersCount' => SORT_DESC])
->all();
1) This query will select * from the cpv table, and I want only specific columns: c.id, c.naziv, c.naziv_bih, c.naziv_hr, c.illustration_url.
2) I need to apply this condition WHERE ct.teritorije_id = $territory_id. You can see how I tried to write this in AR, but it does not work, if I look at debugger, this conditions is not in query at all.
3) I need to apply GROUP BY and ORDER BY like you see in DAO version.
4) clubOffer is relation with table club_offer, but I am not selecting anything from it, except that I use it in DAO for joining.
5) clubTerritories is relation of club_offer table with club_territories table. I need it for WHERE part of the query.
What I am doing wrong ? Eager loading is giving me better performance, but I do not understand if I can do same things with it like with JOIN. Can you help we with this problem ? I would like to use AR but I'm having a hard time "translating" DAO into AR. Thanks
The problem is: Query->with() method you are using is not alias for join - you must still use join() if you want to filter results by related tables attribute. with method only creates extra db requests and populates/adds results as AR relations models. If you still need all your ARs including relations in results you may use join for filtering and with for attaching relations both.
I have a problem with creating optimal SQL query. I have private messages system where user can send single message to many of users or groups of users. Recipients are stored in single text column (don't ask me why is that I wasn't responsible for designing that) like that:
[60,63,103,68]
Additionaly I've added new text column where is placed group which user belongs to so I can have as a groups in database:
[55,11,11,0]
Now I want to get all users (receivers) and their groups. I have table where relation between user and group id. The problem is that single user can belong to multiple groups, for example user 60 can be in group ID 55 and 11. I would like to do it in the most optimal way (there can be 50+ receivers stored in column...) so I can write query like that:
SELECT u.name, u.last_name, g.group_name
FROM
user u
LEFT JOIN
group g ON u.id = g.user_id
WHERE
u.id IN (".$users.") and
g.id IN (".$groups.")
Unfortunately group name returned by query might by not proper - connected with the group ID i placed in WHERE. I may create PHP foreach and get user and his group using IDs I have:
foreach($user as $key => $single)
{
$sql = "...
where u.id = $single AND g.id = $group[$key] ";
}
but I think this is very bad way. Is there any way to get user and specified group in single query?
Since users and groups are only linked by their ordinal positions in the list, you need to make use of that.
The quick and dirty method would be to unnest() in parallel:
SELECT u.name, u.last_name, g.group_name
FROM (
SELECT unnest(string_to_array('3,1,2', ',')::int[]) AS usr_id -- users
, unnest(string_to_array('10,11,12', ',')::int[]) AS grp_id -- groups
) sel
JOIN usr_grp ug USING (usr_id, grp_id)
JOIN usr u USING (usr_id)
JOIN grp g USING (grp_id);
Note how I replaced SQL key words like user or group as identifiers.
-> SQLfiddle
This way, elements with the same ordinal positions in the array (converted from a comma-separated list) form a row. Both arrays need to have the same number of elements or the operation will result in a Cartesian product instead. That should be the case here, according to your description. Add code to verify if that condition might be violated.
Cleaner alternatives
While the above works reliably, it is a non-standard Postgres feature of SRF (set returning functions) which is frowned upon by some.
There are cleaner ways to do it. And the upcoming version 9.4 of Postgres will ship a new feature: WITH ORDINALITY, allowing for much cleaner code. This related answer demonstrates both:
PostgreSQL unnest() with element number
MySQL - Workbench (PHP):
Tables:
TUsers (One to many relationship with TCompanies):
TUsers_CompanyID (FOREIGN KEY)
TUsers_UserName
TUsers_UserPassword
TUsers_ID (UNIQUE)
TCompanies:
TCompanies_CompanyName
TCompanies_CompanyContactNumber
TCompanies_CompanyAddress
TCompanies_ID (UNIQUE)
Is it possible to link multiple tables in a relational database without using the JOIN, or INNER JOIN query commands, without duplicating data in tables?
Thus speaking even another way of creating a relationship that makes the one table "point" to the other's data.
So that one can query the following and successfully retrieve all the data from both tables at once:
MySQL:SELECT * FROM TUsers;
See example above..
You can do it without "appearing" to use a join (ie, the word JOIN won't be in the query), but MySQL will still perform a JOIN...
SELECT *
FROM TUsers, TCompanies
WHERE TUsers_CompanyID=TCompanies_ID;
You won't be able to escape having to use a JOIN for how you want to display your data, but what you might want to do is create what's known as a VIEW, so that you don't actually have to type out the JOIN commands whenever you want to query for the user data:
CREATE VIEW UsersView AS
SELECT *
FROM TUsers a
INNER JOIN TCompanies b ON a.TUsers_CompanyID = b.TCompanies_ID
Then once the view is defined, you can just select from UsersView like so:
SELECT * FROM UsersView
...And it will return the users information as well as the joined company information. You can think of views as a way to simplify (or "compactify") more complex queries, because underneath the hood, it's actually the same thing as:
SELECT *
FROM
(
SELECT *
FROM TUsers a
INNER JOIN TCompanies b ON a.TUsers_CompanyID = b.TCompanies_ID
) UsersView
Imagine a table for articles. In addition to the main query:
SELECT * From articles WHERE article_id='$id'
We also need several other queries to get
SELECT * FROM users WHERE user_id='$author_id' // Taken from main query
SELECT tags.tag
FROM tags
INNER JOIN tag_map
ON tags.tag_id=tag_map.tag_id
WHERE article_id='$id'
and several more queries for categories, similar articles, etc
Question 1: Is it the best way to perform these queries separately with PHP and handle the given results, or there is way to combine them?
Question 2: In the absence of many-to-many relationships (e.g. one tag, category, author for every article identified by tag_id, category_id, author_id); What the best (fastest) was to retrieve data from the tables.
If all the relationships are one-many then you could quite easily retrieve all this data in one query such as
SELECT
[fields required]
FROM
articles a
INNER JOIN
users u ON a.author_id=u.user_id
INNER JOIN
tag_map tm ON tm.article_id=a.article_id
INNER JOIN
tags t t.tag_id=tm.tag_id
WHERE
a.article_id='$id'
This would usually be faster than the three queries separately along as your tables are indexed correctly as MySQL is built to do this! It would save on two round trips to the database and the associated overhead.
You can merge in the user in the first query:
SELECT a.*, u.*
FROM articles a
JOIN users u ON u.user_id = a.author_id
WHERE a.article_id='$id';
You could do the same with the tags, but that would introduce some redundancy in the answer, because there are obviously multiple tags per article. May or may not be beneficial.
In the absence of many-to-many relationships, this would do the job in one fell swoop and would be superior in any case:
SELECT *
FROM users u
JOIN articles a ON a.author_id = u.user_id
JOIN tag t USING (tag_id) -- I assume a column articles.tag_id in this case
WHERE a.article_id = '$id';
You may want to be more selective on which columns to return. If tags ar not guaranteed to exist, make the second JOIN a LEFT JOIN.
You could add an appropriately denormalized view over your normalized tables where each record contains all the data you need. Or you could encapsulate the SQL calls in stored procedures and call these procs from your code, which should aid performance. Prove both out and get the hard figures; always better to make decisions based on evidence rather that ideas. :)