I have a #ManyToMany relationship described as follows:
FOO
-----------------------
FOO_ID
........
FOO_BARS
--------------------
BAR_ID
FOO_ID
BAR
--------------------
BAR_ID
.......
#Entity
#Table(name = "FOO")
public class FOO
{
#Id
#SequenceGenerator(....)
#GeneratedValue(...)
#Column(name = "FOO_ID", unique = true, nullable = false, precision = 10)
private int fooId;
public int getFooId()
{
......
}
public void setFooId(final int fooId)
{
......;
}
.....
##ManyToMany()
#JoinTable(name = "FOO_BARS", joinColumns = { #JoinColumn(name = "FOO_ID", nullable = false) }, inverseJoinColumns = { #JoinColumn(name = "BAR_ID", nullable = false) })
private List<Bar> bars;
public List<Bar> getBars()
{
......
}
public void setBars(final List<Bar> bars)
{
......
}
public Bar addBar(Bar value)
{
.....
}
......
}
#Entity
#Table(name = "BAR")
public class Bar
{
#Id
#SequenceGenerator(...)
#GeneratedValue(...)
#Column(name = "BAR_ID", unique = true, nullable = false, precision = 9)
private int barid;
public int getBard()
{
.....
}
public void setBarId(final int barId)
{
.....
}
.....
#ManyToMany(mappedBy = "bar")
private List<Foo> foos;
public List<Foo> getFoos()
{
....
}
public void setFooList(final List<Foo> foos)
{
....
}
public Foo addFoo(final Foo foo)
{
.......
}
.....
}
I have an sql query which I can successfully run to select ALL foos matching a list of provided barIds. This changes according to the provided barIds but is of the format:
SELECT f.* FROM foo f
WHERE
.....
AND f.foo_id IN
(
SELECT fb.foo_id FROM foo_bars fb
WHERE fb.bar_id IN ( 69, 332)
GROUP BY fb.foo_id
HAVING COUNT(DISTINCT fb.bar_id) = 2
);
As entitities don't use a linking table, my issue is in translating this into a query to run using hibernate syntax.
I have tried the following:
SELECT f FROM Foo AS f
WHERE
.....
AND f.fooId IN
(
SELECT b.foos.fooId FROM f.bars b
WHERE b.barid IN (:barids)
GROUP BY b.foos.fooId
HAVING COUNT ( DISTINCT b.barid ) = 2
)
This results in the following error.
org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.QueryException: illegal attempt to dereference collection [bar2_.BAR_ID.foos] with element property reference [fooId]
Any help would be greatly appreciated.
spring data jpa 1.4.3 with Oracle 11g.
I have an entity like this:
class LinkRecord {
String value;
int linkType;
...
}
I am using (value, linkType) as a composite index.
For a given list of (v, t) tuples, we need to select all the records in the DB so that value = v, linkType = t.
Basically, I want to build this query:
SELECT * FROM LINK_RECORD WHERE (VALUE, LINK_TYPE) IN (('value1', 0), ('value2', 25), ...)
where the list in the IN clause is passed in as a param.
Since we're working with a large volume of data, it would be very undesirable to query for the tuples one by one.
In my repository I've tried this:
#Query("select r from LinkRecord r where (r.value, r.linkType) in :keys")
List<LinkRecord> findByValueAndType(#Param("keys")List<List<Object>> keys);
where keys is a list of (lists of length 2). This gets me ORA_00920: invalid relational operator.
Is there any way to make this work using a named query? Or do I have to resort to native sql?
The answer is too late, but maybe some1 else has the same problem. This is one of my working examples. Here I need to search for all entries that match a given composite key:
The entity....
#Entity
#NamedQueries({
#NamedQuery(name = "Article.findByIdAndAccessId", query = "SELECT a FROM Article a WHERE a.articlePk IN (:articlePks) ORDER BY a.articlePk.article")
})
#Table(name = "ARTICLE")
public class Article implements Serializable
{
private static final long serialVersionUID = 1L;
#EmbeddedId
private ArticlePk articlePk = new ArticlePk();
#Column(name = "art_amount")
private Float amount;
#Column(name = "art_unit")
private String unit;
public Article()
{
}
//more code
}
The PK class....
#Embeddable
public class ArticlePk implements Serializable
{
private static final long serialVersionUID = 1L;
#Column(name = "art_article")
private String article;
#Column(name = "art_acc_identifier")
private Long identifier;
public ArticlePk()
{
}
public ArticlePk(String article, Long identifier)
{
this.article = article;
this.identifier = identifier;
}
#Override
public boolean equals(Object other)
{
if (this == other)
{
return true;
}
if (!(other instanceof ArticlePk))
{
return false;
}
ArticlePk castOther = (ArticlePk)other;
return this.article.equals(castOther.article) && this.identifier.equals(castOther.identifier);
}
#Override
public int hashCode()
{
final int prime = 31;
int hash = 17;
hash = hash * prime + this.article.hashCode();
hash = hash * prime + this.identifier.hashCode();
return hash;
}
//more code
}
Invocation by....
TypedQuery<Article> queryArticle = entityManager.createNamedQuery("Article.findByIdAndAccessId", Article.class);
queryArticle.setParameter("articlePks", articlePks);
List<Article> articles = queryArticle.getResultList();
where....
articlePks is List<ArticlePk>.
I have three tables jobs,categories and job_categories with similar to below structure:
**jobs**
id title desc
**categories**
id name
**job_categories**
id job_id category_id
So I want to retrieve all the jobs belongs to a particular category. For that I wrote an action view() in CategoriesController.php
function view($category_id = NULL)
{
if($category_id != NULL)
{
$conditions['JobCategory']['category_id'] = $category_id;
$all_jobs = $this->Job->find('all',array('conditions' => $conditions));
$this->set('all_jobs',$all_jobs);
}
}
And added a $hasMany to Category.php model:
class Category extends AppModel {
var $name = 'Category';
var $hasMany = array('JobCategory');
}
But when I checked the view , it shows error:
Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'JobCategory' in 'where clause' and the query it generates :
SELECT Job.id, Job.job_title, Job.job_description,
Job.job_skills, Job.contact_number, Job.contact_email,
Job.qualification_id, Job.experience, Job.categories,
Job.remarks, Job.support_image, Job.freshers_apply,
Job.added_on, Job.status, Qualification.id,
Qualification.name FROM cakead.jobs AS Job LEFT JOIN
cakead.qualifications AS Qualification ON
(Job.qualification_id = Qualification.id) WHERE JobCategory =
('1')
try this
//AppModel.php
public $actAs = array('Containable');
//Category.php
class Category extends AppModel {
var $name = 'Category';
var $hasAndBelongsTo = array('Category');
}
//CategoriesController.php
function view($category_id = NULL)
{
if($category_id != NULL)
{
$conditions['Category']['id'] = $category_id;
$all_jobs = $this->Category->find('all',array('conditions' => $conditions));
$this->set('all_jobs',$all_jobs);
}
}
I'm trying to work with grouped data coming back from SQL.
The method I'm writing is to provide the data for a "Case Status Overview" screen.
It must produce a nested XML document.
Now, I could do it the easy way, but I'm trying to learn whether it's possible to use the linq "group by" statement and then to project the data already nested. (the easy way would be just to pull back the data in a tabular fashion from the database and then for-loop through it forming the Xml document for output)
Here is the data hierarchy:
Every Case has a DebtType and every DebtType has a Client.
Here is the SQL that retrieves the data:
SELECT ClientNames.ClientID ,
ClientNames.ClientCode ,
ClientNames.ClientName ,
DebtTypes.DebtTypeID ,
DebtTypes.DebtTypeShortDesc ,
DebtTypes.DebtTypeLongDesc ,
Cases.CurrentStateCode ,
SUM(1 - CAST(Cases.CaseClosed AS INT)) AS OpenCaseCount ,
SUM(CAST(Cases.CaseClosed AS INT)) AS ClosedCaseCount ,
SUM(CAST(Cases.CaseOnHold AS INT)) AS OnHoldCaseCount ,
SUM(CAST(Cases.CaseReferred AS INT)) AS ReferredCaseCount ,
COUNT(Cases.CaseID) AS TotalCaseCount ,
SUM(Cases.CaseTotalPaid) AS TotalAmountPaid ,
SUM(Cases.CaseCurrentOutstandingAmount) AS TotalAmountOutstanding,
SUM(Cases.CaseTotalDebtWrittenOff) AS TotalAmountWrittenOff ,
SUM(Cases.CaseTotalDebtCancelled) AS TotalAmountCancelled
FROM ClientNames
INNER JOIN ClientDebtTypes
ON ClientNames.ClientID = ClientDebtTypes.ClientID
INNER JOIN DebtTypes
ON ClientDebtTypes.DebtTypeID = DebtTypes.DebtTypeID
INNER JOIN Cases
ON ClientDebtTypes.ClientDebtTypeID = Cases.CaseClientDebtTypeID
GROUP BY ClientNames.ClientID ,
ClientNames.ClientCode ,
ClientNames.ClientName ,
DebtTypes.DebtTypeID ,
DebtTypes.DebtTypeShortDesc,
DebtTypes.DebtTypeLongDesc ,
Cases.CurrentStateCode
ORDER BY ClientNames.ClientID,
DebtTypes.DebtTypeID,
CurrentStateCode
Using Linqer it converts it to:
from clientnames in db.ClientNames
join clientdebttypes in db.ClientDebtTypes on clientnames.ClientID equals clientdebttypes.ClientID
join debttypes in db.DebtTypes on clientdebttypes.DebtTypeID equals debttypes.DebtTypeID
join cases in db.Cases on new { ClientDebtTypeID = clientdebttypes.ClientDebtTypeID } equals new { ClientDebtTypeID = cases.CaseClientDebtTypeID }
group new {clientnames, debttypes, cases} by new {
clientnames.ClientID,
clientnames.ClientCode,
clientnames.ClientName1,
debttypes.DebtTypeID,
debttypes.DebtTypeShortDesc,
debttypes.DebtTypeLongDesc,
cases.CurrentStateCode
} into g
orderby
g.Key.ClientID,
g.Key.DebtTypeID,
g.Key.CurrentStateCode
select new {
ClientID = (System.Int32?)g.Key.ClientID,
g.Key.ClientCode,
g.Key.ClientName1,
DebtTypeID = (System.Int32?)g.Key.DebtTypeID,
g.Key.DebtTypeShortDesc,
g.Key.DebtTypeLongDesc,
g.Key.CurrentStateCode,
OpenCaseCount = (System.Int64?)g.Sum(p => 1 - Convert.ToInt32(p.cases.CaseClosed)),
ClosedCaseCount = (Int32?)g.Sum(p => Convert.ToInt32(p.cases.CaseClosed)),
OnHoldCaseCount = (Int32?)g.Sum(p => Convert.ToInt32(p.cases.CaseOnHold)),
ReferredCaseCount = (Int32?)g.Sum(p => Convert.ToInt32(p.cases.CaseReferred)),
TotalCaseCount = (Int64?)g.Count(p => p.cases.CaseID != null),
TotalAmountPaid = (System.Decimal?)g.Sum(p => p.cases.CaseTotalPaid),
TotalAmountOutstanding = (System.Decimal?)g.Sum(p => p.cases.CaseCurrentOutstandingAmount),
TotalAmountWrittenOff = (System.Decimal?)g.Sum(p => p.cases.CaseTotalDebtWrittenOff),
TotalAmountCancelled = (System.Decimal?)g.Sum(p => p.cases.CaseTotalDebtCancelled)
}
Now as I mentioned, I could stop there and right a for loop to create the Xml data.
But I'm trying to create a nested group (IGrouping<ClientName,IGrouping<DebtType,SummaryClass>>)
and then project the data in a nested format.
Now we're using LinqToXsd to create strong type wrappers for out Xml documents, but essentially all this means is that out output type is:
private class ClientSummary
{
public string ClientName { get; set; }
public IList<DebtTypeSummary> DebtTypes { get; set; }
}
private class DebtTypeSummary
{
public string DebtType { get; set; }
public IList<StateCodeSummary> StateCodes { get; set; }
}
private class StateCodeSummary
{
public string StateCode { get; set; }
public int TotalCount { get; set; }
public decimal TotalAmountPaid { get; set; }
//etc
//etc
//etc
}
Now I got as far as writing the following Linq:
var grouping = from cases in db.Cases
join clientdebttypes in db.ClientDebtTypes on cases.CaseClientDebtTypeID equals clientdebttypes.ClientID
join debttypes in db.DebtTypes on clientdebttypes.DebtTypeID equals debttypes.DebtTypeID
group cases by new ClientDebtTypePair() { ClientDebtType = clientdebttypes, DebtType = debttypes } into casesByClientDebtTypes
join clientnames in db.ClientNames on casesByClientDebtTypes.Key.ClientDebtType.ClientName equals clientnames
group casesByClientDebtTypes by clientnames;
var projected = from casesByClientDebtTypes in grouping
let client = casesByClientDebtTypes.Key
select new LoadCaseStatusOverviewScreenOutput.ClientsLocalType()
{
Client = new Client()
{
ClientID = client.ClientID,
DisplayName = client.ClientName1,
},
DebtTypes = from cases in casesByClientDebtTypes
let debttype = cases.Key.DebtType
select new LoadCaseStatusOverviewScreenOutput.ClientsLocalType.DebtTypesLocalType()
{
DebtType = new DebtType()
{
DebtTypeID = debttype.DebtTypeID,
Description = debttype.DebtTypeLongDesc,
DisplayName = debttype.DebtTypeShortDesc,
},
StatesCodes = from cases2 in cases
select new LoadCaseStatusOverviewScreenOutput.ClientsLocalType.DebtTypesLocalType.StatesCodesLocalType()
{
ClosedCasesCount = cases2.Sum(p => Convert.ToInt32(p.cases.CaseClosed))
which joins and groups the database tables and then tries to project the result a ClientSummary (the class names are different but that's because the above is a simplified view of the output classes). I fail completely when I've drilled all the way down to the Cases table and I find that I don't really understand how to do agregate functions. They appear to only be available on IGrouping<K, T>s and it seems I've just got confused.
I need to also ensure that the summaries are calculated server side, pulling back millions of cases would be bad.
Can anybody help me with this one? Is this even possible?
Regards,
James.
-------### UPDATE 1 ###-------
OK, been working on this again today.
I decided to use Linq2SQL to pull pack 2D data and then reformat it using Linq2Objects.
Here is what I started with:
var sql = from clientnames in db.ClientNames
join clientdebttypes in db.ClientDebtTypes on clientnames.ClientID equals clientdebttypes.ClientID
join debttypes in db.DebtTypes on clientdebttypes.DebtTypeID equals debttypes.DebtTypeID
join cases in db.Cases on new { ClientDebtTypeID = clientdebttypes.ClientDebtTypeID } equals new { ClientDebtTypeID = cases.CaseClientDebtTypeID }
group new { clientnames, debttypes, cases } by new
{
clientnames.ClientID,
clientnames.ClientCode,
clientnames.ClientName1,
debttypes.DebtTypeID,
debttypes.DebtTypeShortDesc,
debttypes.DebtTypeLongDesc,
cases.CurrentStateCode
} into g
orderby
g.Key.ClientID,
g.Key.DebtTypeID,
g.Key.CurrentStateCode
select new
{
Client = new Client{ ClientID = g.Key.ClientID, DisplayName = g.Key.ClientName1 },
DebtType = new DebtType{ DebtTypeID = g.Key.DebtTypeID, DisplayName = g.Key.DebtTypeShortDesc, Description = g.Key.DebtTypeLongDesc },
StateSummary = new LoadCaseStatusOverviewScreenOutput.ClientsLocalType.DebtTypesLocalType.StatesCodesLocalType()
{
StateCode = g.Key.CurrentStateCode,
OpenCasesCount = g.Sum(p => 1 - Convert.ToInt32(p.cases.CaseClosed)),
ClosedCasesCount = g.Sum(p => Convert.ToInt32(p.cases.CaseClosed)),
OnHoldCasesCount = g.Sum(p => Convert.ToInt32(p.cases.CaseOnHold)),
ReferredCasesCount = g.Sum(p => Convert.ToInt32(p.cases.CaseReferred)),
TotalCasesCount = g.Count(p => p.cases.CaseID != null),
TotalAmountPaid = g.Sum(p => p.cases.CaseTotalPaid),
TotalAmountOutstanding = g.Sum(p => p.cases.CaseCurrentOutstandingAmount),
TotalAmountWrittenOff = g.Sum(p => p.cases.CaseTotalDebtWrittenOff),
TotalAmountCancelled = g.Sum(p => p.cases.CaseTotalDebtCancelled),
}
};
var res = sql.ToList();
output.Clients = (from results in res
group results by results.Client into resultsByClient
from resultsByDebtType in
(from results in resultsByClient
group results by results.DebtType)
group resultsByDebtType by resultsByClient.Key into resultsByDebtTypeByClient
select new LoadCaseStatusOverviewScreenOutput.ClientsLocalType()
{
Client = resultsByDebtTypeByClient.Key,
DebtTypes = (from resultsByDebtType in resultsByDebtTypeByClient
select new LoadCaseStatusOverviewScreenOutput.ClientsLocalType.DebtTypesLocalType()
{
DebtType = resultsByDebtType.Key,
StatesCodes = (from results in resultsByDebtType
let summary = results.StateSummary
select results.StateSummary).ToList()
}).ToList()
}).ToList();
That runs, but produces one Client/DebtType/Summary set for every result. So even though there is only one client in this case, I end up with 1300 clients, all identical.
I simplified it to the following:
output.Clients = (from results in res
group results by results.Client into resultsByClient
select new LoadCaseStatusOverviewScreenOutput.ClientsLocalType()
{
Client = resultsByClient.Key,
DebtTypes = null,
}).ToList();
That produces 1300 clients. Next I tried this:
output.Clients = (from results in res
group results by results.Client.ClientID into resultsByClient
select new LoadCaseStatusOverviewScreenOutput.ClientsLocalType()
{
Client = new Client { ClientID = resultsByClient.Key },
DebtTypes = null,
}).ToList();
And THAT produces ONE client (hurray!). Except I loose all the client information (boo!)
Guessing that as it's comparing client by refernce instead of by content I wrote the following:
public partial class Client
{
public static bool operator ==(Client left, Client right)
{
return left.ClientID == right.ClientID;
}
public static bool operator !=(Client left, Client right)
{
return left.ClientID != right.ClientID;
}
public override int GetHashCode()
{
return ClientID;
}
}
That did nothing. It repeatedly calls GetHashCode(), which I fudged to force it to return the same hash code for any matching ClientID, but it still created 1300 Client groups.
Regards,
James.
-------### UPDATE 2 ###-------
OK, I thought I would have a go at making the Linq2Sql output only simple values for grouping by:
g.Key.ClientID,
g.Key.ClientName1,
g.Key.DebtTypeID,
g.Key.DebtTypeShortDesc,
g.Key.DebtTypeLongDesc,
And then changed the test Linq2Objects to:
output.Clients = (from results in res
group results by new { ClientID = results.ClientID, DisplayName = results.ClientName1 } into resultsByClient
select new LoadCaseStatusOverviewScreenOutput.ClientsLocalType()
{
Client = new Client { ClientID = resultsByClient.Key.ClientID, DisplayName = resultsByClient.Key.DisplayName },
DebtTypes = null,
}).ToList();
That works. So anonymous types compare in the way I want them to, by content not reference (apparently)
This does not:
output.Clients = (from results in res
group results by new SiDemClient { ClientID = results.ClientID, DisplayName = results.ClientName1 } into resultsByClient
select new LoadCaseStatusOverviewScreenOutput.ClientsLocalType()
{
Client = resultsByClient.Key,//new Client { ClientID = resultsByClient.Key.ClientID, DisplayName = resultsByClient.Key.DisplayName },
DebtTypes = null,
}).ToList();
That still creates 1300 groups.
So, anonymous types compare in a magical way that I don't understand. How can I make my Client class compare like an anonymous type?
Regards,
James.
-------### SOLUTION FOUND ###-------
-------### MANY THANKS TO Enigmativity ###-------
I needed to override the Equals() method instead of implementing the == operator.
Now the grouping works and I have a wonderful Xml document to reutrn!
public partial class SiDemClient
{
public override bool Equals(object obj)
{
if (obj is SiDemClient)
{
return this.ClientID.Equals(((SiDemClient)obj).ClientID);
}
return false;
}
public override int GetHashCode()
{
return ClientID;
}
}
Many Thanks,
James.
When you override GetHashCode you must also override Equals. The == & != operators are irrelevant.
Try with this:
public partial class Client
{
public override bool Equals(object obj)
{
if (obj is Client)
{
return this.ClientID.Equals(((Client)obj).ClientID);
}
return false;
}
public override int GetHashCode()
{
return this.ClientID.GetHashCode();
}
}
See if that helps.
I have a Question class in ActiveRecord with following fields:
[ActiveRecord("`Question`")]
public class Question : ObcykaniDb<Question> {
private long id;
private IList<Question> relatedQuestions;
[PrimaryKey("`Id`")]
private long Id {
get { return this.id; }
set { this.id = value; }
}
[HasAndBelongsToMany(typeof(Question), ColumnRef = "ChildId", ColumnKey = "ParentId", Table = "RelatedQuestion")]
private IList<Question> RelatedQuestions {
get { return this.relatedQuestions; }
set { this.relatedQuestions = value; }
}
}
How do I write a DetachedCriteria query to get all Questions that have at least 5 related questions (count) in the RelatedQuestions collection?
For now this gives me strange results:
DetachedCriteria dCriteria = DetachedCriteria.For<Question>()
.CreateCriteria("RelatedQuestions")
.SetProjection(Projections.Count("Id"))
.Add(Restrictions.EqProperty(Projections.Id(), "alias.Id"));
DetachedCriteria dc = DetachedCriteria.For<Question>("alias").Add(Subqueries.Le(5, dCriteria));
IList<Question> results = Question.FindAll(dc);
Any ideas what I'm doing wrong?
Try something like:
var dc = DetachedCriteria.For<Question>()
.SetProjection(Projections.ProjectionList()
.Add(Projections.GroupProperty("Id")))
.Add(Restrictions.Ge(Projections.Count("RelatedQuestions"), 5))
.SetResultTransformer(new AliasToBeanResultTransformer(typeof(Question)));
var questions = Question.FindAll(dc);