Making a Laravel Model use complex query as table - sql

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.

Related

How to get CJ20N project hierarchy?

I need to get the hierarchy of a project (like shown in transaction CJ20N) in ABAP.
I've found a function module 'GET_PROJECT_HIERARCHY', which delivers me a table like this:
However, I rather need all WBS elements, order numbers, purchase requests and network elements (AUFNR) in this project. Is there a better function module or a next step to the GET_PROJECT_HIERARCHY?
At least what concerns WBS elements you can get them from table PRPS
DATA: lt_prhi TYPE TABLE OF prhi.
CALL FUNCTION 'GET_PROJECT_HIERARCHY'
EXPORTING
i_pronr = '00000113'
TABLES
t_prhi = lt_prhi.
SELECT * FROM prps
INTO TABLE #DATA(lt_prps)
FOR ALL ENTRIES IN lt_prhi
WHERE pspnr = lt_prhi-posnr.
cl_demo_output=>display( lt_prps ).
Thank you Suncatcher, your directed me the right way with the PRPS table and I'll accept your answer.
I finally decided to build a custom query, looking in the tables PRPS, AFVC, AUFK, AFKO and PROJ to get all the data I want using the PSPHI column from GET_PROJECT_HIERARCHY.
I just wanted to post my solution, maybe it helps others too.
SELECT
PROJ~PSPID,
PROJ~POST1 AS PROJ_NAME,
PRPS~POST1 AS WSB_NAME,
AFVC~LTXA1,
AUFK~KTEXT
FROM
PRPS
LEFT JOIN PROJ ON PROJ~PSPNR = PRPS~PSPHI
LEFT JOIN AUFK ON AUFK~PSPEL = PRPS~PSPNR
LEFT JOIN AFKO ON AUFK~AUFNR = AFKO~AUFNR
LEFT JOIN AFVC ON AFKO~AUFPL = AFVC~AUFPL
WHERE
PRPS~PSPHI = '00000136'
INTO TABLE #DATA(LT_RESULT)

Is there any way to use raw sql or i'm stuck with laravel-eloquent?

