I am running a NativeQuery with JPA that gives different results compared to running the query in an sql tool. Probably I missunderstand s.th. within the concept of #SQLResultSetMapping.
--- Overview ---
I am mapping the result to two entities, so I am expecting to receive a List of Entity-Pairs. This works.
When you look at the picture below, you'll see the result of the query in an sql tool, where ..
.. the RED BOX maps to one entity
.. the GREEN BOX maps to the second entity
JPA should give me one of the native row as a pair of two entities.
Problem
This is where things go wrong. Yes, I will receive a list of pairs of both entities, but unlike in the picture the column "pp.id" does not iterate over all rows of the respective table (in the picture "5,6,7,..", from JPA "5,5,5,5,..").
The column pp.id is a joined column, I guess that I missunderstand something within JPA when it comes to Joins + SQLResultSetMappings. It appears to me that the difference is that JPA is always joining THE SAME row from table 'propertyprofile' (more detailes below), unlike when the query is run in sql.
I hope that somebody takes pity on me and helps me out. :)
--- Details ---
Query
I am basically trying to find out if every 'product', has defined a 'value' (table propertyvalue) for a predefined 'property' (table propertyprofile).
The probably most relevant part is at the bottom, where "propertyprofile" is joined and "propertyvalue" is left-joined.
select sg.ID as 'sg.id', sg.Name as 'sg.name', ppcount.totalppcount as 'sg.totalppcount', ppcount.totalppothercount as 'sg.totalppothercount',
p.ID as 'product.id', pp.id as 'pp.id', pp.Role as 'pp.role', pp.Name as 'pp.name',
(case when pv.id is null then '0' else '1' end) as 'hasPropertyValue', pv.ID as 'pv.id', pv.StringValue, pv.IntervallMin, pv.IntervallMax
from shoppingguide sg
join
(
select sg.ID as 'sgid', count(*) as 'totalppcount', count(pp_other.ID) as 'totalppothercount' from propertyprofile pp_all
left join propertyprofile pp_other on pp_other.id = pp_all.id AND pp_other.Role = '0'
join shoppingguide sg on pp_all.ShoppingGuideID = sg.ID
join shopifyshop ss on sg.ShopifyShopID = ss.ID
where
pp_all.ShoppingGuideID = sg.ID AND
ss.Name = :shopName
GROUP BY pp_all.ShoppingGuideID
) ppcount on ppcount.sgid = sg.id
join shopifyshop ss on sg.ShopifyShopID=ss.ID
join product p on p.ShopifyShopID = ss.ID
join propertyprofile pp on (pp.ShoppingGuideID = sg.id AND pp.Role = '0')
left join propertyvalue pv on (pv.ProductID=p.ID and pv.PropertyProfileID = pp.id)
where
ss.Name = :shopName
order by sg.id asc, p.id asc, pp.id asc
;
Tables
There are a lot of tables involved, but these are the most important ones to understand the query:
product
propertyprofile - a feature that all products have (e.g. height, price)
propertyvalue - data for a specific feature; relates to propertyprofile (e.g. 5cm; $120)
SQLResultSetMapping
The mapping is done onto two entites: ProductDataFillSummary_ShoppingGuideInformation, ProductDataFillSummary_ProductInformation.
#SqlResultSetMapping(
name = "ProductDataFillSummaryMapping",
entities = {
#EntityResult (
entityClass = ProductDataFillSummary_ShoppingGuideInformation.class,
fields = {
#FieldResult(name = "shoppingGuideId", column = "sg.id"),
#FieldResult(name = "shoppingGuideName", column = "sg.name"),
#FieldResult(name = "numberOfTotalPropertyProfiles", column = "sg.totalppcount"),
#FieldResult(name = "numberOfTotalPropertyProfilesOther", column = "sg.totalppothercount")
}),
#EntityResult(
entityClass = ProductDataFillSummary_ProductInformation.class,
fields = {
#FieldResult(name = "productID", column = "product.id"),
#FieldResult(name = "propertyProfileId", column = "pp.id"),
#FieldResult(name = "propertyProfileRole", column = "pp.role"),
#FieldResult(name = "propertyValueId", column = "pv.id"),
#FieldResult(name = "hasPropertyValue", column = "hasPropertyValue")
}
)
})
Analysis
The problem seems to be that Hibernate does NOT ..
.. process each row
.. per row map onto designated entities
.. put the mapped entities for this row into List (in my example a pair of entities)
In fact hibernate seems to match both entities, which should go into the same entry of List, based on the primary key attributes, i.e. sth like this:
.. process each row
.. for each row map to respective entities (separately)
.. store the mapped entities using their primary key
.. match respective entities which go into the same entry for List
In my example, a pairs of [ProductDataFillSummary_ShoppingGuideInformation, ProductDataFillSummary_ProductInformation] will be inserted into the list. When 'ProductDataFillSummary_ProductInformation' is insterted, Hibernate will try to find the correct instance using the primary key (here 'ProductDataFillSummary_ProductInformation.productId'). Due to several rows for ProductDataFillSummary_ProductInformation having the same value for productId, always the first instance will be fetched and used for List.
Solution
Either use a compound key that considers 'ProductDataFillSummary_ProductInformation.productId' and '.propertyProfileId', or ..
Use an artifical key (uuid) if it's not possible to use a combined key:
concat(p.ID, '-', pp.ID) as 'uuid'
Related
I have the below query in PG
SELECT
project.project_id,
project.project_name,
category.category_name,
array_agg(row(skill.skill_name,projects_skills.projects_skills_id)) AS skills
FROM project
JOIN projects_skills ON project.project_id = projects_skills.project_id
JOIN skill ON projects_skills.skill_id = skill.skill_id
JOIN category ON project.category_id = category.category_id
GROUP BY project.project_name,project.project_id, category.category_name;
of particular interest is the below line which seems to return a pseudo-type tuple
array_agg(row(skill.skill_name,projects_skills.projects_skills_id)) AS skills
I'm unable to create a view of this because of the pseudo type - in addition to this, the row function seems to return a tuple set like the below:
skills: '{"(Python,3)","(Node,4)","(Javascript,5)"}' }
I could painfully parse it in JavaScript by replacing '(' to '[' etc. but could I do something in postgres to return it preferably as an object?
One possible solution is to register a row type (once):
CREATE TYPE my_type AS (skill_name text, projects_skills_id int);
I am guessing text and int as data types. Use the actual data types of the underlying tables.
SELECT p.project_id, p.project_name, c.category_name
, array_agg((s.skill_name, ps.projects_skills_id)::my_type) AS skills
FROM project p
JOIN projects_skills ps ON p.project_id = ps.project_id
JOIN skill s ON ps.skill_id = s.skill_id
JOIN category c ON p.category_id = c.category_id
GROUP BY p.project_id, p.project_name, c.category_name;
There are many other options, depending on your version of Postgres and what you need exactly.
As well as the excellent suggestions to use JSON in the comments, and #Erwin 's to use a registered composite type, you can use a two-dimension array, or a multivalues approach:
Just replace your line
array_agg(row(skill.skill_name::text,projects_skills.projects_skills_id::text)) AS skills
with the following:
Two dimension array option 1
array_agg(array[skill.skill_name::text,projects_skills.projects_skills_id::text]) AS skills
-- skills will be '{{Python,3},{Node,4},{Javascript,5}}', thus
-- skills[1][1] = 'Python' and skills[1][2] = '3' -- id is text
Two dimension array option 2
array[array_agg(skill.skill_name),array_agg(projects_skills.projects_skills_id)] AS skills
-- skills will be '{{Python,Node,Javascript},{3,4,5}}', thus
-- skills[1][1] = 'Python' and skills[2][1] = '3' -- id is text
Multivalues
array_agg(skill.skill_name) AS skill_names,
array_agg(projects_skills.projects_skills_id) AS skills_ids
-- skills_names = '{Python,Node,Javascript}' and skill_ids = '{3,4,5}', thus
-- skills_names[1] = 'Python' and skills_ids[1] = 3 -- id is integer
Just to clarify i can't change the tables structure, so please leave out the "you should change your tables to this and that" answers, thank you.
So i have a table entities_attributes_values where an entity has a lot of attributes and the value of that attribute, basically imagine 3 fields:
entity_id
entity_attributes_id
value
Because every entities attribute and its value is on row getting more values is not so easy i was thinking of multiple self joins, and because this query will be very common i created a view, which is built with this query:
SELECT `L1`.`entity_id`,
`L1`.`value` as 'company_id',
`L2`.`value` as 'entity_name',
`P`.`value` as 'person_name',
`L4`.`value` as 'establishment_id',
`L5`.`value` as 'department_id'
FROM `entities_attributes_values` `L1`
LEFT JOIN `entities_attributes_values` `L2` ON `L1`.`entity_id` = `L2`.`entity_id` AND `L2`.`entity_attributes_id` = 1
LEFT JOIN `entities_attributes_values` `L3` ON `L1`.`entity_id` = `L3`.`entity_id` AND `L3`.`entity_attributes_id` = 3
LEFT JOIN `persons_attributes_values` `P` ON `L3`.`value` = `P`.`core_persons_id` AND `P`.`core_persons_attributes_id` = 4
LEFT JOIN `entities_attributes_values` `L4` ON `L1`.`entity_id` = `L4`.`entity_id` AND `L4`.`entity_attributes_id` = 12
LEFT JOIN `entities_attributes_values` `L5` ON `L1`.`entity_id` = `L5`.`entity_id` AND `L5`.`entity_attributes_id` = 13
WHERE `L1`.`entity_attributes_id` = 2
So this works but i have one problem i get "duplicate" values and its not really duplicate but the point is that in my view i want every entity to be only one row with all its attributes values but instead i get this:
So as you can see the first three result are not good for me, i only need the fourth one, where i have all my data about one entity.
Thank you in advance for any help!
Try using conditional aggregation instead:
select eav.entity_id,
max(case when entity_attributes_id = 2 then eav.value end) as company_id,
max(case when entity_attributes_id = 1 then eav.value end) as entity_name,
max(case when entity_attributes_id = 3 then eav.value end) as company_name,
. . .
from entities_attributes_values eav
group by eav.entity_id;
This will make it easy to add new attributes to the view. Also, don't use single quotes to delimit column names. Single quotes should only be used for date and time constants.
I'm trying to join two tables in my database based off two values that exist in both tables (model and manufacturer), there is no fk relationship in the database for these values for various reasons.
So far I have the following:
var modelManufacturer = DataContext.Assets_ND.Select(a => new {a.ModelNo, a.Manufacturer}).Distinct();
var masterPMs = DataContext.MasterPlannedMaintenances.Where(pm => pm.PlantId == model.PlantId);
var joined = modelManufacturer.Join(masterPMs.AsEnumerable(), a => new {a.ModelNo, a.Manufacturer},pm => new {pm.Model, pm.Manufacturer}, x => new {x.ModelNo, x.Manufacturer, x.Id});
This doesn't compile and has the error
Error 7 The type arguments for method
'System.Linq.Queryable.Join(System.Linq.IQueryable,
System.Collections.Generic.IEnumerable,
System.Linq.Expressions.Expression>,
System.Linq.Expressions.Expression>,
System.Linq.Expressions.Expression>)'
cannot be inferred from the usage. Try specifying the type arguments
explicitly.
This is where I'm a bit stumped as to how to specify the values, I've tried a few combinations of
modelManufacturer.Join<Asset, MasterPlannedMaintenance....> but intellisense seems to suggest that isnt right, or at least the order I'm putting them in isn't corrrect.
What am I doing wrong or what is a better way to join these two tables based on two string fields and return 2 values from the left table (Model, Manufacturer) and one from the right (id).
var joined = (from a in dc.table1
join b in dc.table2 on a.id equals b.id into temp
from t in temp.DefaultIfEmpty()
select new {
a.Model,
a.Manufacturer,
t.ID
});
This will do a left join on the two tables and only select two values from table1 and 1 from table 2.
Say I have a simple blog entry model in Django:
class Entry(models.Model):
author = models.ForeignKey(Author)
topic = models.ForeignKey(Topic)
entry = models.CharField(max_length=50, default='')
Now say I want to query for a author or topic, but exclude a particular topic altogether.
entry_list = Entry.objects.filter(Q(author=12)|Q(topic=123)).exclude(topic=666)
Sinmple enough, but I've found that this raw SQL contains a join on the topic table, even though it doesn't have to be used:
SELECT `blog_entry`.`id`
FROM `blog_entry`
LEFT OUTER JOIN `blog_topic`
ON (`blog_entry`.`topic_id` = `blog_topic`.`id`)
WHERE ((`blog_entry`.`author_id` = 12
OR `blog_entry`.`topic_id` = 123
)
AND NOT ((`blog_topic`.`id` = 666
AND NOT (`blog_topic`.`id` IS NULL)
AND `blog_topic`.`id` IS NOT NULL
))
)
Why is that? How can I get Django to query only the column ids and not join tables? I've tried the following but it give a FieldError:
entry_list = Entry.objects.filter(Q(author_id=12)|Q(topic_id=123)).exclude(topic_id=666)
i wonder whether this is a bug.
trying a similar example, i get no join when putting the exclude before the filter (but i do get it using your order)
I've got 3 dataset objects that are nested with each other using entity set objects. I am selecting the data like this
var newList = from s in MainTable
from a in s.SubTable1 where a.ColumnX = "value"
from b in a.Detail where b.Name = "searchValue"
select new {
ID = s.ID,
Company = a.CompanyName,
Name = b.Name,
Date = s.DueDate
Colour = b.Colour,
Town = a.Town
};
and this works fine, but the trouble is there are many records in the Detail object-list/table for each Name value so I get a load of duplicate rows and thus I only want to display one record per b.Name. I have tried putting
group s by b.Name into g
before the select, but then this seems to stop the select enabling me to select the columns I want (there are more, in practice). How do I use the group command in this circumstance while still keeping the output rows in a "flat" format?
Appending comment as answer to close question:-
Of course that if you group your results, you cant get select a column of a child, thats because there may be more than one childs and you have to specify an aggregate column for example the sum,max etx –