Re: Joins -- Philosophy

Franck Routier <[email protected]> Mon, 23 Mar 2009 18:36:34 +0100
Newsgroups gmane.comp.java.orm.simpleorm
Message-ID <1237829794.3984.399.camel@franck-laptop>
Hi again, thanks for your comments

...
>  And as discussed it still needs more work on the Select list at
> least.

I think this problem is handled by the rsindex part. See my other mail.

>  And more test cases.

Yep.

> 
> It hurt when you deleted my foreign key generation code long ago and
> thus weakened SimpleORM. But you made it much simpler, and I think
> that that was a good trade off. I am wondering whether the full join
> code might be another case of the same sort of thing. (Likewise it
> hurt when I needed to delete the first cut of the DataSet split code.)

I know that. I am also still missing your first try at DataSets, which
were complex but elegant...

> 
> ALTERNATIVES
> ===========
> 
> What I would suggest is:-
> 
> 1. Implement one level joins as before your changes, no recursive
> joins, but add outer joins. This does not require any clever alias
> processing.

Yep. In fact this works with the code as is.

> 
> 2. Implement a simple calculus approach using subqueries as described
> below. Much easier to understand than full joins and again no complex
> table aliases.

Why not, but this only handles the case when we want to filter. This
does not allow instantiating joined records...
> 
> 3. Always provide an simple single alias for the top level table,
> default being the first letter of the table name. This enables
> recursive subqueries. (Need to then use the alias consistently
> everywhere for Oracle.)

That what I did. You don't have to provide an alias. If yu don't the
code defaults to the table name. This was the most simple way I found to
get unique alias for simple case (my other idea was to use a counter an
generate T1, T2, T3 etc (like Cognos Impromptu does, but this makes the
sql quite unreadable).

> 
> 4. Provide rawJoin and rawClause for special cases.

Does exist.

> 
> I believe that 1,2 and 3 will cover 95% of all cases naturally and
> efficiently. We can then resort to option 4 for the last 5% without
> complicating SimpleOrm.
> 
> There are then other directions that the query language could be
> extended later. In particular to return derived values.

What do you mean by derived values ?

>  And non-trivial expressions rather than our current simple flat
> structure.

Yes, that would be much welcome.

> 
> CALCULUS APPROACH
> ================
> 
> To keep it simple I would prefer to focus on the calculus approach.
> Ie. have an operator
> 
> SQuery(Employee).eq(Employee.MANAGER, Employee.Name, "Fred")
> 
> OR
> 
> ...rawCorrellate(Employee.MANAGER, Employee.Name, "= ?", "Fred")
> ...rawCorrellate(Employee.MANAGER, "NAME = ?", "Fred") // be sure to
> generate ()s.
> 
> These can generate the sub-select in a very intuitive way. There is no
> need for aliases on the inner tables as the SQL queries are scoped. No
> issues with outer joins and missing rows. 
> 
> (The generated query is
> SELECT * FROM EMPLOYEE E 
> WHERE EXISTS (SELECT * FROM EMPLOYEE
> WHERE E.EMP_ID = EMP_ID AND (NAME = ?))

I don't find this more intuitive than
select e.* from employee e
join manager m on e.manager_id = m.id
where m.name = 'Fred'

But that's probably a matter of taste. What I like with join synths is
that you clearly seperate clauses based on the database structure (on)
and clause that are related to your ponctual business concern (where).

> 
> N+1 queries can always be converted to 1+1 using subqueries. But I
> suspect that single level outer joins will handle virtually all the
> normal cases as single queries anyway.
> 
> RANT: ALGEBRA VS CALCULUS
> ======================
> 
> Your problems below about the result basically document why I think
> that joins are generally a bad idea period. 

I have always liked you tact :)))

> 
> The "Alegebra" approach with joins Join is about a Cartesian product,
> very counter intuitive. The result is a new Relation that is a *copy*
> of data out of the original tables with "outer" rows then poked back
> in. This then makes it difficult to relate the result back to one of
> the original tables. Ie. 
> Result =copy table1 joinop table2 joinop table2. 
> The algebra is absolutely not about relating the result back to one of
> the original tables for update. "Result" is a new relation, a first
> class object, to be used in further expressions. Nothing ever gets
> updated in the relational algebra -- it is functional. Ie. exactly how
> we do NOT use a database.

You've got a point here. But by insanciating the different parts of the
query as record instances and working on the dataset, we do overcome
this limitation of sql...

