Grails HQL representation of a query - mysql

I'm stuck representing in HQL the following MySQL query
SELECT t1.id as user_id, t2.id as follow_id
FROM user t1
INNER JOIN follows t2 ON
(t2.follower_id = <user id> AND t2.followee_id = t1.id) OR
(t2.follower_id = t1.id AND t2.followee_id = <user id>)
WHERE t1.active = true
ORDER BY t1.id
Can you please help me out with the representation?
Thanks!
Edit:
So far I've tried the following HQL query, but it fails to return the proper data:
SELECT t1
FROM User t1
JOIN t1.follows t2
JOIN t2.follower follower
JOIN t2.followee followee
WHERE (follower.id = :usr AND followee.id = t1.id) OR
(follower.id = t1.id AND followee.id = :usr)
AND t1.active = true
ORDER BY t1.id
The idea is that I have two tables:
User table:
+------+--------+-------------+
| id | name | last_name |
+------+--------+-------------+
| 1 | Jhon | Doe |
| 2 | Jane | Doe |
| 3 | Alfred | Hitchcock |
+------+--------+-------------+
Follows table:
+------+-------------+-------------+
| id | follower_id | followee_id |
+------+-------------+-------------+
| 1 | 1 | 3 |
| 2 | 3 | 2 |
| 3 | 2 | 1 |
+------+-------------+-------------+
What I need is, for example, obtain the name and last name of all users who follow or are being followed by, let's say, user id 1.
The SQL query I posted does exactly this, but I cannot seem to replicate the same behaviour in HQL.
2nd Edit:
My simplified domain classes are:
class User {
String name
String lastName
...
static hasMany = [ follows: Follows ]
static mappedBy = [ follows: 'follower' ]
}
And...
class Follows {
User Followee
...
static belongsTo = [follower: User]
}

The important thing to keep in mind when writing HQL queries is that domain class joins (HQL deals with domain classes, not tables) are completely reliant on the domain class associations. To put it differently, the associations actually create the (inner) joins, but HQL allows you to change the join type (LEFT OUTER, RIGHT OUTER, and Cartesian). See my from clause article.
I attempted to derive your domain class associations from your HQL and table descriptions and it just doesn't match up. But here's another way to do it.
Domain model
The domain model is very simple. There's a User class containing a one-to-many association to itself (but it's not a self-join, as you'll see in a moment).
class User {
String name
String lastName
static hasMany = [followees: User]
}
Lets say you have a User instance.
def user = User.get(1)
user.followees contains the Users being followed by user.
Table schema
With such a domain model the tables would look something like this:
user table
+------+--------+-------------+
| id | name | last_name |
+------+--------+-------------+
| 1 | John | Doe |
| 2 | Jane | Doe |
| 3 | Alfred | Hitchcock |
+------+--------+-------------+
user_followees table
+-----------+-------------------+
| user_id | followees_user_id |
+-----------+-------------------+
| 1 | 2 |
| 3 | 1 |
+-----------+-------------------+
The data demonstrated suggests that:
John Doe follows Jane Doe.
Jane Doe is followed by John Doe.
Alfred Hitchcock follows John Doe.
John Doe is followed by Alfred Hitchcock.
This simple one-to-many association can be used to model the two-way relationship that is known as following. The same could be done with two one-to-many associations, which would simplify querying but complicate data maintenance. With this model the two-way following relationships are maintained with a single user.addToFollowee() or user.removeFromFollowee().
Querying
Using the domain model I described, you can query for followees and followers. Given a user...
def followees = user.followees // <-- contains Jane Doe
def followers = User.executeQuery
'SELECT u FROM User as u INNER JOIN u.followees as followee WHERE followee = :user',
[user: user] // <-- contains Alfred Hithchcock
def following = followees + followers
Note that GORM/Hibernate does not support SQL's UNION clause, so you'll need two queries. See my where clause article for more details on GORM's equivalent of the where clause.
Followers
It's also possible to use a where or criteria query to get the followers:
def followers = User.where { followees.id == user.id }.list()
def followers = User.withCriteria { followees { eq 'id', user.id } }