I'm trying to migrate an application developed in 2007 and add and api to it so I can easy develop and sync data with the new App I started a laravel project with the old database just to create the api the old database diagram look like this: Too many relationships and crazy polymorphic ones at that.
I want to migrate my query to laravel eloquent but I couldn't do it in time I tried to do it with eloquent but it took too much time is there any way to use raw sql like this or i'm stuck with eloquent this is my laravel code : here
the sql query looks like this
SELECT n.DOC_ID, n.TYP_ID, n.TYP_ID,
type_document.TYP_LIBELLE_AR, n.IND_ID,indication.IND_LIBELLE_AR,n.LAN_ID,
langue.LAN_LIBELLE_AR,
n.DOC_TITRE_PROPRE,
n.DOC_TITRE_COMPLEMENT,
n.DOC_TITRE_PARALLELE,
n.DOC_TITRE_ENSEMBLE,
n.DOC_NUMERO_PARTIE,
editeur.EDT_NOM_AR,
editeur.EDT_KEYWORDS,
n.DOC_LIEU_EDITION,
n.DOC_ANNEE,
n.DOC_EDITION,
periodicite.PER_LIBELLE_AR,
n.DIP_ID,
specialite.SPE_LIBELLE_AR,
n.COL_ID,
n.DOC_NUM,
n.COL_NUMERO,
n.COT_NOTICE,
n.SCL_ID,
n.SCL_NUMERO,
n.DOC_NBR_UNITE,
n.DOC_ILLUSTRATION,
n.DOC_FORMAT,
n.DOC_MATERIEL,
n.DOC_ISBN,
n.DOC_ISSN,
n.DOC_NBR_EXEMPLAIRE,
n.STA_ID,
n.REL_VOLUME,
n.PAY_ID,
n.DOC_AGENCE,
n.DOC_PRET_INTERNE,
n.DOC_PRET_EXTERNE,
n.DOC_KEYWORDS,
n.CREATE_DATE,
n.UPDATE_DATE,
statut_notice.STA_LIBELLE_AR,
notice_auteur.VED_ID,
notice_auteur.FON_ID,
notice_auteur.AUT_TYPE,
GROUP_CONCAT(DISTINCT vmath.VED_NOM) AS mats,
vmath.VED_KEYWORDS,
GROUP_CONCAT( DISTINCT vauth.VED_NOM) AS auths,
vauth.VED_KEYWORDS,
GROUP_CONCAT( DISTINCT notice_exemplaire.EXP_COTE) AS examp_cote,
GROUP_CONCAT( DISTINCT notice_exemplaire.LOC_ID) AS exmp_location
FROM notice n
INNER JOIN editeur ON editeur.EDT_ID =n.EDT_ID
INNER JOIN type_document ON type_document.TYP_ID = n.TYP_ID
INNER JOIN langue ON langue.LAN_ID = n.LAN_ID
INNER JOIN statut_notice ON statut_notice.STA_ID = n.STA_ID
INNER JOIN indication ON indication.IND_ID = n.IND_ID
INNER JOIN notice_exemplaire ON n.DOC_ID = notice_exemplaire.DOC_ID
LEFT JOIN specialite ON n.SPE_ID = specialite.SPE_ID
LEFT JOIN periodicite ON periodicite.PER_ID = n.PER_ID
INNER JOIN notice_auteur ON n.DOC_ID = notice_auteur.DOC_ID
INNER JOIN vedette vauth ON notice_auteur.VED_ID = vauth.VED_ID
LEFT JOIN notice_matiere ON n.DOC_ID = notice_matiere.DOC_ID
LEFT JOIN vedette vmath ON notice_matiere.VED_ID = vmath.VED_ID
GROUP BY n.DOC_ID
1:
For something like this you can use DB::select('YOUR-QUERY').
DB::select('...') calls select() on the underlying Connection class so it's not the same as using DB::table('...')->select('...') which is calling the select method on the Builder class.
For more information on running raw queries you can refer to the documentation.

slow entity framework query , but fast Generated SQL

please consider this model
it's for a fitness center management app
ADHERANT is the members table
INSCRIPTION is the subscription table
SEANCE is the individual sessions table
the seance table contain very fews rows (around 7000)
now the query :
var q = from n in ctx.SEANCES
select new SeanceJournalType()
{
ID_ADHERANT = n.INSCRIPTION.INS_ID_ADHERANT,
ADH_NOM = n.INSCRIPTION.ADHERANT.ADH_NOM,
ADH_PRENOM = n.INSCRIPTION.ADHERANT.ADH_PRENOM,
ADH_PHOTO = n.INSCRIPTION.ADHERANT.ADH_PHOTO,
SEA_DEBUT = n.SEA_DEBUT
};
var h = q.ToList();
this take around 3 seconds wich is an eternity,
the same generated SQL query is almost instantaneous
SELECT
1 AS "C1",
"C"."INS_ID_ADHERANT" AS "INS_ID_ADHERANT",
"E"."ADH_NOM" AS "ADH_NOM",
"E"."ADH_PRENOM" AS "ADH_PRENOM",
"E"."ADH_PHOTO" AS "ADH_PHOTO",
"B"."SEA_DEBUT" AS "SEA_DEBUT"
FROM "TMP_SEANCES" AS "B"
LEFT OUTER JOIN "INSCRIPTIONS" AS "C" ON "B"."INS_ID_INSCRIPTION" = "C"."ID_INSCRIPTION"
LEFT OUTER JOIN "ADHERANTS" AS "E" ON "C"."INS_ID_ADHERANT" = "E"."ID_ADHERANT"
any idea on what's going on please, or how to fix that ?
thanks
it needs some research to optimize this :
if you neglect the data transfer from the db to the server then
as Ivan Stoev Suggested calling the ToList method is the expensive part
as for improving the performance it depends on your needs:
1.if you need add-delete functionality at the server side it is probably best to stick with the list
2.if no need for add-delete then consider ICollection ,, or even better
3.if you have more conditions which will customize the query even more best use IQuerable
customizing the query like selecting a single record based on a condition :
var q = from n in ctx.SEA.... // your query without ToList()
q.where(x=>"some condition") //let`s say x.Id=1
only one record will be transferred from the database to the server
but with the ToList Conversion all the records will be transferred to the server then the condition will be calculated
although it is not always the best to use IQuerable it depends on your business need
for more references check this and this

