I need to set a boolean value on my mapped class, based on a parameter set at run time.
In normal SQL I would achieve it as below:
select * from resource r,
case when k.resource_key is null then 0 else 1 end as owned,
left outer join resource k
on k.resource_key = r.resource_key and k.member_key = 719469993
where r.member_key = 167367873
What I am trying to achieve here is joining the resource table with itself to find out whether 2 given users both own the same resource.
We are using ICriteria in this case.
Use <formula> in your mapping.
I assumes that you have a mapping of an object to resource table, let's call it Resource, and the member_key and resource_key are mapped respectfully to ResourceKey and MemberKey properties in Resource.
<class name="Resource" table="resource" >
<property name="MemberKey">
<column name="member_key "/>
</property>
<property name="ResourceKey ">
<column name="resource_key "/>
</property>
<property name="Owned">
<formula>(select case when exists(select * from resource R where R.resource_key = ResourceKey and R.member_key = MemberKey) then 0 else 1 end)</formula>
</property>
</class>
Related
Overview:
I have these beans in my hbm files. First is a report with two dates (start and stop of report) and a list of excluded periods. Second is keeping excluded periods for report. Report is turned off during these excluded periods.
<class name="com.company.Report" table="TReport">
<property name="startDate" column="m_StartDate" type="timestamp"/>
<property name="endDate" column="m_EndDate" type="timestamp"/>
<list name="excludedPeriods" table="TExcludedPeriod" inverse="true" lazy="false">
<key column="m_TReport" not-null="true"/>
<list-index column="m_ListIndex" base="1"/>
<one-to-many class="com.company.ExcludedPeriod"/>
</list>
</class>
<class name="com.company.ExcludedPeriod" table="TExcludedPeriod">
<id column="m_Id" name="id" />
<many-to-one name="report" class="com.company.JobsReport" column="m_TJobReport" not-null="true" />
<property name="index" column="m_ListIndex" type="integer"/>
<property name="start" column="m_Start" type="timestamp"/>
<property name="stop" column="m_Stop" type="timestamp"/>
</class>
Task:
Select all reports that are turned on during a selected period of time (defined by startPeriod and endPeriod).
My code:
SELECT * FROM treport r LEFT OUTER JOIN texcludedperiod ep ON r.m_id=ep.m_treport
WHERE NOT (ep.m_id IS NOT NULL AND (ep.m_start>=startPeriod AND ep.m_end<=endPeriod));
Problem:
It is working if there is no excluded period for a report or there is only one such a period, but in other cases it selects reports that shouldn't be selected. Consider this example (d/m/yyyy format):
Report: start 1/1/2000, stop 1/2/2000, excluded periods:
7/1/2000-10/1/2000, 12/1/2000-15/1/2000
Searched period: startPeriod 8/1/2000, stopPeriod 9/1/2000.
This report shouldn't be selected.
This is one of those cases where you should use a NOT EXISTS:
SELECT *
FROM treport r
WHERE NOT EXISTS (SELECT *
FROM texcludedperiod ep
WHERE ep.m_treport = r.m_id AND
ep.m_start >= startPeriod AND
ep.m_end <= endPeriod);
Basically you're saying "Give me all the reports where there are no exclusions during the reporting period in question".
I have three Tables:
- Person
- User
- PersonSecret
where PersonSecret reference to Person and User:
<class name="PersonSecret" table="PersonSecret" lazy="false" >
<id name="Id" column="Id" type="Guid">
<generator class="assigned"/>
</id>
...
<many-to-one name="Person" class="Person" foreign-key="FK_Person_PersonSecret" lazy="proxy" fetch="select">
<column name="PersonId"/>
</many-to-one>
<many-to-one name="User" class="User" foreign-key="FK_User_PersonSecret" lazy="proxy" fetch="select">
<column name="UserId"/>
</many-to-one>
This is the mapping from User to PersonSecret:
<set name="PersonSecrets" lazy="true" inverse="true" cascade="save-update" >
<key>
<column name="UserId"/>
</key>
<one-to-many class="PersonSecret"/>
And this from Person to PersonSecret:
<set name="PersonSecrets" lazy="true" inverse="true" cascade="save-update" >
<key>
<column name="PersonId"/>
</key>
<one-to-many class="PersonSecret"/>
Now, i try to select all Persons, which has a Entry in PersonSecret for a specific User:
var query = this.Session.CreateQuery(#"from Person a inner join PersonSecret b
where b.UserId = :userId and b.RemindeBirthday = :remind");
This gives me now the ExceptionMessage: "Path expected for join"
Can someone help me, what I am doing wrong? - Thanks.
There are a couple of issues with your HQL query:
When using HQL you need to reference the names of the properties on your model classes instead of the column names they map to. In this case you should reference the PersonSecret.User.Id property in your where clause, instead of the UserId column.
You should also specify the Person.PersonSecrets property in the join clause, otherwise NHibernate won't know which columns to join on.
You should specify your additional filter as a join condition using the with keyword instead of in the where clause.
Here's the correct version:
var query = this.Session.CreateQuery(
#"from Person as a inner join a.PersonSecrets as b with b.User.Id = :userId and b.RemindeBirthday = :remind");
Related resources:
HQL: The Hibernate Query Language - Associations and joins
you have forgotten the "ON" term.
inner join PersonSecret b ON b.PersonId = a.PersonId
I have following class
<class name="Product" table="Product">
<id name="ID" />
...
<map name="CustomFields" table="CustomFieldView">
<key column="RECORDID" />
<map-key column="CFName" type="String" />
<element column="CFValue" type="String" />
</map>
</class>
and SP to select product with CustomFields dictionary
<sql-query name="GetProducts">
<return alias="p" class="Product" />
<return-join alias="cf" property="p.CustomFields" />
SELECT {p.*}, {cf.*}
FROM Product p
INNER JOIN CustomFieldView cf ON p.ID = cf.RECORDID
// WHERE
</sql-query>
when I select single product like WHERE ID = 1234, then it works as expected - returns one Product with populated CustomFields Dictionary property.
But when I select not single Product like WHERE ID IN (18780, 21642) or other criterias then I get Products duplicated 'CustomFields.Count' times, e.g. 2 Products and each has 20 Custom Fields, then 40 Products and each has 20 valid custom fields.
Do I missed something in mapping ?
You are returning a Cartesian product and therefore your product is being returned x times for every custom field.
To get around this problem you will need to use something like:-
var query = Session
.GetNamedQuery("GetProducts")
.SetResultTransformer(new DistinctRootEntityResultTransformer());
return query.List<Product>();
Please note that you will send all the data down the wire and NHibernate will perform the distinct transformer client (meaning web server or desktop app) side.
I am not 100% sure if the return join will be populated as I have never done things this way, you will need to test this.
edit
I think you fetching strategy is not quite right. Do you really need a <sql-query...> Could you use another strategy e.g. HQL?
I have two classes SystemInvitation and User. User has a property called Email and SystemInvitation has a property called InviteesEmailAddress. There is no relationship in the domain between these properties.
Is it possible using the Criteria API to produce a query like:
select
si.InviteesEmailAddress
, si.Identifier
, case when u.id is null then 0 else 1 end as UserExists
from
SystemInvitation si
left outer join [User] u on u.Email = si.InviteesEmailAddress
?
Thanks!
You should map the InviteesEmailAddress column in the mapping for SystemInvitation using something like this:
<many-to-one name="InviteesEmailAddress" fetch="join" class="User"
column="Email" cascade="none" not-found="ignore" />
I'd like to map the following sql in NHibernate.
Will I need to make a separate entity object i.e RoomTypeVO mapped to tb_tags to do this?
Any help much appreciated.
SELECT
dbo.tb_rooms.id,
dbo.tb_rooms.name,
dbo.tb_tags.name AS 'roomType'
FROM
dbo.tb_rooms
INNER JOIN dbo.tb_tags ON (dbo.tb_rooms.typeID = dbo.tb_tags.id)
<id name="id" column="id">
<generator class="native" />
</id>
<property name="name" />
If you to a straight sql query you do not have to. If you want to use HQL you will have to work with an entity.
But, you can always do sql queries directly.
If you have a mapped entity then you could probably just do something like this:
FROM RoomType
When you refer to 'FROM', are you thinking of something like this?
<property name="totalPrice"
formula="( SELECT SUM (li.quantity*p.price) FROM LineItem li, Product p
WHERE li.productId = p.productId
AND li.customerId = customerId
AND li.orderNumber = orderNumber )"/>