I have a problem to implement a boolean logic with Hibernate Search Filter.
There are persons that can be part of groups. Every group has a status from status catalog.
I need to filter all the users that are in group 1 and have status 2. For that I'm using a boolean query with Occur.MUST for both clauses, but in the filtered result are included persons that has list of grops and one of them is 1 and one of the statuses of the group is 2, for example:
person | group | status
105 (1) 3
105 2 3
105 3 (2)
188 (1) 3
188 7 (2)
197 (1) 4
197 8 5
197 9 (2)
The users 105, 188 and 197 has not to be included in the filtered result. What is the correct way to accomplsh that?
Filter:
BooleanQuery bq = new BooleanQuery();
TermQuery tqGroup = new TermQuery(new Term("groupPersons.id.groupId", "1"));
TermQuery tqStatus = new TermQuery(new Term("groupPersons.status.id", "2"));
bq.add(tqGroup, BooleanClause.Occur.MUST);
bq.add(tqStatus, BooleanClause.Occur.MUST);
filter = new QueryWrapperFilter(bq);
Person entity:
...
private List<GroupPerson> groupPersons = new ArrayList<GroupPerson>(0);
#IndexedEmbedded
#OneToMany(fetch = FetchType.LAZY, mappedBy = "person")
public List<GroupPerson> getGroupPersons() {
return this.groupPersons;
}
GroupPerson entity:
...
#EmbeddedId
#AttributeOverrides({
#AttributeOverride(name = "groupId", column = #Column(name = "group_id", nullable = false)),
#AttributeOverride(name = "personId", column = #Column(name = "person_id", nullable = false)) })
#NotNull
#DocumentId
#FieldBridge(impl = GroupPersonIdBridge.class)
public GroupPersonId getId() {
return this.id;
}
...
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "status_id",nullable = false)
#IndexedEmbedded
#NotNull
public Status getStatus() {
return this.Status;
}
OrganizationPersonIdBridge:
public Object get(String name, Document document) {
GroupPersonId id = new GroupPersonId();
Field field = document.getField( name + ".groupId" );
id.setGroupId(Long.parseLong(field.stringValue()));
field = document.getField( name + ".personId" );
id.setPersonId(Long.parseLong(field.stringValue()));
return id;
}
public String objectToString(Object object) {
GroupPersonId id = (GroupPersonId) object;
StringBuilder sb = new StringBuilder();
sb.append( id.getGroupId() )
.append(" ")
.append(id.getPersonId());
return sb.toString();
}
public void set(String name,Object value,Document document,LuceneOptions luceneOptions) {
GroupPersonId id = (GroupPersonId)value;
Store store = luceneOptions.getStore();
Index index = luceneOptions.getIndex();
TermVector termVector = luceneOptions.getTermVector();
Float boost = luceneOptions.getBoost();
//store each property in a unique field
Field field = new Field(name + ".groupId", id.getGroupId() + "", store, index, termVector);
field.setBoost( boost );
document.add( field );
field = new Field(name + ".personId", id.getPersonId() + "", store, index, termVector);
field.setBoost( boost );
document.add( field );
//store the unique string representation in the named field
field = new Field( name,
objectToString( id ),
store, index, termVector );
field.setBoost( boost );
document.add( field );
}
The version of Hibernate search is 4.5.1.Final
The problem is that a Lucene Document does not have associations. When you are using #IndexedEmbedded you are effectively flattening all associations into a single Lucene Document (which is what's get added to a Lucene index and retrieved at search time). A Document can have the a field with the same name added multiple times. Taking your example, the Document for the Person with the id 105 will contain the following field name to field value pairs:
+-------------------------+-------------+
| field name | field value |
+-------------------------+-------------+
| groupPersons.id.groupId | 1 |
| groupPersons.id.groupId | 2 |
| groupPersons.id.groupId | 3 |
| groupPersons.status.id | 3 |
| groupPersons.status.id | 3 |
| groupPersons.status.id | 2 |
+-------------------------+-------------+
If you now look at your query, you understand why person 105 is a match. Both boolean queries match.
How can you solve the problem? You need to make sure to have something unique to search on. One way of doing this, is to index group and status into a single field - using a custom bridge. Then you can write a query which just targets that field.
For someone who haves the same uses case, here is the solution using classBridge:
public class CustomClassBridge implements FieldBridge, Serializable {
public final static String SEPARATOR = "-";
#Override
public void set(String name, Object value, Document document, LuceneOptions luceneOptions) {
GroupPerson gp = (GroupPerson)value;
String fieldValue = gp.getId().getGroupId() + SEPARATOR + gp.getStatus().getId();
Field field = new Field(name, fieldValue, luceneOptions.getStore(), luceneOptions.getIndex(), luceneOptions.getTermVector());
field.setBoost(luceneOptions.getBoost());
document.add(field);
}
}
Add annotation to GroupPerson entity on class level:
#ClassBridge(name="groupStatus",index=Index.YES, analyze=Analyze.NO, store=Store.YES, impl = CustomClassBridge.class)
And finally in the filter:
TermQuery tq = new TermQuery(new Term("groupPersons.groupStatus", 1 + CustomClassBridge.SEPARATOR + 2));
Related
I have two tables
Estatus table
+----+----------+
| ID | Nombre |
+----+----------+
| 16 | ACTIVE |
| 2 | DISABLED |
+----+----------+
TipoCarga Table
+----+----------+--------------------+-----------+
| ID | Nombre | Descripcion | EstatusID |
+----+----------+--------------------+-----------+
| 1 | Active | first descriptio | 16 |
| 2 | Disabled | second description | 2 |
+----+----------+--------------------+-----------+
and I have custom entity to list and charge like:
IEnumerable<T> Listar(Expression<Func<T, bool>> filter = null,
Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null,
string includeProperties = "");
T Cargar(Expression<Func<T, bool>> filter,
string includeProperties = "");
if I do something like
var claseid = _claseService.Cargar(c => c.Nombre.Equals("TIPOS DE CARGA")).ID;
var estatusid = _estatusService.Cargar(e => e.Nombre.Equals("ACTIVE") && e.ClaseID.Equals(claseid)).ID;
var tc = _tipoCargaService.Listar(c => c.EstatusID.Equals(estatusid), includeProperties: "Estatus");
It load values, but only values who have Nombre.Equals("ACTIVO"), and I want to get all Nombre values
So first I try to get a list of all Estatus like:
//I don´t need this line because I dont wannt to filte by clase
//var claseid = _claseService.Cargar(c => c.Nombre.Equals("TIPOS DE CARGA")).ID;
//get list of all estatus
var estatusid = _estatusService.Listar();
then use this list and use to retrieve value "Nombre" depending of EstatusID of table TipoCarga like:
var tc = _tipoCargaService.Listar(c => c.EstatusID.Equals(estatusid), includeProperties: "Estatus");
Problem is var estatusid = _estatusService.Listar(); get 0 values, and I don´t know why it don´t get values, can someone help ? Regards
Methods implementations:
public virtual IEnumerable<T> Listar(
Expression<Func<T, bool>> filter = null,
Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null,
string includeProperties = "")
{
IQueryable<T> query = _dbset;
if (filter != null)
{
query = query.Where(filter);
}
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
if (orderBy != null)
{
return orderBy(query).AsEnumerable<T>();
}
else
{
return query.AsEnumerable<T>();
}
}
public virtual T Cargar(
Expression<Func<T, bool>> filter,
string includeProperties = "")
{
IQueryable<T> query = _dbset;
query = query.AsNoTracking().Where(filter);
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
return query.FirstOrDefault();
}
UPDATE:
If I use .Tolist as Ivan Stoev comment it return all values of Estatus but now it pass to:
var tc = _tipoCargaService.Listar(c => c.EstatusID.Equals(estatusid), includeProperties: "Estatus");
then it load foreach like:
if (tc != null)
{
List<TipoCargaViewModel> tiposcargas = new List<TipoCargaViewModel>();
foreach (var item in tc)
{
var carga = new TipoCargaViewModel()
{
ID = item.ID,
Nombre = item.Nombre,
Descripcion = item.Descripcion,
EstatusID = item.EstatusID,
NombreEstatus = item.Estatus.Nombre
};
tiposcargas.Add(carga);
}
But when it try to pass foreach it throw an exception:
Unable to create a constant value of type 'Project.Model.Estatus'.
Only primitive types or enumeration types are supported in this
context.
This question is a spin-off of RavenDB: Why do I get null-values for fields in this multi-map/reduce index?, but I realized, the problem was another.
Consider my extremely simplified domain, rewritten to a movie rental store scenario for abstraction:
public class User
{
public string Id { get; set; }
}
public class Movie
{
public string Id { get; set; }
}
public class MovieRental
{
public string Id { get; set; }
public string MovieId { get; set; }
public string UserId { get; set; }
}
It's a text-book many-to-many example.
The index I want to create is this:
For a given user, give me a list of every movie in the database (filtering/search left out for the moment) along with an integer describing how many times (or zero) the user has rented this movie.
Basically like this:
Users:
| Id |
|--------|
| John |
| Lizzie |
| Albert |
Movies:
| Id |
|--------------|
| Robocop |
| Notting Hill |
| Inception |
MovieRentals:
| Id | UserId | MovieId |
|-----------|--------|--------------|
| rental-00 | John | Robocop |
| rental-01 | John | Notting Hill |
| rental-02 | John | Notting Hill |
| rental-03 | Lizzie | Robocop |
| rental-04 | Lizzie | Robocop |
| rental-05 | Lizzie | Inception |
Ideally, I want an index to query, that would look like this:
| UserId | MovieId | RentalCount |
|--------|--------------|-------------|
| John | Robocop | 1 |
| John | Notting Hill | 2 |
| John | Inception | 0 |
| Lizzie | Robocop | 2 |
| Lizzie | Notting Hill | 0 |
| Lizzie | Inception | 1 |
| Albert | Robocop | 0 |
| Albert | Notting Hill | 0 |
| Albert | Inception | 0 |
Or declaratively:
I always want a full list of all the movies (eventually I will add filtering/searching) - even when providing a user that has never rented a single movie yet
I want a count of the rentals for each user, just the integer
I want to be able to sort by the rental-count - i.e. show the most-rented movies for a given user at the top of the list
However, I can't find a way to make the "cross-join" above and save it in the index. Instead, I initially thought I got it right with this maneuver below, but it does not allow me to sort (see failing test):
{"Not supported computation: x.UserRentalCounts.SingleOrDefault(rentalCount => (rentalCount.UserId == value(UnitTestProject2.MovieRentalTests+<>c__DisplayClass0_0).user_john.Id)).Count. You cannot use computation in RavenDB queries (only simple member expressions are allowed)."}
My question is basically: how can I - or can I at all - index so, that my requirements are fulfilled?
Below is my mentioned example, that does not fulfill my requirements, but that's where I am right now. It uses the following packages (VS2015):
packages.config
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Owin.Host.HttpListener" version="3.0.1" targetFramework="net461" />
<package id="NUnit" version="3.5.0" targetFramework="net461" />
<package id="RavenDB.Client" version="3.5.2" targetFramework="net461" />
<package id="RavenDB.Database" version="3.5.2" targetFramework="net461" />
<package id="RavenDB.Tests.Helpers" version="3.5.2" targetFramework="net461" />
</packages>
MovieRentalTests.cs
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using Raven.Client.Indexes;
using Raven.Client.Linq;
using Raven.Tests.Helpers;
namespace UnitTestProject2
{
[TestFixture]
public class MovieRentalTests : RavenTestBase
{
[Test]
public void DoSomeTests()
{
using (var server = GetNewServer())
using (var store = NewRemoteDocumentStore(ravenDbServer: server))
{
//Test-data
var user_john = new User { Id = "John" };
var user_lizzie = new User { Id = "Lizzie" };
var user_albert = new User { Id = "Albert" };
var movie_robocop = new Movie { Id = "Robocop" };
var movie_nottingHill = new Movie { Id = "Notting Hill" };
var movie_inception = new Movie { Id = "Inception" };
var rentals = new List<MovieRental>
{
new MovieRental {Id = "rental-00", UserId = user_john.Id, MovieId = movie_robocop.Id},
new MovieRental {Id = "rental-01", UserId = user_john.Id, MovieId = movie_nottingHill.Id},
new MovieRental {Id = "rental-02", UserId = user_john.Id, MovieId = movie_nottingHill.Id},
new MovieRental {Id = "rental-03", UserId = user_lizzie.Id, MovieId = movie_robocop.Id},
new MovieRental {Id = "rental-04", UserId = user_lizzie.Id, MovieId = movie_robocop.Id},
new MovieRental {Id = "rental-05", UserId = user_lizzie.Id, MovieId = movie_inception.Id}
};
//Init index
new Movies_WithRentalsByUsersCount().Execute(store);
//Insert test-data in db
using (var session = store.OpenSession())
{
session.Store(user_john);
session.Store(user_lizzie);
session.Store(user_albert);
session.Store(movie_robocop);
session.Store(movie_nottingHill);
session.Store(movie_inception);
foreach (var rental in rentals)
{
session.Store(rental);
}
session.SaveChanges();
WaitForAllRequestsToComplete(server);
WaitForIndexing(store);
}
//Test of correct rental-counts for users
using (var session = store.OpenSession())
{
var allMoviesWithRentalCounts =
session.Query<Movies_WithRentalsByUsersCount.ReducedResult, Movies_WithRentalsByUsersCount>()
.ToList();
var robocopWithRentalsCounts = allMoviesWithRentalCounts.Single(m => m.MovieId == movie_robocop.Id);
Assert.AreEqual(1, robocopWithRentalsCounts.UserRentalCounts.FirstOrDefault(x => x.UserId == user_john.Id)?.Count ?? 0);
Assert.AreEqual(2, robocopWithRentalsCounts.UserRentalCounts.FirstOrDefault(x => x.UserId == user_lizzie.Id)?.Count ?? 0);
Assert.AreEqual(0, robocopWithRentalsCounts.UserRentalCounts.FirstOrDefault(x => x.UserId == user_albert.Id)?.Count ?? 0);
var nottingHillWithRentalsCounts = allMoviesWithRentalCounts.Single(m => m.MovieId == movie_nottingHill.Id);
Assert.AreEqual(2, nottingHillWithRentalsCounts.UserRentalCounts.FirstOrDefault(x => x.UserId == user_john.Id)?.Count ?? 0);
Assert.AreEqual(0, nottingHillWithRentalsCounts.UserRentalCounts.FirstOrDefault(x => x.UserId == user_lizzie.Id)?.Count ?? 0);
Assert.AreEqual(0, nottingHillWithRentalsCounts.UserRentalCounts.FirstOrDefault(x => x.UserId == user_albert.Id)?.Count ?? 0);
}
// Test that you for a given user can sort the movies by view-count
using (var session = store.OpenSession())
{
var allMoviesWithRentalCounts =
session.Query<Movies_WithRentalsByUsersCount.ReducedResult, Movies_WithRentalsByUsersCount>()
.OrderByDescending(x => x.UserRentalCounts.SingleOrDefault(rentalCount => rentalCount.UserId == user_john.Id).Count)
.ToList();
Assert.AreEqual(movie_nottingHill.Id, allMoviesWithRentalCounts[0].MovieId);
Assert.AreEqual(movie_robocop.Id, allMoviesWithRentalCounts[1].MovieId);
Assert.AreEqual(movie_inception.Id, allMoviesWithRentalCounts[2].MovieId);
}
}
}
public class Movies_WithRentalsByUsersCount :
AbstractMultiMapIndexCreationTask<Movies_WithRentalsByUsersCount.ReducedResult>
{
public Movies_WithRentalsByUsersCount()
{
AddMap<MovieRental>(rentals =>
from r in rentals
select new ReducedResult
{
MovieId = r.MovieId,
UserRentalCounts = new[] { new UserRentalCount { UserId = r.UserId, Count = 1 } }
});
AddMap<Movie>(movies =>
from m in movies
select new ReducedResult
{
MovieId = m.Id,
UserRentalCounts = new[] { new UserRentalCount { UserId = null, Count = 0 } }
});
Reduce = results =>
from result in results
group result by result.MovieId
into g
select new
{
MovieId = g.Key,
UserRentalCounts = (
from userRentalCount in g.SelectMany(x => x.UserRentalCounts)
group userRentalCount by userRentalCount.UserId
into subGroup
select new UserRentalCount { UserId = subGroup.Key, Count = subGroup.Sum(b => b.Count) })
.ToArray()
};
}
public class ReducedResult
{
public string MovieId { get; set; }
public UserRentalCount[] UserRentalCounts { get; set; }
}
public class UserRentalCount
{
public string UserId { get; set; }
public int Count { get; set; }
}
}
public class User
{
public string Id { get; set; }
}
public class Movie
{
public string Id { get; set; }
}
public class MovieRental
{
public string Id { get; set; }
public string MovieId { get; set; }
public string UserId { get; set; }
}
}
}
Since your requirement says "for a given user", if you really are looking only for a single user, you can do this with a Multi-Map index. Use the Movies table itself to produce the baseline zero-count records and then map in the actual MovieRentals records for the user on top of that.
If you really need it for all users crossed with all movies, I don't believe there is a way to do this cleanly with RavenDB as this would be considered reporting which is noted as one of the sour spots for RavenDB.
Here are some options if you really want to try to do this with RavenDB:
1) Create dummy records in the DB for every user and every movie and use those in your index with a 0 count. Whenever a movie or user is added/updated/deleted, update the dummy records accordingly.
2) Generate the zero-count records yourself in memory on request and merge that data with the data that RavenDB gives you back for the non-zero counts. Query for all users, query for all movies, create the baseline zero-count records, then do the actual query for non-zero counts and layer that on top. Finally, apply paging/filtering/sorting logic.
3) Use the SQL replication bundle to replicate the Users, Movies, and MovieRental tables out to SQL and use SQL for this "reporting" query.
ID Name from to
001-1 ABC 2015/05/01 2015/05/31
001-1 ABC 2015/06/01 2015/07/15
003-2 DEF 2015/05/01 2015/05/11
002-1 LMN 2015/05/01 2015/06/15
002-1 LMN 2015/06/16 2015/07/31
003-2 DEF 2015/06/01 2015/07/15
004-5 GHI 2015/05/11 2015/05/15
I want to have merge the records into one which matching the period from 2015/05/15 to 2015/07/15 like the following result in datable.
ID Name from to
001-1 ABC 2015/05/01 2015/07/15
002-1 LMN 2015/05/01 2015/07/31
003-2 and 004-5 are not in new datatable as they are not in the require range.
How can I get this? I only know very basic knowledge about LINQ and it's very fresh to me. thx.
With this class / data as a mockup:
class Item
{
public string ID { get; set; }
public string Name { get; set; }
public DateTime From { get; set; }
public DateTime To { get; set; }
}
List<Item> items = new List<Item> {
new Item { ID = "001-1", Name = "ABC",
From = DateTime.Parse("2015/05/01"),
To = DateTime.Parse("2015/05/31") },
new Item { ID = "001-1", Name = "ABC",
From = DateTime.Parse("2015/06/01"),
To = DateTime.Parse("2015/07/15") },
new Item { ID = "003-2", Name = "DEF",
From = DateTime.Parse("2015/05/01"),
To = DateTime.Parse("2015/05/11") },
new Item { ID = "002-1", Name = "LMN",
From = DateTime.Parse("2015/05/01"),
To = DateTime.Parse("2015/06/15") },
new Item { ID = "002-1", Name = "LMN",
From = DateTime.Parse("2015/06/16"),
To = DateTime.Parse("2015/07/31") },
new Item { ID = "003-2", Name = "DEF",
From = DateTime.Parse("2015/06/01"),
To = DateTime.Parse("2015/07/15") },
new Item { ID = "004-5", Name = "GHI",
From = DateTime.Parse("2015/05/11"),
To = DateTime.Parse("2015/05/15") }
};
you can use the following linq query to get the desired result set:
var result = from i in items
orderby i.From
group i by new { i.ID, i.Name } into iGroup
where iGroup.First().From <= DateTime.Parse("2015/05/15") &&
iGroup.Last().To >= DateTime.Parse("2015/07/1") &&
(iGroup.Last().To - iGroup.First().From).Days + 1 ==
iGroup.Sum(g => (g.To - g.From).Days + 1)
select new Item
{
ID = iGroup.Key.ID,
Name = iGroup.Key.Name,
From = iGroup.First().From,
To = iGroup.Last().To
};
You can adjust datetime comparisons to fit your actual requirement. In the above linq query I am comparing the smallest From date and the largest To date of each group to the matching period dates.
This comparison:
(iGroup.Last().To - iGroup.First().From).Days + 1 ==
iGroup.Sum(g => (g.To - g.From).Days + 1)
checks for groups that have no gaps in their date ranges.
EDIT:
If the source data is stored in a DataTable such as:
DataTable items = new DataTable();
items.Columns.Add("ID", typeof(string));
items.Columns.Add("Name", typeof(string));
items.Columns.Add("From", typeof(DateTime));
items.Columns.Add("To", typeof(DateTime));
then the linq query becomes a bit more complicated:
var q = from i in items.AsEnumerable()
orderby i.Field<DateTime>("From")
group i by new { ID = i.Field<string>("ID"), Name = i.Field<string>("Name") } into iGroup
where iGroup.First().Field<DateTime>("From") <= DateTime.Parse("2015/05/15") &&
iGroup.Last().Field<DateTime>("To") >= DateTime.Parse("2015/07/1") &&
(iGroup.Last().Field<DateTime>("To") - iGroup.First().Field<DateTime>("From")).Days + 1 ==
iGroup.Sum(g => (g.Field<DateTime>("To") - g.Field<DateTime>("From")).Days + 1)
select new
{
ID = iGroup.Key.ID,
Name = iGroup.Key.Name,
From = iGroup.First().Field<DateTime>("From"),
To = iGroup.Last().Field<DateTime>("To")
};
The above query returns an IEnumerable of anonymous type. It can be converted back to a DataTable using Reflection (see this post for example).
I am trying to convert this sql to hql to fetch the data from artifact table. I have this sql query which works fine on the database
select a.*
from classification c
join (select id from taxonomy_node start with id = 5067 connect by nocycle prior id = parent_id) h
on (c.node_id = h.id)
join artifact a on (a.id = c.artifact_id)
Where a.DOCUMENT_ID = 10462 AND c.active=1
I am trying to convert it to HQL
artifacts = Artifact.executeQuery("FROM classification c "+
"JOIN (SELECT id FROM taxonomy_node START WITH id = :nodeId "+
"CONNECT BY NOCYCLE PRIOR id = parent_id) h "+
"ON (c.node_id = h.id) "+
"JOIN artifact a ON (a.id = c.artifactId) "+
"WHERE a.DOCUMENT_ID = :docid AND c.active=1",
[nodeId: NodeId ,docid: document.id],
[max:limit, offset:startIndex])
The domain classes look like
Classification
class Classification {
public static final String USER_DEFAULT = "USER"
public static final String USER_SYSTEM = "SYSTEM"
TaxonomyNode node
String artifactId //TODO can we drive this with an annotated object or something?
Boolean active
String createdBy
String updatedBy
Date dateCreated
Date lastUpdated
// or should we create an Artifact Toolkit plugin?
static constraints = {
node nullable:false, blank:false
artifactId nullable:false, blank:false, unique: ['node']
active nullable: false, blank: false
createdBy nullable:false, blank:false
updatedBy nullable:false, blank:false
}
static mapping = {
id generator:'sequence', params:[sequence:'classification_seq']
artifactId index: 'classify_by_artifact_node'
node index: 'classify_by_artifact_node'
active defaultValue: "1"
}
}
Artifact looks like
class Artifact {
public enum ArtifactType {
CLAUSE("Clause"),
TITLE("Title"),
DEFINED_TERMS("Defined Terms")
private final String value
ArtifactType(String value) { this.value = value }
String toString() { value }
String getKey() { name() }
}
public enum ArtifactStatus {
ALL("All"),
REVIEWED("Reviewed"),
FOR_REVIEW("For Review"),
UNCLASSIFIED("Unclassified")
private final String value
ArtifactStatus(String value) { this.value = value }
String toString() { value }
String getKey() { name() }
}
Document document
String artifactType
String text
String status
String createdBy
String updatedBy
Date dateCreated
Date lastUpdated
static belongsTo = Document
static hasMany = [classification: Classification]
static constraints = {
artifactType maxSize: 50, nullable: false, blank: false
text nullable: false, blank: false, maxSize: 4000
status nullable:true, blank:true
createdBy nullable:false, blank:false
updatedBy nullable:false, blank:false
}
static mapping = {
id generator:'sequence', params:[sequence:'artifact_seq']
document index: 'artifact_by_doc'
artifactType index: 'artifact_by_doc,artifact_by_type'
text index: 'artifact_by_doc,artifact_by_text'
}
}
the taxonomy Node looks like
class TaxonomyNode {
public static final String USER_DEFAULT = "USER"
public static final String USER_SYSTEM = "SYSTEM"
String hierarchyId
String nodeId
String label
String inputFormat
Boolean active
String createdBy
String updatedBy
Date dateCreated
Date lastUpdated
static hasMany = [metadata: TaxonomyNodeMetadata]
TaxonomyNode parent
static constraints = {
inputFormat nullable:true, blank:true //TODO for now we want to allow null/blank for user-entered nodes
parent nullable:true //TODO we should enforce that only one node per hierarchyId can have a null parent
hierarchyId nullable:false, blank:false //TODO we should enforce the parent's hierarchyId is the same
active nullable: false, blank: false
nodeId nullable:false, blank:false, unique: ['hierarchyId']
label nullable:true, blank:true
createdBy nullable:false, blank:false
updatedBy nullable:false, blank:false
}
static mapping = {
id generator:'sequence', params:[sequence:'taxonomy_node_seq']
hierarchyId index: 'taxonomy_by_hier'
label index: 'taxonomy_by_hier,taxonomy_by_label'
parent index: 'taxonomy_by_hier,taxonomy_by_parent'
active defaultValue: "1"
}
}
I am getting this error
Error |
2014-10-22 09:22:38,633 [http-bio-8080-exec-4] ERROR ast.ErrorCounter - line 1:28: unexpected token: (
Error |
2014-10-22 09:22:38,634 [http-bio-8080-exec-4] ERROR ast.ErrorCounter - line 1:28: unexpected token: (
Message: unexpected token: (
Line | Method
->> 281 | $tt__index in com.ald.aeandsdx.ArtifactController
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| 198 | doFilter in grails.plugin.cache.web.filter.PageFragmentCachingFilter
| 63 | doFilter . in grails.plugin.cache.web.filter.AbstractFilter
| 1145 | runWorker in java.util.concurrent.ThreadPoolExecutor
| 615 | run . . . in java.util.concurrent.ThreadPoolExecutor$Worker
^ 745 | run in java.lang.Thread
Error |
org.springframework.orm.hibernate4.HibernateQueryException: unexpected token: ( near line 1, column 28 [FROM classification c JOIN (SELECT id FROM taxonomy_node START WITH id = :nodeId CONNECT BY NOCYCLE PRIOR id = parent_id) hON (c.node_id = h.id) JOIN artifact a ON (a.id = c.artifactId) WHERE a.DOCUMENT_ID = :docid AND c.active=1]; nested exception is org.hibernate.hql.internal.ast.QuerySyntaxException: unexpected token: ( near line 1, column 28 [FROM classification c JOIN (SELECT id FROM taxonomy_node START WITH id = :nodeId CONNECT BY NOCYCLE PRIOR id = parent_id) hON (c.node_id = h.id) JOIN artifact a ON (a.id = c.artifactId) WHERE a.DOCUMENT_ID = :docid AND c.active=1]
HQL supports subqueries only in SELECT and WHERE clauses, see: http://docs.jboss.org/hibernate/orm/3.3/reference/en/html/queryhql.html#queryhql-subqueries
I've some 37K documents stored in Mongo that looks similar to these:
{
"_id" : GUID,
"Country" : "Germany",
"TypeIds" : [47]
}
{
"_id" : GUID,
"Country" : "France",
"TypeIds" : [54, 47]
}
Using the MongoDB C# driver, and based on the two example records, how can I query for the following information:
All documents that have TypeIds containing 47 or 54 - should result in 2 records
All documents that have TypeIds containing 47 AND 54 - should result in 1 records
All documents that have TypeIds containing 54 AND a Country of 'Germany' - should result in 0 records
Thanks,
Kieron
You have class like this(i just instead of guid use GuidId.ToString()):
public class Test
{
public Test()
{
TypeIds = new List<int>();
}
[BsonId]
public string Id { get; set; }
public string Country { get; set; }
public List<int> TypeIds { get; set; }
}
i've inserted rows into db according above documents
var collection = db.Database.GetCollection("items");
var id1 = Guid.NewGuid().ToString();
var id2 = Guid.NewGuid().ToString();
var test = new Test() { Id = id1, Country = "Germany" };
test.TypeIds.Add(47);
var test2 = new Test() { Id = id2, Country = "France" };
test2.TypeIds.Add(54);
test2.TypeIds.Add(47);
collection.Insert(test);
collection.Insert(test2);
Queries:
//All documents that have TypeIds containing 47 or 54 - should result in 2 records
var array = new List<int>() { 47, 54 };
var condition1 = collection.FindAs<Test>(Query.In("TypeIds", BsonArray.Create(array))).ToList();
//All documents that have TypeIds containing 54 AND a Country of 'Germany' - should result in 0 records
var condition3 = collection.FindAs<Test>(Query.And(Query.EQ("TypeIds", 47), Query.EQ("Country", "Germany"))).ToList();
Update:
I found way to do second condition:
//All documents that have TypeIds containing 47 AND 54 - should result in 1 records
var array2 = new List<int>() { 47, 54 };
var query = Query.All("TypeIds",BsonArray.Create(array2));
var condition2 = collection.FindAs<Test>(query).ToList();