How to specify the primary keys for join table - jdo

I have a class that has a List that I wish to relate to through a join table to allow 1-M and M-1 relations in the same time. In other words, I'd like to reuse the elements in the list.
In case that doesn't make sense or is inapplicable in a way please advice as I'm still trying to design that part of the model.
So the list is mapped this way:
#Persistent(table = "ixl_csv_metric_rel", defaultFetchGroup = "true")
#Join(column = "ixl_csv_fk")
#Order(column = "order")
#Element(dependent = "true", column = "ixl_metric_fk")
private List<IxlMetric> metrics;
The thing is that the join table is created with a compound primary key composed of the ixl_csv_fk and order columns which isn't what I need. The primary key would better be composed of the ixl_metric_fk column instead of the order column which make a lot more sense to me.
I can disable generating the primary key and set it manually later on but I was wondering if there was a better way to do it ?
#Join(column = "ixl_csv_fk", extensions = { #Extension(vendorName = "datanucleus", key = "primary-key", value = "false") })

No other way. Disable it and manually override it yourself. JDO provides the only sensible way of doing it to match the class definitions and a List field.

Related

Converting a field to lower case and merging data in an sqlite database

I need to merge some randomly uppercased data that has been collected in an SQLite table key_val, such that key is always lowercase and no vals are lost. There is a unique compound index on key,val.
The initial data looks like this:
key|val
abc|1
abc|5
aBc|1
aBc|5
aBc|3
aBc|2
AbC|1
abC|3
The result after the merge would be
key|val
abc|1
abc|2
abc|3
abc|5
In my programmer brain, I would
for each `key` with upper case letters;
if a lower cased `key` is found with the same value
then delete `key`
else update `key` to lower case
Re implementing the loop has a sub query for each row found with upper case letters, to check if the val already exists as a lower case key
If it does, I can delete the cased key.
From there I can UPDATE key = lower(key) as the "duplicates" have been removed.
The first cut of the programming method of finding the dupes is:
SELECT * FROM key_val as parent
WHERE parent.key != lower(parent.key)
AND 0 < (
SELECT count(s.val) FROM key_val as s
WHERE s.key = lower(parent.key) AND s.val = parent.val
)
ORDER BY parent.key DESC;
I'm assuming there's a better way to do this in SQLite? The ON CONFLICT functionality seems to me like it should be able to handle the dupe deletion on UPDATE but I'm not seeing it.
First delete all the duplicates:
DELETE FROM key_val AS k1
WHERE EXISTS (
SELECT 1
FROM key_val AS k2
WHERE LOWER(k2.key) = LOWER(k1.key) AND k2.val = k1.val AND k2.rowid < k1.rowid
);
by keeping only 1 combination of key and val with the min rowid.
It is not important if you kept the key with all lower chars or not, because the 2nd step is to update the table:
UPDATE key_val
SET key = LOWER(key);
See the demo.
Honestly it might just be easier to create a new table and then insert into it. As it seems you really just want a distinct select here, use:
INSERT INTO kev_val_new ("key", val)
SELECT DISTINCT LOWER("key"), val
FROM key_val;
Once you have populated the new table, you may drop the old one, and then rename the new one to the previous name:
DROP TABLE key_val;
ALTER TABLE key_val_new RENAME TO key_val;
I agree with #Tim that it would be easire to re-create table using simple select distict lower().. statement, but that's not always easy if table has dependant objects (indexes, triggers, views). In this case this can be done as sequence of two steps:
insert lowered keys which are not still there:
insert into t
select distinct lower(tr.key) as key, tr.val
from t as tr
left join t as ts on ts.key = lower(tr.key) and ts.val = tr.val
where ts.key is null;
now when we have all lowered keys - remove other keys:
delete from t where key <> lower(key);
See fiddle: http://sqlfiddle.com/#!5/84db50/11
However this method assumes that key is always populated (otherwise it would be a strange key)
If vals can be null then "ts.val = tr.val" should be replaced with more complex stuff like ifnull(ts.val, -1) = ifnull(tr.val, -1) where -1 is some unused value (can be different). If we can't assume any unused value like -1 then it should be more complex check for null / not null cases.

How to get all the elements of an association?

