Problem with FieldCompareSetPredicate sub-select query

Posts   
 
    
Stevenn
User
Posts: 30
Joined: 26-Sep-2007
# Posted on: 16-Jul-2015 16:11:06   

I have two tables in a test database: Person(PersonId, Fk_ParentPersonId) and PersonDetails (Fk_PersonId, Name, Surname)

What i am trying to test is, given someone's surname, return the details for their parent. This is the query:

SELECT DISTINCT [TestDB].[dbo].[PersonDetails].[Name], [TestDB].[dbo].[PersonDetails].[Surname], [TestDB].[dbo].[Person].[PersonId], [TestDB].[dbo].[Person].[Fk_ParentPersonId] FROM ( [TestDB].[dbo].[Person]  INNER JOIN [TestDB].[dbo].[PersonDetails]  ON  [TestDB].[dbo].[Person].[PersonId]=[TestDB].[dbo].[PersonDetails].[Fk_PersonId]) WHERE ( [TestDB].[dbo].[Person].[PersonId] IN 
(SELECT [TestDB].[dbo].[Person].[Fk_ParentPersonId] FROM [TestDB].[dbo].[Person] INNER JOIN [TestDB].[dbo].[PersonDetails] ON  [TestDB].[dbo].[Person].[PersonId]=[TestDB].[dbo].[PersonDetails].[Fk_PersonId]  WHERE [TestDB].[dbo].[PersonDetails].[Surname] = 'S2'))

This returns the expected result. To try and generate this, i used:

bucket.PredicateExpression.Add(new FieldCompareSetPredicate(
                        PersonFields.PersonId, null, PersonFields.FkParentPersonId, null,
                        SetOperator.In, (PersonDetailFields.Surname == textBox5.Text))); 

However, the SQL that is generated does not contain the INNER JOIN that is included in the sub-select in the above SQL statement and so no records are returned. The typedlist just contains the two entities linked with an INNER. What am i missing?

Pro 4.2 Final, June 9th, 2015.

Thanks.

Walaa avatar
Walaa
Support Team
Posts: 14950
Joined: 21-Aug-2005
# Posted on: 16-Jul-2015 23:41:06   

You would either use an In query (fieldCompareSetPredicate) or an innerJoin (Relation). I don't see a reason to have both of them.

Are you fetching an EntityCollection or a TypedList?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 17-Jul-2015 11:27:35   

... and if typed list, what's the definition of the typed list, and what query is generated exactly? (please enable tracing to obtain it, thanks)

Frans Bouma | Lead developer LLBLGen Pro
Stevenn
User
Posts: 30
Joined: 26-Sep-2007
# Posted on: 20-Jul-2015 11:13:49   

Hi

I am trying to retrieve a typed list. The typed list consists of both tables, connected with an inner join. I am trying to retrieve the details of the <parent> of the passed in Surname, fields: persondetails.Name, persondetails.Surname, person.PersonId and person.Fk_ParentPersonId (could be null). person.Fk_ParentPersonId has a parent/child relationship with person.PersonId and allows nulls.

The query that is generated is as follows:


Generated Sql query: 
    Query: SELECT DISTINCT [TestDB].[dbo].[PersonDetails].[Name], [TestDB].[dbo].[Person].[PersonId], [TestDB].[dbo].[Person].[Fk_ParentPersonId] AS [ParentPersonId], [LPA_P1].[Surname] FROM (( [TestDB].[dbo].[Person]  INNER JOIN [TestDB].[dbo].[PersonDetails]  ON  [TestDB].[dbo].[Person].[PersonId]=[TestDB].[dbo].[PersonDetails].[Fk_PersonId]) INNER JOIN [TestDB].[dbo].[PersonDetails] [LPA_P1]  ON  [TestDB].[dbo].[Person].[PersonId]=[LPA_P1].[Fk_PersonId]) WHERE ( [TestDB].[dbo].[Person].[PersonId] IN (SELECT [TestDB].[dbo].[Person].[Fk_ParentPersonId] AS [FkParentPersonId] FROM [TestDB].[dbo].[Person]   WHERE [TestDB].[dbo].[PersonDetails].[Surname] = @p1))
    Parameter: @p1 : String. Length: 20. Precision: 0. Scale: 0. Direction: Input. Value: "S2".

If i manually put the second INNER JOIN in the sub-select statement, then i can run that in SSMS and get the desired result. This is against a LocalDB database, though i imagine that makes no difference.