Just for the sake of simplicity and since I was only reading data, I ended up using Groovy SQL which is accepted by Grails and itself accepts the plain SQL query I included in my original post (and doesn't require to match any specific domain model, big plus for me in this scenario)
Here's a link to the JavaDoc, I specifically used the rows(String sql, Map params) method definition to query de DB and return the data I was searching for.
For using groovy.sql.Sql in Grails, I recommend adding it as a bean to the resources.groovy file:
beans = {
groovySql(groovy.sql.Sql, ref('dataSource'))
}
and then inject the same into wherever you wanna use it:
class MyService {
def groovySql
...
def myMethod() {
String query = "SELECT * FROM user WHERE ..."
Map params = [param1: p1]
List<GroovyRowResult> result = groovySql.rows(query, params)
...
}
}
Hope this helps anyone trying to perform plain SQL queries without the hassle of having to convert them to HQL or Criteria.

Related

How can I add a group by to a hasMany relationship in laravel 5.3?

I have an elquent model named Conversation.
In that model I define a hasMany relationship on an eloquent model CallParticipant like so:
public function participants(){
return $this->hasMany('App\Models\Purecloud\Analytics\CallParticipant', 'conversationId', 'conversationId');
}
Now, CallParticipants can be system processes, customers, or agents. I need a list of all the CallParticipants that are considered "agents" so I defined another relation like this:
public function agents(){
return $this->hasMany('App\Models\Purecloud\Analytics\CallParticipant', 'conversationId', 'conversationId')
->whereIn('partPurpose', ['agent','user']);
}
Now, an "agent" can be a "participant" multiple times on a "conversation" that is to say there can be multiple rows for the same agent just with a different participantId, like this:
+----------------+---------------+--------------+-------------+
| conversationId | participantId | partUserName | partPurpose |
+----------------+---------------+--------------+-------------+
| 1 | 100 | Alex | agent |
| 1 | 101 | Mary | agent |
| 1 | 102 | Alex | agent | <-- I want to exlcude this
+----------------+---------------+--------------+-------------+
I need to remove these sortof-duplicates so that the relationship only returns one row for each partUserName (one row for Mary, and one row for Alex).
I tried to do this by adding a groupBy like this:
public function agents(){
return $this->hasMany('App\Models\Purecloud\Analytics\CallParticipant', 'conversationId', 'conversationId')
->whereIn('partPurpose', ['agent','user'])
->groupBy('partUserName');
}
But this produces the error:
SQLSTATE[42000]: Syntax error or access violation: 1055 'analytics.callParticipants.participantId' isn't in GROUP BY
I also tried doing it in the with statement like below, but I get the same error:
$conversations = Conversation::with(
[
'agents' => function($query){
$query->groupBy('partUserName');
}
])
->get();
Is there any way I can limit the relationship to only rows with unique partUserName
I think that the only way to do this is by "hacking" the relation. Could you try:
public function agents(){
return $this->hasOne('App\Models\Purecloud\Analytics\CallParticipant', 'conversationId', 'conversationId')
->whereIn('partPurpose', ['agent','user'])
->latest();
}

Laravel Eloquent and 3 table relationship

I'm looking for some help in working out how to setup the Eloquent relationships for my application.
I have created migrations for three tables.
| users | | items | | user_items |
+-----------+ +-----------+ +------------+
| id | | id | | id |
| username | | name | | user_id |
| item_id |
| qty |
I have setup an unique index on the user_items table, limiting 1 user id to 1 item id, but with a qty column. I want to setup Eloquent so that I have two models, User and Item. Where I could say:
$item = $user->items()->first();
$name = $item->name;
$qty = $item->qty;
The problem is I'm trying to figure out if I need a 3rd model, UserItem or not.
What are you doing here is actually a M:M relationships, and Laravel Eloquent already have support for that out of the box. The user_item table you have is referred to as the pivot table.
Here's how to setup your relationship using Eloquent.
// User class
class User extends Eloquent {
public items() {
return $this->belongsToMany(Item::class)->withPivot('qty');
}
}
class Item extends Eloquent {
public function users() {
return $this->belongsToMany(User::class)->withPivot('qty');
}
}
For this to work, you will need three migrations:
one for the user table
one for the item table
one for the item_user table. In Eloquent, the pivot table consists of the two tables names in alphabetical order. separated with a dash.
following Eloquent conventions, if you set the FK to use _id, Eloquent will not need additional information. So, in your case, the FK in the item_user table should be user_id and item_id respectively.
There is no need for a ItemUser model. You can access the pivot in the relationship by doing:
$user = User::find(1);
$user->items[0]->pivot->qty; // retrieve the quantity

export phpList subscribers via sql in mysql database

For some reason, I am unable to export a table of subscribers from my phpList (ver. 3.0.6) admin pages. I've searched on the web, and several others have had this problem but no workarounds have been posted. As a workaround, I would like to query the mySQL database directly to retrieve a similar table of subscribers. But I need help with the SQL command. Note that I don't want to export or backup the mySQL database, I want to query it in the same way that the "export subscribers" button is supposed to do in the phpList admin pages.
In brief, I have two tables to query. The first table, user contains an ID and email for every subscriber. For example:
id | email
1 | e1#gmail.com
2 | e2#gmail.com
The second table, user_attribute contains a userid, attributeid, and value. Note in the example below that userid 1 has values for all three possible attributes, while userid's 2 and 3 are either missing one or more of the three attributeid's, or have blank values for some.
userid | attributeid | value
1 | 1 | 1
1 | 2 | 4
1 | 3 | 6
2 | 1 | 3
2 | 3 |
3 | 1 | 4
I would like to execute a SQL statement that would produce a row of output for each id/email that would look like this (using id 3 as an example):
id | email | attribute1 | attribute2 | attribute3
3 | e3#gmail.com | 4 | "" | "" |
Can someone suggest SQL query language that could accomplish this task?
A related query I would like to run is to find all id/email that do not have a value for attribute3. In the example above, this would be id's 2 and 3. Note that id 3 does not even have a blank value for attributeid3, it is simply missing.
Any help would be appreciated.
John
I know this is a very old post, but I just had to do the same thing. Here's the query I used. Note that you'll need to modify the query based on the custom attributes you have setup. You can see I had name, city and state as shown in the AS clauses below. You'll need to map those to the attribute id. Also, the state has a table of state names that I linked to. I excluded blacklisted (unsubscribed), more than 2 bounces and unconfirmed users.
SELECT
users.email,
(SELECT value
FROM `phplist_user_user_attribute` attrs
WHERE
attrs.userid = users.id and
attributeid=1
) AS name,
(SELECT value
FROM `phplist_user_user_attribute` attrs
WHERE
attrs.userid = users.id and
attributeid=3
) AS city,
(SELECT st.name
FROM `phplist_user_user_attribute` attrs
LEFT JOIN `phplist_listattr_state` st
ON attrs.value = st.id
WHERE
attrs.userid = users.id and
attributeid=4
) AS state
FROM
`phplist_user_user` users
WHERE
users.blacklisted=0 and
users.bouncecount<3 and
users.confirmed=1
;
I hope someone finds this helpful.

MySQL: Possible to apply the OR operator across multiple selected rows?

I have three MySQL tables: users, roles and positions.
The users table is pretty self-explanatory. The roles table is a list of job titles a person might hold, such as janitor, president, manager, etc. The roles table also has a long array of boolean permissions, such as access_basement or user_directory_access. If the role has has that bit value set to false (or "0") that role lacks that permission.
Where it gets tricky is that a user might have multiple roles, hence why they are connected by the positions table, which is simply a pairing of the userId and roleId fields. So if I perform a query like:
SELECT * FROM users
LEFT JOIN positions ON users.userId=positions.userId
LEFT JOIN roles ON roles.roleId=positions.roleId
WHERE users.userId=123
I might get results like:
+---------+-----------+-----------------+-----------------------+
| name | title | basement_access | user_directory_access |
+---------+-----------+-----------------+-----------------------+
| Bob | Janitor | true | false |
+---------+-----------+-----------------+-----------------------+
| Bob | President | false | true |
+---------+-----------+-----------------+-----------------------+
Since Bob has two roles, but has different access with each, I'd like to combine the results with a since MySQL query and the logical OR operation across all rows, resulting in a table like:
+---------+-----------------+-----------------------+
| name | basement_access | user_directory_access |
+---------+-----------------+-----------------------+
| Bob | true | true |
+---------+-----------------+-----------------------+
So the question is: is it possible to apply the OR operator across multiple selected MySQL rows?
Thanks!
One way to solve this is if you use values 0 and 1 as role permissions. And use some query like:
SELECT u.name, SUM(r.basement_access) AS basement_access, SUM(r.user_directory_access) AS user_directory_access
FROM users u
LEFT JOIN positions p ON u.userId=p.userId
LEFT JOIN roles r ON r.roleId=p.roleId
WHERE u.userId=123
GROUP BY u.userId;

How do I store URL fragments in a database?

How are URLs (fragments) stored in a relational database?
In the following URL fragment:
~/house/room/table
it lists all the information on a table, and perhaps some information about the table.
This fragment:
~/house
outputs: Street 13 and Room, Garage, Garden
~/house/room
outputs: My room and Chair, Table, Window
What does the Database schema looks like? What if I rename house to flat?
Possible solution
I was thinking that I could create a hash for the URL and store it along with parentID and information. If I rename some upper-level segment I would then need to update all the rows which contain the given segment.
Then I thought would store each segment along with information and its level:
SELECT FROM items WHERE key=house AND level=1 AND key=room AND level=2
How do I solve this problem if the URL can be arbitrarily deep?
check The Adjacency List Model and The Nested Set Model described in Joe Celko's Trees and Hierarchies in SQL for Smarties
you should find plenty information to this topic. one article is here
Update
The Nested Set Model is very good if you are looking for a task like 'Retrieving a Single Path'. What you have is 'Find the Immediate Subordinates of a Node'. Here the Adjacency List Model is better.
| id | p_id | name |
| 1 | null | root |
| 2 | 1 | nd1.1 |
| 3 | 2 | nd1.2 |
| 4 | 1 | nd2.1 |
SQL to get a row with name of a fragment and it's direct sub items.
SELECT
p.name,
GROUP_CONCAT(
c.name
SEPARATOR '/'
) AS subList
FROM _table p
INNER JOIN _table c
ON p.id = c.p_id
WHERE p.name = 'root'
P.S. prefer WHERE p.id = 1. Id is unique where as name can be ambiguous.
see MySQL GROUP CONCAT function for more syntax details.