Nhibernate join filtering

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.

Inconsistent results between NHibernate Query and intended results

I have the following query in HQL :
public IEnumerable<Player> PlayersNotInTeam(Team team)
{
return Session.CreateQuery("from Player p where p.Sex = :teamSex and p.Visible and p.Id not in (select pit.Player from PlayerInTeam as pit join pit.Roster as roster join roster.Team as team where team = :teamId)")
.SetParameter("teamId", team.Id)
.SetParameter("teamSex", team.Sex)
.Enumerable<Player>();
}
When I run this query with NHibernate, it will return 2 rows.
If I run the SQL script generated by NH in my database browser (SQLite Explorer):
select player0_.Id as Id26_, player0_.Sex as Sex26_, player0_.FirstName as FirstName26_, player0_.LastName as LastName26_, player0_.DefaultNumber as DefaultN5_26_, player0_.Visible as Visible26_, player0_.DefaultPosition_id as DefaultP7_26_
from Players player0_
where player0_.Sex='Male'
and player0_.Visible=1
and (player0_.Id not in
(select playerinte1_.Player_id
from "PlayerInTeam" playerinte1_
inner join "Roster" roster2_ on playerinte1_.Roster_id=roster2_.Id
inner join Teams team3_ on roster2_.Team_id=team3_.Id,
Players player4_
where playerinte1_.Player_id=player4_.Id
and team3_.Id=2));
I have 3 rows, which is what I should have.
Why are my results different?
Thanks in advance
Mike
I have noticed that sometimes the logged SQL is not exactly the same as the one being really used against the database. The last time I had this issue, it was a problem with trimming the Id value, e.g., where the generated SQL has something like and team3_.Id=2, the SQL being used was actually and team3_.Id='2 ' (or perhaps, player_0.Sex='Male '), which would always fail.
I would suggest you try this HQL:
string hql = #"from Player p where p.Sex = 'Male'
and p.Visible and p.Id not in
(select pit.Player from PlayerInTeam as pit join pit.Roster as roster join roster.Team as team where team = 2)";
return Session.CreateQuery(hql).Enumerable<Player>();
If that works, you need to check if your values have spare whitespaces in them.
I've changed my query like this:
return Session.CreateQuery("from Player p where p.Sex = :teamSex and p.Visible and not exists (from PlayerInTeam pit where pit.Player = p and pit.Roster.Team = :teamId)")
.SetParameter("teamId", team.Id)
.SetParameter("teamSex", team.Sex)
.Enumerable<Player>();
And it now works. I had the idea to use "not exists" after I changed my mappings to try to use LINQ, which gave me the hint.
If you ask why I don't keep LINQ, that's because currently I hide the relationships between my entities as private fields, to force the users of the entities to use the helper functions which associate them. But the wrong thing is that in most cases, that forbids me to use LINQ in my repositories.
But I'm wondering if this wouldn't be better to "un-hide" my relationships and expose them as public properties, but keep my helper functions. This would allow me to use LINQ in my queries.
What do you do in your apps using NH?
Do you think this would be an acceptable trade-off to maintain easy mappings and queries (with the use of LINQ), but with the cost of some potential misuses of the entities if the user doesn't use the helper functions which keep the relationships?