I have a query very similar to this:
SELECT FROM Post
LET
$category = (SELECT EXPAND(out('PartOf')) FROM $current),
$poster = (SELECT EXPAND(in('Posted')) FROM $current)
WHERE
$poster <> #18:1
AND $poster IN (SELECT EXPAND(out('IsUser')) FROM (SELECT EXPAND(out('Related')) FROM #18:1) WHERE out('RelatedIn') IN ($category))
LIMIT 10000
And a graph like this:
CREATE CLASS Post EXTENDS V;
CREATE CLASS Category EXTENDS V;
CREATE CLASS User EXTENDS V;
CREATE CLASS UserRelated EXTENDS V;
CREATE CLASS PartOf EXTENDS E;
CREATE CLASS IsUser EXTENDS E;
CREATE CLASS Posted EXTENDS E;
CREATE CLASS Related EXTENDS E;
CREATE CLASS RelatedIn EXTENDS E;
PartOf relates Post and Cateogry
IsUser relates UserRelated and User
Posted relates User and Post
Related relates User and UserRelated
RelatedIn relates UserRelated and Category
UserRelated is an hyper-edge
What I'm looking for are posts, from users related to another user (#18:1), where the relation between the users is in the same category as the category of the post the query is traversing at that moment.
The where clause after the AND does not work. If I remove it, the query gives the same results.
On the other side, if I change $category in that clause with a specific #rid, the query works perfectly... but I need more than one specific category.
So, what is wrong with this query?
(I hope this explanation is clear enough)
Thanks
It seems that $category cannot be accessed from the where. I really don't know why.
But I solved it using another varaible:
SELECT FROM Post
LET
$category = (SELECT EXPAND(out('PartOf')) FROM $current),
$poster = (SELECT EXPAND(in('Posted')) FROM $current),
$relatedUser = (SELECT EXPAND(out('IsUser')) FROM (SELECT EXPAND(out('Related')) FROM #18:1) WHERE out('RelatedIn') IN ($parent.category))
WHERE
$poster <> #18:1
AND $poster IN ($relatedUser)
LIMIT 10000
The key was using $parent.category inside the subquery. It was not so clear to me that a declared variable in the parent should be used with $parent.variable in any subqueries.
I hope this helps someone in the future!
Related
I'm using SpringBoot 2.4 with JPA 2.0 and I have a Model like following:
#Entity
#Data
public class Nation {
#Id
#GeneratedValue (strategy = GenerationType.IDENTITY)
private Integer id;
#OneToMany(mappedBy = "nation")
private List<Country> country;
}
And:
#Entity
#Data
public class Country {
#Id
#GeneratedValue (strategy = GenerationType.IDENTITY)
private Integer id;
#ManyToOne
private Nation nation;
}
Now I would to like to find all Nation filtered by their ID and by country ID. In pure SQL is simply something like:
select * from nation n, country c where n.id = [nation_id] AND c.id = [country_id];
Therefore I thought about doing this way with JPA:
#Query("select n from Nation n JOIN n.country c where n.id = ?1 AND c.id = ?2)
public List<Nation> find(Integer nationID, Integer countryID);
But it doesn't work; it is filtered by Nation but not by countries.
If I print the Hibernate generate SQL by adding:
spring.jpa.show.sql=true
I can see that the query is exactly the same I posted above in pure SQL. The problem occours when I invoke nation.getCountry(), it generates another query that load all country connected to given Nation id.
Is there a way to solve this?
I believe that you can use JPA DTO projections for this case ...
So, in your Nation class create a constructor like this:
/**
* Copy Constructor. It creates a "detached" copy of the
* given nation with only a copy of the provided country.
*/
public Nation(Nation n, Country c) {
super();
this.id = n.id;
// copy other nation values ...
this.country.add( new Country(c) );
}
Modify your query to invoke such constructor ... something like this (assuming that Nation is declared in the java package my.domain):
#Query("select new my.domain.Nation(n, c) from Nation n JOIN n.country c where n.id = ?1 AND c.id = ?2)
Disclaimer:
I have done this using JPA and hibernate. So far, I haven't tested with spring, but I guess this does not matter because your JPA provider is probably hibernate too.
I have done this using only parts (or attributes) of the target entities (as described in the provided link) ... I never have pass the full entities (as I suggest to do in the query). Let me explain, in the cases where I have applied this, I have a constructor like Nation(String name, int population) and in the query I do something like: SELECT new my.domain.Nation(n.name, c.population) ... Try to pass the full entities to see if it works ... if it fails, fallback to create a constructor that receives only the attributes that you require for your business case.
I have two tables: users (Users) and groups (Groups).
Users
-----------------
id | username | group
1 | Bob | 2
Groups
-----------------
id | name
1 | firstgroup
2 | secondgroup
I would like to display: users.ID, users.username, group.name (1, Bob, secondgroup)
An SQL statement like so would work:
SELECT Users.id, Users.username, Groups.name
FROM Users
INNER JOIN
Groups ON Groups.id = Users.group
However, I'm struggling to write this in Eloquent, since there is no "FROM". At the moment I'm going for something along the lines of the below, using JOINS (http://laravel.com/docs/queries#joins)
$users = Users::select('id','username', 'Groups.name')->joins('Groups.id', '=', 'id')->get();
Now this isn't working - I think the joins has to come before the select but I just can't work it out :(
I think you're confusing a few things here...
You're mixing Eloquent with the lower-level DB::table('foo')->select() syntax. When you want to use Eloquent I suggest you take a look at the docs about relationships in Eloquent.
You should define your models like so:
class User extends Eloquent {
public function group()
{
return $this->belongsTo('Group', 'group');
// second parameter is necessary because you didnt
// name the column "group_id" but simply "group"
}
}
class Group extends Eloquent {
public function users()
{
return $this->hasMany('User', 'group');
}
}
This sets up all the joins you might be needing later. You can then simply use User::with('group')->all(); and have the query built and run for you.
Database: Query Builder(DB) is not a Eloquent(ORM):
Database query builder you have to inform the table names and the fields, like it says on in your related link of laravel docs: "...provides a convenient, fluent interface to creating and running database queries." like these query below:
$users = DB::table('users')
->join('contacts', 'users.id', '=', 'contacts.user_id')
->join('orders', 'users.id', '=', 'orders.user_id')
->select('users.*', 'contacts.phone', 'orders.price')
->get();
Eloquent is a ORM - Object related Mapping, it means that your class User is related to the table users (look at you files Migrations) and this class extends the Model Class, thus you can access the methods like these bellow:
class User extends Models
{
public static function usersWithGroups(){
return User::select('id', 'name', 'email')->with('groups')->get();
}
}
Observe that method is into the class User, so you can access that in a static way "User::", using Eloquent you'll have many hidden static methods that will improve you time codding, because you are inheriting de Model methods, to more details visit the Eloquent Docs at: Eloquent Docs
First off, I'm a Ruby/Rails newbie, so I apologize if this question is basic.
I've got a DB that (among other things) looks like this:
organizations { id, name, current_survey_id }
surveys { id, organization_id }
responses { id, survey_id, question_response_integer }
I'm trying to create a scope method that adds the average of the current survey answers to a passed-in Organization relation. In other words, the scope that's getting passed into the method would generate SQL that looks like more-or-less like this:
select * from organizations
And I'd like the scope, after it gets processed by my lambda, to generate SQL that looks like this:
select o.id, o.name, cs.average_responses
from organizations o join
(select r.id, avg(r.question_response_integer) as average_responses
from responses r
group by r.id) cs on cs.id = o.current_survey_id
The best I've got is something like this:
current_survey_average: lambda do |scope, sort_direction|
average_answers = Responses.
select("survey_id, avg(question_response_integer) as average_responses").
group("survey_id")
scope.joins(average_answers).order("average_responses #{sort_direction}")
end
That's mostly just a stab in the dark - among other things, it doesn't specify how the scope could be expected to join to average_answers - but I haven't been able to find any documentation about how to do that sort of join, and I'm running out of things to try.
Any suggestions?
EDIT: Thanks to Sean Hill for the answer. Just to have it on record, here's the code I ended up going with:
current_survey_average: lambda do |scope, sort_direction|
scope_table = scope.arel.froms.first.name
query = <<-QUERY
inner join (
select r.survey_id, avg(r.question_response_integer) as average_responses
from responses r
group by r.survey_id
) cs
on cs.survey_id = #{scope_table}.current_survey_id
QUERY
scope.
joins(query).
order("cs.average_responses #{sort_direction}")
end
That said, I can see the benefit of putting the averaged_answers scope directly onto the Responses class - so I may end up doing that.
I have not been able to test this, but I think the following would work, either as-is or with a little tweaking.
class Response < ActiveRecord::Base
scope :averaged, -> { select('r.id, avg(r.question_response_integer) as average_responses').group('r.id') }
scope :current_survey_average, ->(incoming_scope, sort_direction) do
scope_table = incoming_scope.arel.froms.first.name
query = <<-QUERY
INNER JOIN ( #{Arel.sql(averaged.to_sql)} ) cs
ON cs.id = #{scope_table}.current_survey_id
QUERY
incoming_scope.joins(query).order("average_responses #{sort_direction}")
end
end
So what I've done here is that I have split out the inner query into another scope called averaged. Since you do not know which table the incoming scope in current_survey_average is coming from, I got the scope table name via scope.arel.froms.first.name. Then I created a query string that uses the averaged scope and joined it using the scope_table variable. The rest is pretty self-explanatory.
If you do know that the incoming scope will always be from the organizations table, then you don't need the extra scope_table variable. You can just hardcode it into the join query string.
I would make one suggestion. If you do not have control over sort_direction, then I would not directly input that into the order string.
I'm really struggling on this one.
I need to be able to sort my user by the number of positive vote received on their comment.
I have a table userprofile, a table comment and a table likeComment.
The table comment has a foreign key to its user creator and the table likeComment has a foreign key to the comment liked.
To get the number of positive vote a user received I do :
LikeComment.objects.filter(Q(type = 1), Q(comment__user=user)).count()
Now I want to be able to get all the users sorted by the ones that have the most positive votes. How do I do that ? I tried to use extra and JOIN but this didn't go anywhere.
Thank you
It sounds like you want to perform a filter on an annotation:
class User(models.Model):
pass
class Comment(models.Model):
user = models.ForeignKey(User, related_name="comments")
class Like(models.Model):
comment = models.ForeignKey(Comment, related_name="likes")
type = models.IntegerField()
users = User \
.objects \
.all()
.extra(select = {
"positive_likes" : """
SELECT COUNT(*) FROM app_like
JOIN app_comment on app_like.comment_id = app_comment.id
WHERE app_comment.user_id = app_user.id AND app_like.type = 1 """})
.order_by("positive_likes")
models.py
class UserProfile(models.Model):
.........
def like_count(self):
LikeComment.objects.filter(comment__user=self.user, type=1).count()
views.py
def getRanking( anObject ):
return anObject.like_count()
def myview(request):
users = list(UserProfile.objects.filter())
users.sort(key=getRanking, reverse=True)
return render(request,'page.html',{'users': users})
Timmy's suggestion to use a subquery is probably the simplest way to solve this kind of problem, but subqueries almost never perform as well as joins, so if you have a lot of users you may find that you need better performance.
So, re-using Timmy's models:
class User(models.Model):
pass
class Comment(models.Model):
user = models.ForeignKey(User, related_name="comments")
class Like(models.Model):
comment = models.ForeignKey(Comment, related_name="likes")
type = models.IntegerField()
the query you want looks like this in SQL:
SELECT app_user.id, COUNT(app_like.id) AS total_likes
FROM app_user
LEFT OUTER JOIN app_comment
ON app_user.id = app_comment.user_id
LEFT OUTER JOIN app_like
ON app_comment.id = app_like.comment_id AND app_like.type = 1
GROUP BY app_user.id
ORDER BY total_likes DESCENDING
(If your actual User model has more fields than just id, then you'll need to include them all in the SELECT and GROUP BY clauses.)
Django's object-relational mapping system doesn't provide a way to express this query. (As far as I know—and I'd be very happy to be told otherwise!—it only supports aggregation across one join, not across two joins as here.) But when the ORM isn't quite up to the job, you can always run a raw SQL query, like this:
sql = '''
SELECT app_user.id, COUNT(app_like.id) AS total_likes
# etc (as above)
'''
for user in User.objects.raw(sql):
print user.id, user.total_likes
I believe this can be achieved with Django's queryset:
User.objects.filter(comments__likes__type=1)\
.annotate(lks=Count('comments__likes'))\
.order_by('-lks')
The only problem here is that this query will miss users with 0 likes. Code from #gareth-rees, #timmy-omahony and #Catherine will include also 0-ranked users.
I am having a bit of a trouble understanding this line of code from the book Pro JPA 2
According to the book on page 181.
The result type of a select query cannot be a collection; it must be a
single valued object such as an entity instance or persistent field
type. Expressions such as e.phones are illegal in the SELECT clause
because they would result in Collection instances (each occurrence of
e.phones is a collection, not an instance). Therefore, just as with
SQL and tables, if we want to navigate along a collection association
and return elements of that collection, we must join the two entities
together.
Please consider this entities below with relationship mapping
#Entity
public class Employee {
..
#OneToMany(mappedBy="employee", cascade=CascadeType.ALL, targetEntity=Phone.class)
private Collection<Phone> phones = new ArrayList<Phone>();
..
}
#Entity
public class Phone {
..
#OneToOne
private Employee employee;
..
}
Now in a test class i tried it with this test case
#Test
public void selectCollectionTest(){
TypedQuery<Object> query = em.createQuery("select e.phones from Employee e where e.id = 1", Object.class);
List<Object> empList = query.getResultList();
for(Object temp: empList){
System.out.println(temp);
}
}
I was expecting that an exception would be thrown but nothing is happening and I was able to select the collection?
Is this correct? Can somebody explain or clear out my understanding?
Eclipselink
EclipseLink allows this, it is an extension, the JPA spec does not support it.
It is the same as the query,
select p from Employee e join e.phones p where e.id = 1
Try to run your query with below code by removing the where clause:
select e.phones from Employee e, Object.class
The point i am trying to make is may be your result for emp id 1 contains only single phone object.