I have a question about joins in NHIBERNATE. We had an issue with our sql query that was generated but nhibernate. Our db developer optimized the raw sql so it works as we need, but we need to change the nhibernate code to make generated sql look like optimized.
the part of the original part of the query is:
FROM PERSON_VISIT this_
inner join PERSON_Basic per2_
on this_.PERSON_ID = per2_.PERSON_ID
left outer join PERSONC_QUESTIONS perint10_
on per2_.PERSON_ID = perint10_.PERSON_ID
left outer join TELEPHONE_QUESTIONS intaudit13_
on perint10_.PP_QUESTIONS_ID = intaudit13_.PP_QUESTIONS_ID
inner join C_QUESTIONS intdef14_
on perint10_.QUESTION_ID = intdef14_.QUESTION_ID
and perint10_.QUESTIONS_CODE = intdef14_.QUESTIONS_CODE
and perint10_.QUESTION_ID = intdef14_.QUESTION_ID
The optimized one is :
FROM PERSON_VISIT this_
inner join PERSON_Basic per2_
on this_.PERSON_ID = per2_.PERSON_ID
left outer join PERSONC_QUESTIONS perint10_
on per2_.PERSON_ID = perint10_.PERSON_ID
left outer join TELEPHONE_QUESTIONS intaudit13_
on perint10_.PP_QUESTIONS_ID = intaudit13_.PP_QUESTIONS_ID
left outer join C_QUESTIONS intdef14_
on perint10_.QUESTION_ID = intdef14_.QUESTION_ID
and perint10_.QUESTIONS_CODE = intdef14_.QUESTIONS_CODE
and perint10_.QUESTION_ID = intdef14_.QUESTION_ID
and intdef14_.DISCIPLINE_CODE = this_.DISCIPLINE_CODE
To change query from inner join to left outer join is easy, i changed only one line of code:
.CreateAlias("PersonInt.QuestionEntity", "IntDef", JoinType.LeftOuterJoin)
But how I can add
and intdef14_.DISCIPLINE_CODE = this_.DISCIPLINE_CODE
using nhibernate code?
There is an option to add reference from PERSON_VISIT definition to C_QUESTIONS, but the problem is that
PERSON_VISIT is used everywhere and I don't want this change to possibly break other queries, I just wnat to add only one line of code to add, how I can do that? Is there any way to have access to the raw join to change it? Or some other way to add this
and intdef14_.DISCIPLINE_CODE = this_.DISCIPLINE_CODE
To the query?
I know that somebody will say that we can add a restriction to the query through criteria.Add, but it is not an option cause db developer optimized our query taking this restriction from WHERE clause to the join.
How I can do that quickly without changing the models definitions? Just changing only this one query without changing the whole model?
It is possible using HQL and the Criteria API's.
This question gives you the answer: Adding conditionals to outer joins with nhibernate
Something like this may solve your issue.
.CreateAlias("PersonInt.QuestionEntity", "IntDef", JoinType.LeftOuterJoin,
Restrictions.EqProperty("DISCIPLINE_CODE", "IntDef.DISCIPLINE_CODE"))
Thanks for answers. We use 2.0 version of NHibernate in our project so we didn't have a chance to use new methods of .CreateAlias with restrictions.
I have fixed an issue using Interceptors:
public class SqlInterceptor : EmptyInterceptor, IInterceptor
{
SqlString IInterceptor.OnPrepareStatement(SqlString sql)
{
//manipulating with the sql
return sql;
}
}
than
var factory = Session.SessionFactory;
var session = factory.OpenSession(new SqlInterceptor());
And use my query without a change.
Related
I need to add a condition that only selects the rows where a field (my_galleries.format) equals a string value of 'pictures'. I am trying to add it to this working sql statement.
SELECT
gallery_url,
preview_url
FROM
my_galleries,
my_gallery_previews
WHERE
my_galleries.gallery_id = my_gallery_previews.gallery_id
I tried this, with no luck....
SELECT
gallery_url,
preview_url
FROM
my_galleries,
my_gallery_previews
WHERE
my_galleries.gallery_id = my_gallery_previews.gallery_id
AND my_galleries.format='pictures'
Any ideas?
I recommend not using the FROM foo, bar WHERE foo.key = bar.key approach to performing a JOIN as it isn't very flexible and isn't obvious to new readers of your code. Instead you should perform an explicit JOIN instead:
SELECT
gallery_url,
preview_url
FROM
my_gallery_previews
INNER JOIN my_galleries ON my_gallery_previews.gallery_id = my_galleries.gallery_id
WHERE
my_galleries.format = 'pictures'
How about you try this
SELECT gallery_url, preview_url FROM my_galleries
JOIN my_gallery_previews ON
my_galleries.gallery_id = my_gallery_previews.gallery_id
WHERE my_galleries.format='pictures'
With the join, make sure to prefix your selected columns with their respective tables. I hope this helps
As others have stated I recommend you use the new structure of joining as it is more readable.
SELECT mg.gallery_url,
mgp.preview_url
FROM my_galleries mg
JOIN my_gallery_previews mgp ON mg.gallery_id = mgp.gallery_id
AND mg.format = 'pictures'
You can place the filter condition in the join using an AND statement. (Just another way to do it)
Your query appears to be correct syntax. I would recommend going through your data set or editing it into your question. It is possible that no picture gallery previews currently share a gallery id with an existing gallery row which is stopping the join from functioning.
I am working on a open source ORM, and have ended up with the following generated Statement
SELECT "Task"."id"
,"Task"."title"
,"Task"."projectId"
,"Project"."id" AS "Project.id"
,"Project"."title" AS "Project.title"
,"Project"."userId" AS "Project.UserId"
,"Project.User"."id" AS "Project.User.id"
,"Project.User"."username" AS "Project.User.username"
FROM "Task" AS "Task"
LEFT OUTER JOIN ("Project" AS "Project"
INNER JOIN "User" AS "Project.User" ON "Project"."userId" = "Project.User"."id"
AND "Project.User"."username" = 'test01' ) ON "Task"."projectId" = "project"."id";
which produces the following error
no such column: Project.User.id
Thanks to help from a previous question here I was able to solve it by removing the periods from the alias inside the parentheses
SELECT "Task"."id"
,"Task"."title"
,"Task"."projectId"
,"Project"."id" AS "Project.id"
,"Project"."title" AS "Project.title"
,"Project"."userId" AS "Project.UserId"
,"Project_User"."id" AS "Project.User.id"
,"Project_User"."username" AS "Project.User.username"
FROM "Task" AS "Task"
LEFT OUTER JOIN ("Project" AS "Project"
INNER JOIN "User" AS "Project_User" ON "Project"."userId" = "Project_User"."id"
AND "Project_User"."username" = 'test01' ) ON "Task"."projectId" = "project"."id";
However, since I am working on an ORM the format of the the SQL statement is important and having the periods in those inner aliases would help a lot. I have discovered the following SQL works.
SELECT "Task"."id"
,"Task"."title"
,"Task"."projectId"
,"Project"."id" AS "Project.id"
,"Project"."title" AS "Project.title"
,"Project"."userId" AS "Project.UserId"
,"Project.User"."id" AS "Project.User.id"
,"Project.User"."username" AS "Project.User.username"
FROM "Task" AS "Task"
LEFT OUTER JOIN ("Project" AS "Project"
INNER JOIN "User" AS "Project.User" ON "Project"."userId" = "Project.User"."id"
AND "Project.User"."username" = 'test01' ) "Project.User" ON "Task"."projectId" = "project"."id";
But I do not understand why, or how to scale this to a solution that has many nested joins of a similar nature.
All columns and tables exist with the names given, and no other problems are getting in the way (I assume this mainly based on the failure of the first example and the success of the second.) The initial error only happens in sqlite, all over dialects I have tried seem to not have an issue.
My question is, a, why does the third example work and, b, would it scale to even deeper joins with aliases with periods?
I am developing a web app that interfaces with an ERP production database and the data is being retrieved by a complex SQL query with many joins and aliases before being passed to my controllers and views which modify them in different ways.
The result might be, for example, an array of products that contains all relevant information for each purchase such as model, serial number, customer, etc., and in a smaller scale app these would certainly all be stored in the same table.
However because I am pulling from a very complex ERP solution these details are taken from many different tables, but I'd like to treat it as a single table in my model.
Is there any way to accomplish this so that I can use Eloquent as usual? Things like Product::all() do not work, though I can call custom methods like Product::getWhere($serial) but I'd rather do this the Eloquent way since it is easier and makes pagination much nicer to work with.
Here is an idea of what my queries looks like:
SELECT
[PSerials].[ElementID] AS [ProductElementID],
[PSerials].[SerialNumber] AS [SerialNumber],
[PSerials].[SNStatus] as [ProductStatus],
[PSerials].[JobID] AS [JobID],
[Element].[Desc] AS [Description],
[PSerials_UD].[Prog] AS [ProgramNum],
[PSerials_UD].[Elec] AS [ElecPrint],
[PSerials_UD].[SinglePHVolt_c] AS [SinglePHVolt],
[PSerials_UD].[SinglePHAmp_c] AS [SinglePHAmp],
[PSerials_UD].[ThreePHVolt_c] AS [ThreePHVolt],
[PSerials_UD].[ThreePHAmp_c] AS [ThreePHAmp],
[PSerials_UD].[Notes_c] AS [Notes],
[PSerials_UD].[WarrantyExpDate_c] AS [WarrantyExpDate],
[QuoteDtl].[QuoteNum] AS [QuoteNum],
[QuoteDtl].[QuoteComment] AS [QuoteComment],
[OrderDtl].[RequestDate] AS [ShipDate],
[Customer].[CustID] AS [CustomerID],
[Customer].[Name] AS [CustomerName],
[Customer1].[CustID] AS [ShipToCustomerID],
[Customer1].[Name] AS [ShipToCustomerName]
FROM Erp.PSerials AS PSerials
INNER JOIN Erp.Element AS Element ON
PSerials.Company = Element.Company AND PSerials.ElementID = Element.ElementID
AND (Element.ProdCode = 'MACH' AND Element.ClassID = 'MAC')
INNER JOIN Erp.PSerials_UD AS PSerials_UD ON
PSerials_UD.ForeignSysRowID = PSerials.SysRowID
LEFT OUTER JOIN Erp.PProd AS PProd ON PProd.JobID = PSerials.JobID
LEFT OUTER JOIN Erp.OrderDtl AS OrderDtl ON PProd.Company = OrderDtl.Company
AND PProd.OrderNum = OrderDtl.OrderNum AND PProd.OrderLine = OrderDtl.OrderLine
LEFT OUTER JOIN Erp.QuoteDtl AS QuoteDtl ON OrderDtl.QuoteNum = QuoteDtl.QuoteNum
AND OrderDtl.QuoteLine = QuoteDtl.QuoteLine
LEFT OUTER JOIN Erp.Customer AS Customer ON OrderDtl.Company = Customer.Company
AND OrderDtl.CustNum = Customer.CustNum
LEFT OUTER JOIN Erp.Customer AS Customer1 ON PSerials.Company = Customer1.Company
AND PSerials.ShipToCustNum = Customer1.CustNum
I have it sitting in a static variable on my Product model and then have custom functions that simple tack on SQL queries to the end as needed, but this isn't elegant and it isn't fun to work with, here's an example of how I use these queries:
public static function getWhere($serial)
{
$query = DB::select(DB::raw(Self::$baseQuery));
$collection = new \Illuminate\Support\Collection($query);
$product = $collection->where("SerialNumber", $serial)->first();
return $product;
}
Does anyone how I can have my model simply treat queries like that as if they were a simple table, or is there perhaps another way to go about doing this?
Update
Thanks to Zamrony P. Juhara's advice below I was able to successfully create an SQL view and tell my Products model to reference is like a table:
class Product extends Model
{
protected $table = 'dbo.ProductInfo';
}
Now Product::all() works as expected.
Regarding the limitations pointed out by Peh this app is purely for viewing the data that exists in the ERP software and won't need to update or delete any records, so in this case the SQL view is absolutely perfect.
You can turn SQL command into VIEW (CREATE VIEW) then use it just like ordinary table in your model.
I am using Rails 3 with ActiveRecord and I cannot get it to generate the query I want without inserting almost plain SQL in it.
I simply want to execute this SQL query (actually the query is a little more complex but it's really this part that I cannot get right).
SELECT DISTINCT users.*, possible_dates.*
FROM users
LEFT OUTER JOIN possible_dates
ON possible_dates.user_id = users.id
AND possible_dates.event_id = MY_EVENT_ID;
which I managed to using
User.includes(:possible_dates)
.joins("LEFT OUTER JOIN possible_dates
ON possible_dates.user_id = users.id
AND possible_dates.event_id = #{ActiveRecord::Base.sanitize(self.id)}"
)
.uniq
but I feel that I am missing something to simply add the AND condition of the left join using ActiveRecord query methods.
I also tried to do something like this
User.includes(:possible_dates)
.where('possible_dates.event_id' => self.id)
.uniq
but this yields (as expected), a query with a WHERE clause at the end and not the AND clause I want on the join.
By the way, self in the two snippets above is an instance of my Event class.
Thank you for your help.
(1 year later...)
After looking around, I found out that the best was probably to use arel tables.
For the above example, the following code would work.
ut = User.arel_table
pt = PossibleDate.arel_table
et = Event.arel_table
User.joins(
ut.join(pt, Arel::Nodes::OuterJoin)
.on(pt[:user_id].eq(u[:id])
.and(pt[:event_id].eq(1))
).join_sql
).includes(:possible_dates).uniq
the 1 in eq(1) should be replaced by the correct event id.
I have an issue with Linq to Nhibernate producing queries with outer joins.
For example:
return Session.Linq<ClientContact>().Where(c => c.Client.Id = 13).ToList();
Produces a query similar to:
SELECT...
FROM mw_crafru.clientcontact this_,
mw_crafru.client client1_,
mw_crafru.relationshiptype relationsh4_
WHERE this_.clientid = client1_.clientid(+)
AND this_.relationshiptypeid = relationsh4_.relationshiptypeid
AND client1_.clientid = :p0;:p0 = 13
Notice the outer join this.clientid = client1.clientid(+). Boo!
To resolve the issue I went back to using Session.CreateCriteria.
return Session.CreateCriteria(typeof (ClientContact)).Add(Restrictions.Eq("Client.Id", clientId)).List<ClientContact>();
Produces the following query:
SELECT...
FROM mw_crafru.clientcontact this_,
mw_crafru.client client1_,
mw_crafru.relationshiptype relationsh4_
WHERE this_.clientid = client1_.clientid
AND this_.relationshiptypeid = relationsh4_.relationshiptypeid
AND client1_.clientid = :p0;:p0 = 13
Notice no outer join this.clientid = client1.clientid. Yay!
Anyone know why this is happening?
With SQL Server at least, a many-to-one mapping with not-null="true", or Not.Nullable() using Fluent NH, causes Get operations to use an inner join instead of a left join. Try adding that to your mapping for Client in ClientContact.