I have created a CDS views as follows:
define view YGAC_I_REQUEST_ROLE
with parameters
pm_req_id : grfn_guid,
#Consumption.defaultValue: 'ROL'
pm_item_type : grac_prov_item_type,
#Consumption.defaultValue: 'AP'
pm_approval : grac_approval_status
as select from YGAC_I_REQ_PROVISION_ITEM as provitem
association [1..1] to YGAC_I_ROLE as _Role on _Role.RoleId = provitem.ProvisionItemId
association [1..*] to YGAC_I_ROLE_RS as _Relation on _Relation.RoleId1 = provitem.ProvisionItemId
{
key ReqId,
key ReqIdItem,
Connector,
ProvisionItemId,
ActionType,
ValidFrom,
ValidTo,
_Role.RoleId,
_Role.RoleName,
_Role.RoleType,
_Role,
_Relation
}
where
ReqId = $parameters.pm_req_id
and ProvisionItemType = $parameters.pm_item_type
and ApprovalStatus = $parameters.pm_approval
Then I have consumed in ABAP:
SELECT
FROM ygac_i_request_role( pm_req_id = #lv_test,
pm_item_type = #lv_item_type,
pm_approval = #lv_approval
)
FIELDS reqid,
connector,
provisionitemid
INTO TABLE #DATA(lt_result).
How to get the list of _Relation according to selection above.
This is generally not possible like in ABAP SQL queries:
SELECT m~*, kt~*
FROM mara AS m
JOIN makt AS kt
...
This contradicts the whole idea of CDS associations, because they were created to join on-demand and to reduce redundant calls to database. Fetching all fields negates the whole idea of "lazy join".
However, there is another syntax in FROM clause which is enabled by path expressions that allows querying underlining associations both fully and by separate elements. Here is how
SELECT *
FROM ygac_i_request_role( pm_req_id = #lv_test )
\_Role AS role
INTO TABLE #DATA(lt_result).
This fetches all the fields of _Role association into internal table.
Note: remember, it is not possible to fetch all the published associations of current view simultaneously, only one path per query.
Possible workaround is to use JOIN
SELECT *
FROM ygac_i_request_role AS main
JOIN ygac_i_request_role
\_Role AS role
ON main~ProvisionItemId = role~RoleId
JOIN ygac_i_request_role
\_Relation AS relation
ON main~ProvisionItemId = relation~RoleId1
INTO TABLE #DATA(lt_table).
This creates deep-structured type with dedicated structure for every join association like this:
If you are not comfortable with such structure for you task, lt_table should be declared statically to put up all the fields in a flat way
TYPES BEGIN OF ty_table.
INCLUDE TYPE ygac_i_request_role.
INCLUDE TYPE ygac_i_role.
INCLUDE TYPE ygac_i_role_rs.
TYPES END OF ty_table.

JDO shared-join-tables foreign-keys aren't valid

Please assume the following mapping
Class PieChart{
#Persistent(defaultFetchGroup = "true", table = "chart_metrics_rel")
#Join(column = "chart_fk")
#Order(column = "order")
#Element(dependent = "true", column = "chart_metric_fk")
#Extensions({ #Extension(vendorName = "datanucleus", key = "relation-discriminator-column", value = "chart_type"),
#Extension(vendorName = "datanucleus", key = "relation-discriminator-pk", value = "true"),
#Extension(vendorName = "datanucleus", key = "relation-discriminator-value", value = "pie") })
private List<ChartMetric> metrics;
}
This works as expected but the problem is that I have the same exact mapping with another class called TimeseriesChart which have a whole another table with different id's. So the chart_fk column in the mapping shown above may sometimes point to PieChart and some other times point to TimeseriesChart. So the shared join table is created with 2 foreign-keys on the chart_fk column, once pointing to pie_charts (i.e. PieChart, the current class) and once pointing to the timeseries_chart (i.e. TimeseriesChart).
Which is not allowing me to add elements to the join table unless the chart_fk value exists in both the pie and timeseries tables !
I checked the docs and used the generateForeignKey annotation attribute for #Join but it isn't working.
That's the attribute's description:
generateForeignKey (String) ORM:Whether to generate a FK constraint on the join table (when not specifying the name)
So why does the attribute has no effect as the foreign-keys are still generated ?
And what name is when not specifying the name referring to ? the join table's or the FK's ?
Since that vendor extension clearly applies to sharing a join table between relations to the same type (as per what the DN docs show), then you're trying to apply it where it is not intended, hence user-error.
If you have two List<Chart> fields and one has one subclass (PieChart), and the other a different subclass (TimeseriesChart) then it is potentially applicable there (all of our tests are based around Collections of the same element type), but you present no such information.

EntityFramework using with database foreign key

Actually I spend whole day on the EntityFramework for foreign key.
assume we have two table.
Process(app_id,process_id)
LookupProcessId(process_id, process_description)
you can understand two tables with names, first table ,use process_id to indicate every application, and description is in the seoncd table.
Actually i try many times and figure out how to do inquery: it was like
Dim result = (from x in db.Processes where x.LookupProcess is (from m in db.LookupProcessIds where descr = "example" select m).FirstOrDefault() select x).FirstOrDefault()
First I want to ask is there easier way to do it.
Second i want to ask question is about insert
p As New AmpApplication.CUEngData.Process
p.app_id=100
p.LookupProcess = (from m in db.LookupProcessIds where descr = "example" select m).FirstOrDefault()
db.AddToProcesses(p)
db.SaveChanges()
from appearance it looks fine, but it give me error says
Entities in 'AmpCUEngEntities.Processes' participate in the 'FK_Process_LookupProcess' relationship. 0 related 'LookupProcess' were found. 1 'LookupProcess' is expected.
can i ask is that insert wrong? and is that my query correct?
For your first question:
Dim result = (from x in db.Processes
where x.LookupProcess.descr = "example"
select x).FirstOrDefault()
Actually, you missed some concepts from DataEntityModel, and its Framework. To manipulate data, you have to call object from contextual point of view. Those allow you to specify to the ObjectStateManager the state of an DataObject. In your case, if you have depending data from FK, you will have to add/update any linked data from leaf to root.
This example demonstrate simple (no dependances) data manipulation. A select if existing and an insert or update.
If you want more info about ObjectStateManager manipulation go to http://msdn.microsoft.com/en-us/library/bb156104.aspx
Dim context As New Processing_context 'deseign your context (this one is linked to a DB)
Dim pro = (From r In context.PROCESS
Where r.LOOKUPPROCESS.descr = LookupProcess.descr
Select r).FirstOrDefault()
If pro Is Nothing Then 'add a new one
pro = New context.PROCESS With {.AP_ID = "id", .PROCESS_ID = "p_id"}
context.PROCESS.Attach(pro)
context.ObjectStateManager.ChangeObjectState(pro, System.Data.EntityState.Added)
Else
'update data attibutes
pro.AP_ID = "id"
pro.PROCESS_ID = "p_id"
context.ObjectStateManager.ChangeObjectState(pro, System.Data.EntityState.Modified)
'context.PROCESS.Attach(pro)
End If
context.SaveChanges()
I hope this will help. Have a nice day!
For your first question, to expand on what #jeroenh suggested:
Dim result = (from x in db.Processes.Include("LookupProcess")
where x.LookupProcess.descr = "example"
select x).FirstOrDefault()
The addition of the Include statement will hydrate the LookupProcess entities so that you can query them. Without the Include, x.LookupProcess will be null which would likely explain why you got the error you did.
If using the literal string as an argument to Include is not ideal, see Returning from a DbSet 3 tables without the error "cannot be inferred from the query" for an example of doing this using nested entities.
For your second question, this line
p.LookupProcess = (from m in db.LookupProcessIds
where descr = "example" select m).FirstOrDefault()
Could cause you problems later on because if there is no LookupProcessId with a process_description of "example", you are going to get null. From MSDN:
The default value for reference and nullable types is null.
Because of this, if p.LookupProcess is null when you insert the entity, you will get the exception:
Entities in 'AmpCUEngEntities.Processes' participate in the 'FK_Process_LookupProcess' relationship. 0 related 'LookupProcess' were found. 1 'LookupProcess' is expected.
To avoid this kind of problem, you will need to check that p.LookupProcess is not null before it goes in the database.
If Not p.LookupProcess Is Nothing Then
db.AddToProcesses(p)
db.SaveChanges()
End If

Join two columns from Table A to Table B

I have found the answer to my question, but in the wrong format.
Using SQL Alchemy I want to join columns from Table A to one column on Table B.
Table A contains two columns for Location Code. I can retrieve the Location Name by joining on to Table B, but how to do this?
So far I have this:
locationreq = sa.Table("INMPTL_LOCATION_REQUEST", meta.metadata,
sa.Column("request_id", sa.types.String(), primary_key=True),
sa.Column("status", sa.types.String(100)),
sa.Column("new_loc", sa.types.String(), sa.ForeignKey("INMPTL_LOCATIONS_TBL.inmptl_location_code")),
sa.Column("previous_loc", sa.types.String(), sa.ForeignKey("INMPTL_LOCATIONS_TBL.inmptl_location_code")),
autoload=True,
autoload_with=engine)
locationtable = sa.Table("INMPTL_LOCATIONS_TBL", meta.metadata,
sa.Column("INMPTL_LOCATION_CODE", sa.types.Integer(), primary_key=True),
autoload=True,
autoload_with=engine)
orm.mapper(Location, locationtable )
orm.mapper(LocationRequest, locationreq, extension= wf.WorkflowExtension(), properties = {'location':relation(Location)}
If only one of these columns were mapped to the second table, I could call something such as:
model.LocationRequest.location.location_name
But because I am mapping two columns to the same table, it is getting confused.
Does anyone know the proper way to achieve this?
I was going to delete this question, but this is not a duplicate. The answer is here (setting the primary and secondary joins)
orm.mapper(LocationRequest, locationreq, extension= wf.WorkflowExtension(),
properties={
"new_location":relation(Location,
primaryjoin=locationtable.c.inmptl_location_code==locationreq.c.new_loc, lazy = False),
"previous_location":relation(Location,
primaryjoin=locationtable.c.inmptl_location_code==locationreq.c.previous_loc, lazy = False)
})

Resources