It is easy enough to get the result by running two queries, the first to retrieve the PersonId of the input Surname, and then pull the parent, if any, of that PersonId, but i am trying to minimise calls and i don't want to pull the entire list down just to allow someone to lookup a Surname.

Thanks

Walaa avatar
Walaa
Support Team
Posts: 14950
Joined: 21-Aug-2005
# Posted on: 20-Jul-2015 18:03:08   

SELECT DISTINCT

[TestDB].[dbo].[PersonDetails].[Name], [TestDB].[dbo].[Person].[PersonId], [TestDB].[dbo].[Person].[Fk_ParentPersonId] AS [ParentPersonId], [LPA_P1].[Surname]

FROM (( [TestDB].[dbo].[Person] INNER JOIN [TestDB].[dbo].[PersonDetails] ON [TestDB].[dbo].[Person].[PersonId]=[TestDB].[dbo].[PersonDetails].[Fk_PersonId]) INNER JOIN [TestDB].[dbo].[PersonDetails] [LPA_P1] ON [TestDB].[dbo].[Person].[PersonId]=[LPA_P1].[Fk_PersonId])

WHERE ( [TestDB].[dbo].[Person].[PersonId] IN (SELECT [TestDB].[dbo].[Person].[Fk_ParentPersonId] AS [FkParentPersonId] FROM [TestDB].[dbo].[Person] WHERE [TestDB].[dbo].[PersonDetails].[Surname] = @p1))

I wonder why the Surname uses a table alias in the select list. Please provide the typedList fetch code. Also is there multiple instances of PersonDetails entity in the TypedList?

Ideally you you'd be good without the LPA_P1 in the select list, and with only one inner join (duplication is not needed, and without the IN predicate, only a filter by the Surname would be enough.

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 21-Jul-2015 08:16:32   

My two cents: As Walaa says, you only need the JOIN there. Here is a similar example where I want the Manager (parent) information from a specific employee (child).

[TestMethod]
public void FetchParentDetails()
{
    // set the fields I want from the Parent 
    var fields = new EntityFields2(3);
    fields.DefineField(EmployeeFields.EmployeeId, 0, "EmployeeId", "Parent");
    fields.DefineField(EmployeeFields.FirstName, 1, "FirstName", "Parent");
    fields.DefineField(EmployeeFields.LastName, 2, "LastName", "Parent");

    // set the filter on the child, and add the appropriate relation
    var employeeFilter = new RelationPredicateBucket(EmployeeFields.LastName.SetObjectAlias("Child") == "Davolio");
    employeeFilter.Relations.Add(EmployeeEntity.Relations.EmployeeEntityUsingEmployeeIdReportsTo, "Child", "Parent", JoinHint.Left);

    // fetch results
    var results = new DataTable();
    using (var adapter = new DataAccessAdapter())
    {
        adapter.FetchTypedList(fields, results, employeeFilter); 
    }

    // test: Davolis's manager should be Fuller.
    Assert.AreEqual(1, results.Rows.Count);
    Assert.AreEqual("Fuller", results.Rows[0][2].ToString());
}

