Slick 3.0 Multiple Many-To-Many calls - join

Taking inspiration from this post:
How can I present a many-to-many relationship using a link table with ScalaQuery or SLICK?
My situation is a somewhat the same with some small exception.
def testManyToMany(): Unit = db withSession {
object A extends Table[(Int, String)]("a") {
def id = column[Int]("id", O.PrimaryKey)
def s = column[String]("s")
def * = id ~ s
def bs = AToB.filter(_.aId === id).flatMap(_.bFK)
// note I have another many-to-many join
def cs = AtoC.filter(_cId === id).flatMap(_.aFK)
}
object B extends Table[(Int, String)]("b") {
def id = column[Int]("id", O.PrimaryKey)
def s = column[String]("s")
def * = id ~ s
def as = AToB.filter(_.bId === id).flatMap(_.aFK)
}
object AToB extends Table[(Int, Int)]("a_to_b") {
def aId = column[Int]("a")
def bId = column[Int]("b")
def * = aId ~ bId
def aFK = foreignKey("a_fk", aId, A)(a => a.id)
def bFK = foreignKey("b_fk", bId, B)(b => b.id)
}
object AToC extends Table[(Int, Int)]("a_to_c") {
def aId = column[Int]("a")
def cId = column[Int]("c")
def * = aId ~ cId
def aFK = foreignKey("a_fk", aId, A)(a => a.id)
def cFK = foreignKey("c_fk", cId, C)(c => c.id)
}
}
Now when I want to fetch all A's by id, I would also want to fetch it associations in B and C, I would do something like:
for {
a <- A if a.id >= 2
aToB <- AToB if aToB.aId === a.id
b <- B if b.id === aToB.bId
} yield (a.s, b.s)
How can I include the join to the C table? Is having something like this correct?
for {
a <- A if a.id >= 2
aToB <- AToB if aToB.aId === a.id
aToC <- AToC if aToC.aId === a.id
b <- B if b.id === aToB.bId
c <- C if c.id === aToC.cId
} yield (a.s, b.s)
Wouldn't this try to do a sub-select on aToC for each aToB as this is just a flatMap operation?

Related

Comparing Properties in hasMany Criteria

I have a domain classes like so:
class Person {
Team team
static hasMany = [history: PersonHistory]
}
class PersonHistory {
Team team
Person person
}
Now I would like to create a criteria that pulls back all the 'persons' who have a PersonHistory instance with a different team.
Something along the lines of:
def c = Person.createCriteria()
def experiment = c.list {
history {
neProperty("team", history.team)
}
}
However this is throwing (because of the history.team):
Missing Property Exception
No such property: history for class: grails.orm.HibernateCriteriaBuilder
Can I do this inside one criteria query? If so, how?
I haven't test it but I think this should work:
Person.withCriteria {
createAlias 'history', 'h'
ne 'team', h.team
}
You have to create an alias for the join with history.
EDIT:
Now I got it! With the following sample data:
def t1 = new Team(name: 'team 1').save()
def t2 = new Team(name: 'team 2').save()
def t3 = new Team(name: 'team 3').save()
def p1 = new Person(team: t1).save()
def p2 = new Person(team: t1).save()
def p3 = new Person(team: t2).save()
def p4 = new Person(team: t2).save()
def p5 = new Person(team: t3).save()
def ph1 = new PersonHistory(person: p1, team: t1).save()
def ph2 = new PersonHistory(person: p2, team: t3).save() // t3 instead of t1
def ph3 = new PersonHistory(person: p3, team: t2).save()
def ph4 = new PersonHistory(person: p4, team: t1).save() // t1 instead of t2
def ph5 = new PersonHistory(person: p5, team: t3).save(flush: true)
Now you can execute the following criteria:
List<Person> persons = PersonHistory.withCriteria {
createAlias 'person', 'p'
neProperty 'p.team', 'team'
projections {
property 'person'
}
}
That will return the correct persons p2 and p4

Return Original datatype after LINQ group by

