Grails query to filter on association and only return matching entities - sql

I have the following 1 - M (one way) relationship:
Customer (1) -> (M) Address
I am trying to filter the addresses for a specific customer that contain certain text e.g.
def results = Customer.withCriteria {
eq "id", 995L
addresses {
ilike 'description', '%text%'
}
}
The problem is that this returns the Customer and when I in turn access the "addresses" it gives me the full list of addresses rather than the filtered list of addresses.
It's not possible for me to use Address.withCriteria as I can't access the association table from the criteria query.
I'm hoping to avoid reverting to a raw SQL query as this would mean not being able to use a lot functionality that's in place to build up criteria queries in a flexible and reusable manner.
Would love to hear any thoughts ...

I believe the reason for the different behavior in 2.1 is documented here
Specifically this point:
The previous default of LEFT JOIN for criteria queries across associations is now INNER JOIN.
IIRC, Hibernate doesn't eagerly load associations when you use an inner join.
Looks like you can use createAlias to specify an outer join example here:
My experience with this particular issue is from experience with NHibernate, so I can't really shed more light on getting it working correctly than that. I'll happily delete this answer if it turns out to be incorrect.

Try this:
def results = Customer.createCriteria().listDistinct() {
eq('id', 995L)
addresses {
ilike('description', '%Z%')
}
}
This gives you the Customer object that has the correct id and any matching addresses, and only those addresses than match.
You could also use this query (slightly modified) to get all customers that have a matching address:
def results = Customer.createCriteria().listDistinct() {
addresses {
ilike('description', '%Z%')
}
}
results.each {c->
println "Customer " + c.name
c.addresses.each {address->
println "Address " + address.description
}
}
EDIT
Here are the domain classes and the way I added the addresses:
class Customer {
String name
static hasMany = [addresses: PostalAddress]
static constraints = {
}
}
class PostalAddress {
String description
static belongsTo = [customer: Customer]
static constraints = {
}
}
//added via Bootstrap for testing
def init = { servletContext ->
def custA = new Customer(name: 'A').save(failOnError: true)
def custB = new Customer(name: 'B').save(failOnError: true)
def custC = new Customer(name: 'C').save(failOnError: true)
def add1 = new PostalAddress(description: 'Z1', customer: custA).save(failOnError: true)
def add2 = new PostalAddress(description: 'Z2', customer: custA).save(failOnError: true)
def add3 = new PostalAddress(description: 'Z3', customer: custA).save(failOnError: true)
def add4 = new PostalAddress(description: 'W4', customer: custA).save(failOnError: true)
def add5 = new PostalAddress(description: 'W5', customer: custA).save(failOnError: true)
def add6 = new PostalAddress(description: 'W6', customer: custA).save(failOnError: true)
}
When I run this I get the following output:
Customer A
Address Z3
Address Z1
Address Z2

Related

Django restframework SerializerMethodField background work

I am writing a project in Django with rest framework by using SerializerMethodField. This method makes queries for every row to get data, or View collects all queries and send it to DB? Django can make it as one joint query?
class SubjectSerializer(serializers.ModelSerializer):
edu_plan = serializers.SerializerMethodField(read_only=True)
academic_year_semestr = serializers.SerializerMethodField(read_only=True)
edu_form = serializers.SerializerMethodField(read_only=True)
def get_edu_plan(self, cse):
return cse.curriculum_subject.curriculum.edu_plan.name
def get_academic_year_semestr(self, cse):
semester = cse.curriculum_subject.curriculum.semester
return {'academic_year': semester.academic_year, 'semester': semester}
def get_edu_form(self, cse):
return cse.curriculum_subject.curriculum.edu_form.name
class Meta:
model = CurriculumSubjectEmployee
fields = [
'id',
'name',
'edu_plan',
'academic_year_semestr',
'edu_form'
]
class SubjectViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = SubjectSerializer
def get_queryset(self):
contract = self.request.user.employee.contract
if contract is None:
raise NotFound(detail="Contract not found", code=404)
department = contract.department
cses = CurriculumSubjectEmployee\
.objects\
.filter(curriculum_subject__department=department)
return cses