> 
> On the other hand the "Calculus" approach is about filtering the
> primary table by reference to secondary tables. Ie. the set 
> {r:T | P(r)} // set r of type T where P(r) is true.
> P(r) can reference other tables. This means corollated subqueries in
> SQL. No new "Result" relations are created, nothing needs to be
> matched.
> 
> It is a *major* problem with SQL syntax that the more natural calculus
> approach is ugly, but the weird join approach is easy. So instead of
> writing 
> "employee.Department.name = 'Count Penies' " 
> we need to write 
> "Exists (select * from Department where department.id =
> employee.departmentId and name = 'Count Penies' )". 
> However we can address this with our simple query language. And then
> all of your list problems go away, including the outer join one.
> 
Agreed. But are we going to change the sql spec soon ? :)

> The only real advantage of Joins is that extra columns can be returned
> by the one query.

This is the major advantage. This is why we need joins.

>  And that is the one thing that we do not really support very well.

Well. I think we do with the code I submitted.

>  One level should be more than enough for most cases.

It would certainly be as long as we allow one-to-many joins (PaySlip /
Employee / PaySlipDetail case). But then, one level or multi-level does
not make a great difference in complexity.
> 
> As to your return type problem, I suspect you should just return
> duplicates, that is what the weird semantics are (I have not thought
> this through properly).

I changed the code to do that. The use can then use a LinkedHashSet to
remove duplicates if he wants.
> 
> CONCLUSION
> ==========
> 
> I have strong reservation as to the complexity of the join code.

I don't find it so complex. The major drawback is adding methods with
aliases, which make the API a bit bloated.

> 
> I would much prefer to add subqueries before full join processing.
> They are much easier to implement and are more object oriented.

I really think my implementation works. The part that is not object
oriented is when we turn resultSet into records (reading a table)... but
this is what an ORM is for !

> 
> I believe that once implemented the need for full joins will largely
> disappear. So we can then keep it simple.

Well, I don't see what is complex with :

select * from department d
left join employee e on e.dept_id = d.dept_id
where e.salary > 10000

written in Simpleorm as 
new
SQuery<Department>(Department.meta).leftJoin(Employee.DEPARTMENT).gt(Employee.SALARY, 10000);

that would be easier as :

select * from department d
where exists(select * from employee
where d.dept_id = dept_id and salary > 10000)

written as
SQuery<Department>(Department.meta).rawCorrelate(Employee.DEPARTMENT,
Employee.Salary, "> ?", 10000));  // How to express outer join ?

+

select * from employee
where salary > 10000
and dept_id is not null

written as 
SQuery<Employee>(Employee.meta).gt(Employee.Salary,
10000).notNull(Employee.DEPT_ID);

to get the _same_ result, that is a dataset with my departments, and all
employees that are in a department and have a salary > 10000.


> Please have a look at your actual queries, and see if they can be done
> naturally as subqueries. If so then simplicity wins.

We have a lot of queries where we do things like this :

select d.* , u.quantity, r.account from detail d
join used_resource u on d.used_id = u.id and d.company = u.company
join resource r on r.id = u.res_id and r.company = u.company
where resource.group = 'a group'
and d.company = 'bigone'

translating them into :

select * from detail d
where exists(select * from used_resource u , resource r
             where r.id = u.res_id
             and r.company = u.company
             and r.group = 'a group'
             and u.company = d.company
             and d.used_id = u.id)
and d.company = 'bigone'
+
select * from used_resource u
where exists(select * from detail d
              where exists(select * from resource r
                           where r.id = u.res_id
                           and r.company = u.company
                           and r.group = 'a group'
                           and u.company = d.company
                           and d.used_id = u.id)
        )
and u.company = 'bigone'
+
select * from resource r... well, I'm not sure I'll manage this one :)


or resorting to N+1 queries, won't make things any simplier...

What do you think of it ?

Regards,

Franck





------------------------------------

Yahoo! Groups Links

<*> To visit your group on the web, go to:
    http://groups.yahoo.com/group/SimpleORM/

<*> Your email settings:
    Individual Email | Traditional

<*> To change settings online go to:
    http://groups.yahoo.com/group/SimpleORM/join
    (Yahoo! ID required)

<*> To change settings via email:
    mailto:[email protected] 
    mailto:[email protected]

<*> To unsubscribe from this group, send an email to:
    [email protected]

<*> Your use of Yahoo! Groups is subject to:
    http://docs.yahoo.com/info/terms/