I need my LINQ Query to return the Product Datatype, after being grouped. It seems to be encased into an anonymous psuedo family.
I have some properties in Product that I don't care about, just needing the p.ID and p.Name etc.
The error I'm getting at the moment with this is:
The entity or complex type 'DatabaseModel.Product' cannot be constructed in a LINQ to Entities query.
This is my Method:
public static List<Product> GetSellableSpecialOffers(DatabaseEntities db)
{
var query = from p in db.Products
where (p.Visible == true && p.Active == true)
&& p.ShowInSpecialOffers == true
group p by p.FamilyID == null ? 0 : p.FamilyID into f
select new Product {
ID = f.First().ID,
Name = f.First().Name,
Cost = f.First().Cost,
RRP = f.First().RRP
};
return query.ToList();
}
What is the problem? Is there a better way around this? SQL would always return 1 record instead of encasing the object in a secondary datatype, I don't get it.
Many thanks,
EDIT 1:
My apologies for extending the specification, but I need the returned product to be programatically generated e.g.
select new Product {
ID = f.First().ID,
Name = f.First().Name,
Cost = f.OrderBy(p => p.NowCost).FirstOrDefault(),
RRP = f.First().RRP
}
or if I could strongly type the family class:
public partial class ProductFamily
{
public Product GroupedProduct
{
get
{
return this.Products.OrderBy(p => p.NowCost).FirstOrDefault();
}
}
}
Then I would do:
var query = (from p in db.Products
where (p.Visible == true && p.Active == true)
&& p.ShowInSpecialOffers == true
group p by p.ProductFamily == null ? null : p.ProductFamily into f
select f.GroupedProduct).ToList<Product>();
But I can't get either solution to work with what I have.
You can try (boring, but not sure you have the choice)
var query = from p in db.Products
where (p.Visible == true && p.Active == true)
&& p.ShowInSpecialOffers == true
group p by p.FamilyID == null ? 0 : p.FamilyID into f
select new {
ID = f.First().ID,
Name = f.First().Name,
Cost = f.OrderBy(m => m.NowCost).First().Cost,
RRP = f.First().RRP
};
return query.ToList().Select(m =>
new Product {
ID = m.ID,
Name = m.Name,
Cost = m.Cost,
RRP = m.RRP
};
EDIT
Or as pointed by Master Skeet (not exactly the same as what you tried, but much easier)
var query = from p in db.Products
where (p.Visible == true && p.Active == true)
&& p.ShowInSpecialOffers == true
group p by p.FamilyID == null ? 0 : p.FamilyID into f
select f.OrderBy(m => m.NowCost).First();
You could simply select f.First() and return the resulting data, like this:
var query = from p in db.Products
where (p.Visible == true && p.Active == true)
&& p.ShowInSpecialOffers == true
group p by p.FamilyID == null ? 0 : p.FamilyID into f
select f.First();
Then outside the method use whatever properties you need, ignoring what you don't care about (you are handling Product objects, properties should be there even if you don't need them).
Remove the word 'Product' from
select new Product{}
'select new' itself will create an anonymous type whereas you are specifying it as a Product.

Is It Possible To Cast A Range