Convert SQL to Active Record Query matching on IN

How would I convert this sort of SQL into Active Record Syntax.
I've struggled mainly resolving the IN with the other elements.
SELECT \"accounts\".* FROM account_categories, accounts WHERE \"accounts\".\"worksheet_id\" = 5 AND (account_categories.name IN ('Savings','Deposit') AND account_categories.id = accounts.account_category_id) ORDER BY \"accounts\".\"id\" ASC"
worksheet_id will vary, won't always be 5.
I want to use this in a scope in the Account model.
Similar like this
scope :savings, -> { from('account_categories, accounts').where("account_categories.name = ? AND account_categories.id = zen_accounts.account_category_id", 'Savings') }
but testing for both Savings & Deposit something like this:
scope :savings_and_deposit, -> { from('account_categories, accounts').where("account_categories.name = ? AND account_categories.id = zen_accounts.account_category_id", ['Savings','Deposit]) }
Try this code:
scope :savings, -> (worksheet_id) { filtered_categories(worksheet_id, ['Savings']) }
scope :savings_and_deposit, -> (worksheet_id) { filtered_categories(worksheet_id, ['Savings', 'Deposit']) }
scope :filtered_categories, -> (worksheet_id, category_names) do
joins(:account_categories).
where(worksheet_id: worksheet_id).
where(account_categories: {name: category_names}).
order(id: :asc)
end
This code supposes what Account model already has relation account_categories, otherwise replace joins(:account_categories) with joins("JOIN account_categories ON account_categories.id = accounts.account_category_id")

How correctly built an object graph based on multi level join in Slick?

I have a model structure as following:
Group -> Many Parties -> Many Participants
In on of the API calls I need to get single groups with parties and it's participants attached.
This whole structure is built on 4 tables:
group
party
party_participant
participant
Naturally, with SQL it's a pretty straight forward join that combines all of them. And this is exactly what I am trying to do with slick.
Mu method is dao class looks something like this:
def findOneByKeyAndAccountIdWithPartiesAndParticipants(key: UUID, accountId: Int): Future[Option[JourneyGroup]] = {
val joins = JourneyGroups.groups join
Parties.parties on (_.id === _.journeyGroupId) joinLeft
PartiesParticipants.relations on (_._2.id === _.partyId) joinLeft
Participants.participants on (_._2.map(_.participantId) === _.id)
val query = joins.filter(_._1._1._1.accountId === accountId).filter(_._1._1._1.key === key)
val q = for {
(((journeyGroup, party), partyParticipant), participant) <- query
} yield (journeyGroup, party, participant)
val result = db.run(q.result)
result ????
}
The problem here, is that the result is type of Future[Seq[(JourneyGroup, Party, Participant)]]
However, what I really need is Future[Option[JourneyGroup]]
Note: case classes of JourneyGroup and Party have sequences for there children defined:
case class Party(id: Option[Int] = None,
partyType: Parties.Type.Value,
journeyGroupId: Int,
accountId: Int,
participants: Seq[Participant] = Seq.empty[Participant])
and
case class JourneyGroup(id: Option[Int] = None,
key: UUID,
name: String,
data: Option[JsValue],
accountId: Int,
parties: Seq[Party] = Seq.empty[Party])
So they both can hold the descendants.
What is the correct way to convert to the result I need? Or am I completely in a wrong direction?
Also, is this statement is correct:
Participants.participants on (_._2.map(_.participantId) === _.id) ?
I ended up doing something like this:
journeyGroupDao.findOneByKeyAndAccountIdWithPartiesAndParticipants(key, account.id.get) map { data =>
val groupedByJourneyGroup = data.groupBy(_._1)
groupedByJourneyGroup.map { case (group, rows) =>
val parties = rows.map(_._2).distinct map { party =>
val participants = rows.filter(r => r._2.id == party.id).flatMap(_._3)
party.copy(participants = participants)
}
group.copy(parties = parties)
}.headOption
}
where DAO method's signature is:
def findOneByKeyAndAccountIdWithPartiesAndParticipants(key: UUID, accountId: Int): Future[Seq[(JourneyGroup, Party, Option[Participant])]]

How to avoid ImprovedNamingStrategy in joinTable in Grails

I have a legacy database which I can't change and I have this setup
class Foo {
static hasMany = [bars:Bar]
static mapping = {
version false
columns {
id column: "FooId"
color column: "FooColor"
bars joinTable: [name: "FooBar", key: 'FooId', column: 'BarId']
}
transient
def getBarName(){
((Bar)this.bars.toArray()[0]).name
}
}
class Bar {
static hasMany = [foos:Foo]
static belongsTo = [Foo, Baz]
static mapping = {
version false
columns {
id column: "BarId"
name column: "BarName"
}
}
When i try to access the method getBarName() in a controller Hibernate translates the inverse column name to "bar_id". Is there some way to set up a mapping like the one for the id and property columns?
And on a side note. How do i correctly implement getBarName()? Thacan't possibly be the correct implementation...
*EDIT*
----------------------------------------------------------------------
Apparently I was unclear above. The thing is that i already have a join column which has the form
-------------------
|RowId|FooId|BarId|
-------------------
| 1 | abc | 123 |
-------------------
Benoit's answer isn't really applicable in this situation since I want to avoid having a domain object for the joinTable.
*EDIT 2*
----------------------------------------------------------------------
Solved it. Dont understand it though... But split the join table information between the two domain classes and it works...
class Foo {
static hasMany = [bars:Bar]
static mapping = {
version false
columns {
id column: "FooId"
color column: "FooColor"
bars joinTable: [name: "FooBar", key: 'FooId']
}
transient
def getBarName(){
((Bar)this.bars.toArray()[0]).name
}
}
class Bar {
static hasMany = [foos:Foo]
static belongsTo = [Foo, Baz]
static mapping = {
version false
columns {
id column: "BarId"
name column: "BarName"
bars joinTable: [name: "FooBar", key: 'BarId']
}
}
As stated in the documentation Many-to-One/One-to-One Mappings and One-to-Many Mapping :
With a bidirectional one-to-many you can change the foreign key column
used by changing the column name on the many side of the association
as per the example in the previous section on one-to-one associations.
However, with unidirectional associations the foreign key needs to be
specified on the association itself.
Thye given example is:
class Person {
String firstName
static hasMany = [addresses: Address]
static mapping = {
table 'people'
firstName column: 'First_Name'
addresses column: 'Person_Address_Id'
}
}

Django ORM equivalent

I have the following code in a view to get some of the information on the account to display. I tried for hours to get this to work via ORM but couldn't make it work. I ended up doing it in raw SQL but what I want isn't very complex. I'm certain it's possible to do with ORM.
In the end, I just want to populate the dictionary accountDetails from a couple of tables.
cursor.execute("SELECT a.hostname, a.distro, b.location FROM xenpanel_subscription a, xenpanel_hardwarenode b WHERE a.node_id = b.id AND customer_id = %s", [request.user.id])
accountDetails = {
'username': request.user.username,
'hostname': [],
'distro': [],
'location': [],
}
for row in cursor.fetchall():
accountDetails['hostname'].append(row[0])
accountDetails['distro'].append(row[1])
accountDetails['location'].append(row[2])
return render_to_response('account.html', accountDetails, context_instance=RequestContext(request))
It would be easier if you post models. But from SQL I'm assuming the models are like this:
class XenPanelSubscription(models.Model):
hostname = models.CharField()
distro = models.CharField()
node = models.ForeignKey(XenPanelHardwareNode)
customer_id = models.IntegerField()
class Meta:
db_table = u'xenpanel_subscription'
class XenPanelHardwareNode(models.Model):
location = models.CharField()
class Meta:
db_table = u'xenpanel_hardwarenode'
Based on these models:
accountDetails = XenPanelSubscription.objects.filter(customer_id = request.user.id)
for accountDetail in accountDetails:
print accountDetail.hostname, accountDetail.distro, accountDetail.node.location