Note that the filter is on the child, because I want the parent information for the child whose last name is "Davolio". The only difference from your scenario is that you have an additional 1:1 table called PersonDetails. If employee in the example would have that, it would be something like this (add additional join between the parent and the details table:

[TestMethod]
public void FetchParentDetails()
{
    // set the fields I want from the Parent 
    var fields = new EntityFields2(3);
    fields.DefineField(EmployeeDetailsFields.EmployeeId, 0, "EmployeeId", "ParentDetails");
    fields.DefineField(EmployeeDetailsFields.FirstName, 1, "FirstName", "ParentDetails");
    fields.DefineField(EmployeeDetailsFields.LastName, 2, "LastName", "ParentDetails");

    // set the filter on the child, and add the appropriate relation
    var employeeFilter = new RelationPredicateBucket(EmployeeFields.LastName.SetObjectAlias("Child") == "Davolio");
    employeeFilter.Relations.Add(EmployeeEntity.Relations.EmployeeEntityUsingEmployeeIdReportsTo, "Child", "Parent", JoinHint.Left);
    employeeFilter.Relations.Add(EmployeeEntity.Relations.EmployeeDetailsEntityUsingEmployeeIdEmployeeId, "Parent", "ParentDetails", JoinHint.Inneer);

    // fetch results
    var results = new DataTable();
    using (var adapter = new DataAccessAdapter())
    {
        adapter.FetchTypedList(fields, results, employeeFilter); 
    }

    // test: Davolis's manager should be Fuller.
    Assert.AreEqual(1, results.Rows.Count);
    Assert.AreEqual("Fuller", results.Rows[0][2].ToString());
}

Basically you have to pay attention to the aliases, define them in the relation objects and reference them in your fields definition and filters.

David Elizondo | LLBLGen Support Team
Stevenn
User
Posts: 30
Joined: 26-Sep-2007
# Posted on: 21-Jul-2015 09:36:45   

Walaa wrote:

I wonder why the Surname uses a table alias in the select list. Please provide the typedList fetch code. Also is there multiple instances of PersonDetails entity in the TypedList?

Ideally you you'd be good without the LPA_P1 in the select list, and with only one inner join (duplication is not needed, and without the IN predicate, only a filter by the Surname would be enough.

Hi Walaa I had been fiddling with the typed list and had added and deleted the PersonDetails table multiple times. I have also been fiddling with the relationship types. I must have deleted the initial PersonDetails leading to the aliasing?

Anyway, here is my typed list fetch code, if this is what you are looking for:


TestTypedListTypedList test = new TestTypedListTypedList();

            using (DataAccessAdapter adapter = new DataAccessAdapter(TheActiveConnection.ActiveConnection.GetActiveConnection(ConnectionType.ServerConnection)))
            {
                IRelationPredicateBucket bucket = test.GetRelationInfo();

                IPredicateExpression filter = new PredicateExpression();
                if (!string.IsNullOrEmpty(textBox5.Text))
                {
                    //filter.Add(PersonDetailFields.Surname == textBox5.Text);
                    //filter.Add(PersonDetailFields.FkPersonId == PersonFields.ParentPersonId);
                    bucket.PredicateExpression.Add(new FieldCompareSetPredicate(
                        PersonFields.PersonId, null, PersonFields.FkParentPersonId, null,
                        SetOperator.In, (PersonDetailFields.Surname == textBox5.Text)));    
                }

                bucket.PredicateExpression.Add(filter);
                adapter.FetchTypedList(test.GetFieldsInfo(), test, bucket, 0, null, false); 
            }

            foreach (TestTypedListRow row in test.Rows)
            {
                textBox4.Text = row.Name + " " + row.Surname;
            }

This is generating SQL without the second INNER and returning no records. Only adding the second INNER JOIN, as per my first posting is returning the correct record.

daelmo wrote:

The only difference from your scenario is that you have an additional 1:1 table called PersonDetails.

Hi daelmo The problem is that second table though, as the above, and my first SQL query, i need the second INNER to get the correct result. I fiddled with combinations of your code example, and got similar results to my current efforts, either incorrect record, or no records returned, as well as bounding and duplicates in the FROM clause, depending on which (incorrect) relationship combinations i used.

I think for now i will just split my query into two calls. If i find the solution, i'll just post it back here for reference.

Thanks

Steven

Walaa avatar
Walaa
Support Team
Posts: 14950
Joined: 21-Aug-2005
# Posted on: 21-Jul-2015 21:06:03   

Could you please providea screenshot from the Designer showing the TypedList entities and fields?

Stevenn
User
Posts: 30
Joined: 26-Sep-2007
# Posted on: 24-Jul-2015 08:11:04   

Walaa wrote:

Could you please providea screenshot from the Designer showing the TypedList entities and fields?

Sorry for the late response. Attached, please find the screen shots as requested.

Thanks

Steven

Attachments
Filename File size Added on Approval
LLBLGen.7z 346,524 24-Jul-2015 08:12.26 Approved
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 24-Jul-2015 09:19:42   

Stevenn wrote:

I have two tables in a test database: Person(PersonId, Fk_ParentPersonId) and PersonDetails (Fk_PersonId, Name, Surname)

What i am trying to test is, given someone's surname, return the details for their parent. This is the query:

SELECT DISTINCT [TestDB].[dbo].[PersonDetails].[Name], [TestDB].[dbo].[PersonDetails].[Surname], [TestDB].[dbo].[Person].[PersonId], [TestDB].[dbo].[Person].[Fk_ParentPersonId] FROM ( [TestDB].[dbo].[Person]  INNER JOIN [TestDB].[dbo].[PersonDetails]  ON  [TestDB].[dbo].[Person].[PersonId]=[TestDB].[dbo].[PersonDetails].[Fk_PersonId]) WHERE ( [TestDB].[dbo].[Person].[PersonId] IN 
(SELECT [TestDB].[dbo].[Person].[Fk_ParentPersonId] FROM [TestDB].[dbo].[Person] INNER JOIN [TestDB].[dbo].[PersonDetails] ON  [TestDB].[dbo].[Person].[PersonId]=[TestDB].[dbo].[PersonDetails].[Fk_PersonId]  WHERE [TestDB].[dbo].[PersonDetails].[Surname] = 'S2'))

This returns the expected result. To try and generate this, i used:

bucket.PredicateExpression.Add(new FieldCompareSetPredicate(
                        PersonFields.PersonId, null, PersonFields.FkParentPersonId, null,
                        SetOperator.In, (PersonDetailFields.Surname == textBox5.Text))); 

However, the SQL that is generated does not contain the INNER JOIN that is included in the sub-select in the above SQL statement and so no records are returned. The typedlist just contains the two entities linked with an INNER. What am i missing?

Pro 4.2 Final, June 9th, 2015.

Thanks.

The FieldCompareSetPredicate you specified, doesn't contain the relationship between Person and PersonDetails. To get a join in the correlated subquery you have to specify it there.

So to get the join there, you have to do: (I hope I've guessed the name of the relationship correctly!)

bucket.PredicateExpression.Add(new FieldCompareSetPredicate(
                        PersonFields.PersonId, null, PersonFields.FkParentPersonId, null,
                        SetOperator.In, (PersonDetailFields.Surname == textBox5.Text), 
                        new RelationCollection(PersonEntity.Relations.PersonDetailEntityUsingFkPersonId))); 

No need for aliases as there is no correlation between the 2 different references of person and persondetail.

A typed list is just a SELECT ... FROM ..., nothing more. Everything that's to be specified in a WHERE you have to specify yourself, including joins.

Btw, fetching typedlists is easier with queryspec, as it's more natural than the low-level predicates at points.

var qf = new QueryFactory();
var tl = new TestTypedList();
var q = tl.GetQuerySpecQuery(qf)
                .Where(PersonFields.PersonId.In(
                            qf.Create()
                                .Select(PersonFields.FkParentPersonId)
                                .From(qf.Person.InnerJoin(qf.PersonDetail).On(PersonFields.PersonId==PersonDetailFields.FkPersonId))
                                .Where(PersonDetailFields.Surname == textBox5.Text)));
// and then fetch q using 
new DataAccessAdapter().FetchAsDataTable(q, tl);

Hope this helps.

Frans Bouma | Lead developer LLBLGen Pro
Stevenn
User
Posts: 30
Joined: 26-Sep-2007
# Posted on: 24-Jul-2015 11:26:00   

Otis wrote:

bucket.PredicateExpression.Add(new FieldCompareSetPredicate(
                        PersonFields.PersonId, null, PersonFields.FkParentPersonId, null,
                        SetOperator.In, (PersonDetailFields.Surname == textBox5.Text), 
                        new RelationCollection(PersonEntity.Relations.PersonDetailEntityUsingFkPersonId))); 
var qf = new QueryFactory();
var tl = new TestTypedList();
var q = tl.GetQuerySpecQuery(qf)
                .Where(PersonFields.PersonId.In(
                            qf.Create()
                                .Select(PersonFields.FkParentPersonId)
                                .From(qf.Person.InnerJoin(qf.PersonDetail).On(PersonFields.PersonId==PersonDetailFields.FkPersonId))
                                .Where(PersonDetailFields.Surname == textBox5.Text)));
// and then fetch q using 
new DataAccessAdapter().FetchAsDataTable(q, tl);

Hi Otis

Spot on. Your first code sample sorted the problem. I assumed that

IRelationPredicateBucket bucket = test.GetRelationInfo();

would have retrieved all the relation data.

As for the QuerySpec stuff, i guess there is too much to this product that i don't realise is even there. When i am coding, i just go with what i know and just code to meet deadlines. I suppose i should sit down with the manual some time and just read beginning to end. Is there a best practices section that outlines stuff like this? Maybe i should start with that and maybe look at refactoring some of the code i have written over the years.

Anyway, as always, excellent support from all of you again, thanks for the patience.

Cheers

Steven

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 25-Jul-2015 09:36:52   

smile glad it's solved!

As for best practices... if you know how to get from A to B, use that. E.g. the querying systems, we built them on top of the low-level api you're used to, so if anything they're convenience over what you use now, not essential things you have to learn to be able to get things done, with a couple of exceptions like async querying and resultset caching, which are implemented at the Linq and QuerySpec level.

Just learn new things when you wonder 'can they do this too?' and if you then find the answer in the manual, apply it. The low-level API won't go away, we just nowadays recommend to use linq or queryspec instead for most things as they feel more 'modern', but it's not required.

Frans Bouma | Lead developer LLBLGen Pro