I'd like to do something like this:
def results = Item.findAll("from Item c, Tag b, ItemTag a where c = a.item and b = a.tag and (b.tag like :q or c.uri like :q) " + ob,[q:q])
def items = (Item) results[0..1][0]
but I get
Cannot cast object '[Ljava.lang.Object;#1e224a5' with class '[Ljava.lang.Object;' to class 'org.maflt.ibidem.Item'
I can get what I need with this, but it doesn't seem like it's the best solution:
def items = [] as Set
def cnt = results.size()
for (i=0;i<cnt-1;i++) { items << results[i][0] }
items = items as List
UPDATE
The solution suggested to use
def results = Item.findAll("from Item c, Tag b, ItemTag a where c = a.item and b = a.tag and (b.tag like :q or c.uri like :q) " + ob,[q:q])
def items = results[0] as List
doesn't work. The query actually produces the correct SQL:
select
item0_.id as id3_0_,
tag1_.id as id16_1_,
itemtag2_.id as id6_2_,
item0_.version as version3_0_,
item0_.last_updated as last3_3_0_,
item0_.docsize as docsize3_0_,
item0_.customer_id as customer5_3_0_,
item0_.uri as uri3_0_,
item0_.created_by_person_id as created7_3_0_,
item0_.updated_by_person_id as updated8_3_0_,
item0_.md5 as md9_3_0_,
item0_.original_file_name as original10_3_0_,
item0_.date_created as date11_3_0_,
item0_.doctype as doctype3_0_,
item0_.identifier as identifier3_0_,
tag1_.version as version16_1_,
tag1_.tagtype_id as tagtype3_16_1_,
tag1_.tag as tag16_1_,
tag1_.last_updated as last5_16_1_,
tag1_.customer_id as customer6_16_1_,
tag1_.created_by_person_id as created7_16_1_,
tag1_.updated_by_person_id as updated8_16_1_,
tag1_.date_created as date9_16_1_,
itemtag2_.version as version6_2_,
itemtag2_.updated_by_person_id as updated3_6_2_,
itemtag2_.weight as weight6_2_,
itemtag2_.tag_id as tag5_6_2_,
itemtag2_.item_id as item6_6_2_,
itemtag2_.last_updated as last7_6_2_,
itemtag2_.date_created as date8_6_2_,
itemtag2_.source_id as source9_6_2_,
itemtag2_.created_by_person_id as created10_6_2_
from
item item0_,
tag tag1_,
item_tag itemtag2_
where
item0_.id=itemtag2_.item_id
and tag1_.id=itemtag2_.tag_id
and (
lower(tag1_.tag) like '%english%'
or lower(item0_.original_file_name) like '%english%'
)
order by
item0_.id
But unfortunately the items = results[0] as List doesn't work. It only returns 3 rows and only items[0] is an Item.
items.each{println it.class}
gives:
class org.maflt.ibidem.Item
class org.maflt.ibidem.Tag
class org.maflt.ibidem.ItemTag
As your HQL selects several entities, you should be more specific. The following should return only items:
def items = Item.executeQuery("select c from Item as c, Tag as b, ItemTag as a where c = a.item and b = a.tag and (b.tag like :q or c.uri like :q) " + ob,[q:q])
Cheers!

Sorting Tables - Lua

I am trying to sort but there is a nil. How can i get around this?
Code im useing: (sorting it by name and HPs. in case there is duplicate HPs)
T = { {Name = "Mark", HP = 54, Breed = "Ghost"},
{Name = "Stan", HP = 24, Breed = "Zombie"},
{Name = "Juli", HP = 100, Breed = "Human"},
{ HP = 100, Breed = "Human"}
}
function Sorting(T)
table.sort(T,
function(x,y)
return x.Name < y.Name and x.HP < y.HP
end
)
end
Assuming you want to compare by HP if name isn't available, how about you change the sort comparison function to:
function(x, y)
if x.Name == nil or y.Name == nil then return x.HP < y.HP
else return x.Name < y.Name and x.HP < y.HP
end
end
Your problem is that Name isn't a real key if it's not available all the time.

Sorting Tables Twice in one Function - Lua

Want the function to sort the table by HP but if duplicate HPs then sorts by name. When i run this function it just groups the duplicate HPs together in no order by the name.
T = { {Name = "Mark", HP = 54, Breed = "Ghost"}, {Name = "Stan", HP = 24, Breed = "Zombie"}, {Name = "Juli", HP = 100, Breed = "Human"}, { HP = 100, Breed = "Human"} }
function(x, y) if x.Name == nil or y.Name == nil then return x.HP < y.HP else return x.Name < y.Name and x.HP < y.HP end end) end
Try this sort func:
function(x,y)
if x.Name == nil or y.Name == nil then
return x.HP < y.HP
else
return x.HP < y.HP or (x.HP == y.HP and x.Name < y.Name)
end
end
Since you always want differing HPs to be the primary sorting order (and name secondary), you want the HP check to come first in the or clause so that if it differs it'll skip any other checks and just determine based on HP.

Resources