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
Related
Hey I am working on tree data structure. I want to know can we get index of each node in level wise. I below diagram represent how I want the value + index. Level A or B represent node value and index value represent index value
Node
| | |
Level A -> 1 2 3
index value-> 0 1 2
| | | | | |
| | | | | |
Leve B-> 4 5 6 7 8 9
index value-> 0 1 2 3 4 5
....// more level
How can we achieved index in each level wise. I am adding my logic how I am adding value in each level wise. Could you someone suggest how can I achieve this?
var baseNode: LevelIndex = LevelIndex()
var defaultId = "1234"
fun main() {
val list = getUnSortedDataListForLevel()
val tempHashMap: MutableMap<String, LevelIndex> = mutableMapOf()
list.forEach { levelClass ->
levelClass.levelA?.let { levelA ->
val levelOneTempHashMapNode = tempHashMap["level_a${levelA}"]
if (levelOneTempHashMapNode != null) {
if (defaultId == levelClass.id && levelOneTempHashMapNode is LevelOne) {
levelOneTempHashMapNode.defaultValue = true
}
return#let
}
val tempNode = LevelOne().apply {
value = levelA
if (defaultId == levelClass.id) {
defaultValue = true
}
}
baseNode.children.add(tempNode)
tempHashMap["level_a${levelA}"] = tempNode
}
levelClass.levelB?.let { levelB ->
val levelTwoTempHashMapNode = tempHashMap["level_a${levelClass.levelA}_level_b${levelB}"]
if (levelTwoTempHashMapNode != null) {
if (defaultId == levelClass.id && levelOneTempHashMapNode is LevelTwo) {
levelTwoTempHashMapNode.defaultValue = true
}
return#let
}
val tempNode = LevelTwo().apply {
value = levelB
if (defaultId == levelClass.id) {
defaultValue = true
}
}
val parent =
tempHashMap["level_a${levelClass.levelA}"] ?: baseNode
parent.children.add(tempNode)
tempHashMap["level_a${levelClass.levelA}_level_b${levelB}"] =
tempNode
}
levelClass.levelC?.let { levelC ->
val tempNode = LevelThree().apply {
value = levelC
if (defaultId == levelClass.id) {
defaultValue = true
}
}
val parent =
tempHashMap["level_a${levelClass.levelA}_level_b${levelClass.levelB}"]
?: baseNode
parent.children.add(tempNode)
}
}
}
open class LevelIndex(
var value: String? = null,
var children: MutableList<LevelIndex> = arrayListOf()
)
class LevelOne : LevelIndex() {
var defaultValue: Boolean? = false
}
class LevelTwo : LevelIndex() {
var defaultValue: Boolean? = false
}
class LevelThree : LevelIndex() {
var defaultValue: Boolean = false
}
UPDATE
I want index value by root level because, I have one id, I want to match that combination with that id, if that value is present then I am storing that value b true, and need to find that index value.
Node
| | |
Level A -> 1 2 3
index value-> 0 1 2
default value-> false true false
| | | | | |
| | | | | |
Leve B-> 4 5 6 7 8 9
index value-> 0 1 2 3 4 5
default value->false false true false false false
....// more level
So, Level A I'll get index 1.
For Level B I'll get index 2
I'd create a list to put the nodes at each level in order. You can recursively collect them from your tree.
val nodesByLevel = List(3) { mutableListOf<LevelIndex>() }
fun collectNodes(parent: LevelIndex) {
for (child in parent.children) {
val listIndex = when (child) {
is LevelOne -> 0
is LevelTwo -> 1
is LevelThree -> 2
// I made LevelIndex a sealed class. Otherwise you would need an else branch here.
}
nodesByLevel[listIndex] += child
collectNodes(child)
}
}
collectNodes(baseNode)
Now nodesByLevel contains three lists containing all the nodes in each layer in order.
If you just need the String values, you could change that mutableList to use a String type and use += child.value ?: "" instead, although I would make value non-nullable (so you don't need ?: ""), because what use is a node with no value?
Edit
I would move defaultValue up into the parent class so you don't have to cast the nodes to be able to read it. And I'm going to treat is as non-nullable.
sealed class LevelIndex(
var value: String = "",
val children: MutableList<LevelIndex> = arrayListOf()
var isDefault: Boolean = false
)
Then if you want to do something with the items based on their indices:
for ((layerNumber, layerList) in nodesByLevel.withIndex()) {
for((nodeIndexInLayer, node) in layerList) {
val selectedIndexForThisLayer = TODO() //with layerNumber
node.isDefault = nodeIndexInLayer == selectedIndexForThisLayer
}
}
Iam having a table like this
SorOrder Name Date
1 Image1 5/6/15
2 Image2 6/8/16
3 Image3 6/8/16
4 Image4 9/8/16
..........
Now if iam deleting image2 i want to udate the table so that the sortorder
again is in ordered form like this
Updated Table :
SorOrder Name Date
1 Image1 5/6/15
2 Image3 6/8/16
3 Image4 9/8/16
..........
SO how to make it posible??
This is the class for the table Images:
public class Images extends ApplicationEntity{
#Column(name="PROFILE_ID", allowsNull="false")
private Profile profile;
private int sortOrder;
private boolean active;
private Date deletedDate;
public Images (){
super.setEntity("Images ");
}
public Images (Profile profile, int sortOrder, boolean active,
Date deletedDate) {
super();
this.profile = profile;
this.sortOrder = sortOrder;
this.active = active;
this.deletedDate = deletedDate;
}
public Profile getProfile() {
return profile;
}
public int getSortOrder() {
return sortOrder;
}
public void setSortOrder(int sortOrder) {
this.sortOrder = sortOrder;
}
public boolean isActive() {
return active;
}
public void setActive(boolean active) {
this.active = active;
}
public Date getDeletedDate() {
return deletedDate;
}
public void setDeletedDate(Date deletedDate) {
this.deletedDate = deletedDate;
}
#Override
public String toString() {
return "Images [profile=" + profile + ", sortOrder=" + sortOrder
+ ", active=" + active + ", deletedDate=" + deletedDate + "]";
}
}
I tried this query: String query = "update Images set SORTORDER =((SELECT selected_value FROM (SELECT MAX(SORTORDER) AS selected_value FROM Images where ACTIVE = 0 && PROFILE_Id="+profileId+") AS sub_selected_value) + 1) where PROFILE_Id="+profileId;
But it updates all the sorOrder to same value.
I was using this code to update the sortorder:
int sortoder=1;
for (Images file : imagesListFromDB) {
file.setSortOrder(sortOrder);
sortOrder++;
}
But it takes more time,if iam having 8000 images then its really slow.SO i thought of updating in a single query. But not getting any idea
To do in a single statement you could make use of SQL. Here are a couple of ideas (adapt to your use-case) - you use the "?" parameter to set the position above what you delete.
UPDATE IMAGES SET SORTORDER =
(CASE WHEN (SORTORDER <= ?) THEN SORTORDER
ELSE (SORTORDER-1) END)
Or
UPDATE IMAGES SET SORTORDER = SORTORDER-1
WHERE SORTORDER > ?
Using DataNucleus JDOQL UPDATE extension you could do this (and set the parameter "param" to the sortOrder start point to update
pm.newQuery("UPDATE mydomain.Images SET this.sortOrder=this.sortOrder-1 WHERE this.sortOrder > :param");
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 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));
I Have 4 tables:
Position Table:
| Position | PositionId |
| driver | 1 |
| clerk | 2 |
position Skill table:
| SkillId | skill | PositionId |
| 1 | driving | 1 |
| 2 | drifting | 1 |
Worker table:
| Name | WorkerId |
| John | 1 |
| alex | 2 |
Worker skill table:
| skillId | skill | WorkerId |
| 1 | driving | 1 |
| 2 | drifting | 1 |
I join the position table with position Skill table
and worker table with worker skill
What I'm having trouble with is how can I compare the two joined tables to have a result of
for example:
I need to know who's worker have all the specific skills that the position have
Like:
I Select position with positionId of 1 and have the skillname of driving and drifting
I need to get the Worker with the same skills with driving and drifting also
so far i got this:
var PositionsWithSkills = (from a in db.Client_Customer_Position
where a.ID == position
select new
{
PositionID = a.ID,
RequiredSkills = (from b in db.Client_Customer_Position_Skills
where b.ClientCusPosId == a.ID
select b.SkillName)
}).ToList();
var WorkersWithSkills = (from x in db.Workers
select new
{
workerId = x.ID,
Skills = (from y in db.Worker_Skills
where y.Worker_ID == x.ID
select y.SkillName)
}).ToList();
var PositionWithSkilledWorkers = (from pos in PositionsWithSkills
select new
{
PositionId = pos.PositionID,
Workers = (from worker in WorkersWithSkills
where pos.RequiredSkills.All(skill => worker.Skills.Any(workerSkill => workerSkill == skill))
select worker.workerId)
}).ToList();
the two query works well.. but the last query where i must compare the two query =.. i cant get the worker id
and can i turn this to a stored proc?
Sorry if am wrong. What I got to know from your question is you want the workers list satisfying all the skills of the position you pass. If this is what you want you may try this:
var workerWithallSkill = (from u in db.workerList join x in db.workerSkillList on u.WorkerId equals x.WorkerId
where ((from y in db.workerSkillList where y.WorkerId == u.WorkerId select y).Count() == (from p in db.positionSkillList where p.PositionId == 1("pass your positionId here") select p).Count())
select u).ToList().Distinct();
or if you want to use lambda expression you can use this
var workerWithallSkill = (from u in workerList join x in workerSkillList on u.WorkerId equals x.WorkerId where (workerSkillList.Where(y=> y.WorkerId == u.WorkerId).Count() == positionSkillList.Where(p=>p.PositionId == 1).Count()) select u).ToList().Distinct();
For more understanding you can try the below code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication8
{
class Program
{
static void Main(string[] args)
{
IList<Position> positionList = new List<Position>() {
new Position(){ position="Driver", PositionId=1}
,new Position(){ position="clerk", PositionId=2}
};
IList<PositionSkill> positionSkillList = new List<PositionSkill>() {
new PositionSkill(){ Skill = "driving",skillid = 1,PositionId = 1}
,new PositionSkill(){ Skill = "drifting",skillid = 2,PositionId = 1}
};
IList<Worker> workerList = new List<Worker>() {
new Worker(){ name = "John",WorkerId = 1}
,new Worker(){ name = "alex",WorkerId = 2}
};
IList<WorkerSkill> workerSkillList = new List<WorkerSkill>(){
new WorkerSkill(){Skill = "driving",skillid = 1,WorkerId = 2}
, new WorkerSkill(){Skill = "drifting",skillid = 2,WorkerId = 2}
};
var workerWithallSkill = (from u in workerList join x in workerSkillList on u.WorkerId equals x.WorkerId where (workerSkillList.Where(y => y.WorkerId == u.WorkerId).Count() == positionSkillList.Where(p => p.PositionId == 1).Count()) select u).ToList().Distinct();
foreach (var worker in workerWithallSkill)
{
Console.WriteLine(worker.name);
}
Console.ReadLine();
}
}
public class Position
{
public string position { get; set; }
public int PositionId { get; set; }
}
public class PositionSkill
{
public int skillid { get; set; }
public string Skill { get; set; }
public int PositionId { get; set; }
}
public class Worker
{
public string name { get; set; }
public int WorkerId { get; set; }
}
public class WorkerSkill
{
public int skillid { get; set; }
public string Skill { get; set; }
public int WorkerId { get; set; }
}
}
if a worker has skills from different positions the above code will not work, if this is the scenario try the below code:
var WorkerPositionSkill = from p in db.positionSkillList join q in db.workerSkillList on p.skillid equals q.skillid select new { posSkill = p, workerSkill = q };
var workerWithallSkill = (from u in db.workerList join x in db.workerSkillList on u.WorkerId equals x.WorkerId where (WorkerPositionSkill.Where(y => y.workerSkill.WorkerId == u.WorkerId && y.posSkill.PositionId == 1).Count() == db.positionSkillList.Where(p => p.PositionId == 1).Count()) select u).ToList().Distinct();
This is highly unlikely to work with Linq To SQL because...its a huge steaming pile of #$%&. But this Linq query should given a sufficiently magical IQueryProvider give the right SQL. I've seen some very magical things come from Entity Framework.
var PositionsWithSkills = from a in db.Client_Customer_Position
where a.ID == position
select new
{
PositionID = a.ID,
RequiredSkills = (from b in db.Client_Customer_Position_Skills
where b.ClientCusPosId == a.ID
select b.SkillName)
};
var WorkersWithSkills = from x in db.Workers
select new
{
workerId = x.ID,
Skills = (from y in db.Worker_Skills
where y.Worker_ID == x.ID
select y.SkillName)
};
var PositionWithSkilledWorkers = from pos in PositionsWithSkills
from worker in WorkersWithSkills
where pos.RequiredSkill.All(worker.Skills.Contains)
group worker.Name by pos.PositionID;
PS please learn to use associations as opposed to join/where. If you are going to use join/where, you might as well just use SQL.
var PositionsWithSkills = (from a in Positions select new {
PositionID = a.PositionId,
RequiredSkills = (from b in PositionSkills where b.PositionId == a.PositionId select b.skillId).ToList()
}).ToList();
var WorkersWithSkills = (from x in Workers select new {
Name = x.Name,
Skills = (from y in WorkerSkills where y.WorkerId == x.WorkerID select y.skillId).ToList()
}).ToList();
var PositionWithSkilledWorkers = (from pos in PositionsWithSkills select new {
PositionId = pos.PositionID,
Workers = (from worker in WorkersWithSkills where pos.RequiredSkills.All(skill => worker.Skills.Any(workerSkill => workerSkill == skill)) select worker.Name).ToList()
}).ToList();
i think, your database's tables have not been designed correctly...
you need a relation between worker skill and position skill, i think your tables must be desinged like this:
Skill table: SkillID, Skill
Position table: PositionID, Position
PositionSkill table: ID, SkillID, PositionID
Worker table: WorkerID, Name
WorkerSkill table: ID, SkillID, WorkerID
but by this way you designed your tables, if assume skill field (description of skills) are the same in worker skill and position skill, we can use this as a relation, and your query can be like this:
// skills of specific position
var PositionSkills = Context.PositionSkill.Where(u => u.PositionId == 1);
var WorkersWithSkills = Context.Worker
.Join(Context.WorkerSkill,
worker => worker.WorkerId,
workerSkill => workerSkill.WorkerId,
(worker, workerSkill) => new { worker, workerSkill })
.GroupBy(u => u.worker)
.Select(u => new
{
u.Key.WorkerId,
u.Key.Name,
Skills = u.Select(t => t.workerSkill.skill)
});
var SkilledWorkers = WorkersWithSkills
.Where(u => PositionSkills.All(t => u.Skills.Contains(t.skill)))
.ToList();
if you wont change your database's tables, you can add a join table between position skill and worker skill like:
WorkerPositionSkill: PositionSkillID, WorkerSkillID
Here is a LinqPad program that returns the result, { Worker = John, Position = Driver}. If I understand your requirements you want to find a worker who satisfies the conditions where the worker has all the skills required for Position = 1, which is driving and drifting skills. The query returns two rows the following [{worker = John, Position = Driver}, {worker = John, Position = Driver}]. I had to use distinct to display it once. The reason for two rows is he satisfies both driving and drifting job skills. If the position required 4 skills in which the worker met, there would be 4 duplicate rows. The unique fixes that problem. Hope this helps you along.
I created this solution in LinqPad, which is great tool with hundred's of very well documented linq query examples.
void Main()
{
// Table Setup
// ************
var position = new List<Position>();
position.Add(new Position { Id = 1, Name = "driver" });
position.Add(new Position { Id = 2, Name = "clerk" });
var positionSkill = new List<PositionSkill>();
positionSkill.Add(new PositionSkill { Id = 1, Skill = "driving", PositionId = 1 });
positionSkill.Add(new PositionSkill { Id = 2, Skill = "drifting", PositionId = 1 });
var worker = new List<Worker>();
worker.Add(new Worker { Id = 1, Name = "John" });
worker.Add(new Worker { Id = 2, Name = "alex" });
var workerSkill = new List<WorkerSkill>();
workerSkill.Add(new WorkerSkill { Id = 1, Skill = "driving", WorkerId = 1 });
workerSkill.Add(new WorkerSkill { Id = 2, Skill = "drifting", WorkerId = 1 });
// The Query
// *********
var positionValue = 1;
var r = from p in position
join ps in positionSkill on p.Id equals ps.PositionId
join ws in workerSkill on ps.Skill equals ws.Skill
join w in worker on ws.WorkerId equals w.Id
where p.Id == positionValue
select new {
PositionName = p.Name,
WorkerName = w.Name
};
// Get Distinct Names
r.Distinct().Dump();
}
// Define other methods and classes here
public class Position
{
public int Id { get; set; }
public string Name { get; set; }
}
public class PositionSkill
{
public int Id { get; set; }
public string Skill { get; set; }
public int PositionId { get; set; }
}
public class Worker
{
public int Id { get; set; }
public string Name { get; set; }
}
public class WorkerSkill
{
public int Id { get; set; }
public string Skill { get; set; }
public int WorkerId { get; set; }
}