Creating a 'many-to-many' relationship - bookshelf.js

I have three tables: Schools (id, school_name), Schools_Focuses (id, school_id, focus_id) and Focuses (id, focus) and want to create a method on my Schools model that returns all related Focuses.
I can perform what I want to do with this SQL QUERY:
SELECT focus FROM focuses INNER JOIN schools_focuses ON focuses.id = schools_focuses.focus_id INNER JOIN schools ON schools.id = schools_focuses.school_id WHERE schools.id = 36;
Model code:
// Define Models
exports.School = School = Bookshelf.PG.Model.extend({
tableName: 'schools',
focuses: function() {
return this.hasMany(Focus).through(Schools_Focuses);
}
});
Error:
Possibly unhandled Error: column focuses.schools_focuse_id does not exist, sql: select "focuses".*, "schools_focuses"."id" as "_pivot_id", "schools_focuses"."school_id" as "_pivot_school_id" from "focuses" inner join "schools_focuses" on "schools_focuses"."id" = "focuses"."schools_focuse_id" where "schools_focuses"."school_id" in (?)
I don't want to have this extra column (focuses.schools_focuse_id) in Focuses because a focus can belongTo more than one school.
How can I correctly set this up? I have played around with the foreign keys and other keys for hasMany() and through() but no luck.
Thanks!

Sounds like you need to use belongsToMany() instead of hasMany().
// Define Models
exports.School = School = Bookshelf.PG.Model.extend({
tableName: 'schools',
focuses: function() {
return this.belongsToMany(Focus, 'schools_focuses');
}
});
The second parameter is needed since the join table isn't in alpha order ('focuses_schools'). And if it has problems identifying the join keys (school_id, focus_id) you can override those as parameters as well. Also, since it uses the join table internally, you don't need to create a separate model for Schools_Focuses.

If we don't need to create a separate model for Schools_Focuses, then how do we query data from the junction table i.e:
'SELECT * FROM focuses_schools WHERE focus_id = 2 & school_id = 1'

Related

How to write complex recursive maria db query

Im trying to write a recursive query for a use on a old and poorly designed database - and so the queries get quite complex.
Here is the (relevant) table relationships
Because people asked - here is the creation code for these tables:
CREATE TABLE CircuitLayout(
CircuitLayoutID int,
PRIMARY KEY (CircuitLayoutID)
);
CREATE TABLE LitCircuit (
LitCircuitID int,
CircuitLayoutID int,
PRIMARY KEY (LitCircuitID)
FOREIGN KEY (CircuitLayoutID) REFERENCES CircuitLayout(CircuitLayoutID)
);
CREATE TABLE CircuitLayoutItem(
CircuitLayoutItemID int,
CircuitLayoutID int,
TableName varchar(255),
TablePK int,
PRIMARY KEY (CircuitLayoutItemID)
FOREIGN KEY (CircuitLayoutID) REFERENCES CircuitLayout(CircuitLayoutID)
);
TableName refers to another table in the database and thus TablePK is a primary key from the specified table
One of the valid options for TableName is LitCircuit
I'm trying to write a query that will select a circuit and any circuit it is related to
I am having trouble understanding the syntax for recursive ctes
my non-functional attempt is this:
WITH RECURSIVE carries AS (
SELECT LitCircuit.LitCircuitID AS recurseList FROM LitCircuit
JOIN CircuitLayoutItem ON LitCircuit.CircuitLayoutID = CircuitLayoutItem.CircuitLayoutID
WHERE CircuitLayoutItem.TableName = "LitCircuit" AND CircuitLayoutItem.TablePK IN (00340)
UNION
SELECT LitCircuit.LitCircuitID AS CircuitIDs FROM LitCircuit
JOIN CircuitLayout ON LitCircuit.CircuitLayoutID = CircuitLayoutItem.CircuitLayoutID
WHERE CircuitLayoutItem.TableName = "LitCircuit" AND CircuitLayoutItem.TablePK IN (SELECT recurseList FROM carries)
)
SELECT * FROM carries;
the "00340" is a dummy number for testing, and it would get replaced with an actual list in usage
What i'm attempting to do is get a list of LitCircuitIDs based on one or many LitCircuitIDs - that's the anchor member, and that works fine.
What I want to do is take this result and feed it back into itself.
I lack an understanding of how to access data from the anchor member:
I don't know if it is a table with the columns from the select in the anchor or if it is simply a list of resulting values
I dont understand if or where I need to include "carries" in the FROM part of a query
If I were to write this function in python I would do it like this:
def get_circuits(circuit_list):
result_list = []
for layout_item_key, layout_item in CircuitLayoutItem.items():
if layout_item['TableName'] == "LitCircuit" and layout_item['TablePK'] in circuit_list:
layout = layout_item['CircuitLayoutID']
for circuit_key, circuit in LitCircuit.items():
if circuit["CircuitLayoutID"] == layout:
result_list.append(circuit_key)
result_list.extend(get_circuits(result_list))
return result_list
How do I express this in SQL?
danblack's comment made me realize something I was missing:
Here is what I was trying to do:
WITH RECURSIVE carries AS (
SELECT LitCircuit.LitCircuitID FROM LitCircuit
JOIN CircuitLayoutItem ON LitCircuit.CircuitLayoutID = CircuitLayoutItem.CircuitLayoutID
WHERE CircuitLayoutItem.TableName = 'LitCircuit' AND CircuitLayoutItem.TablePK IN (00340)
UNION ALL
SELECT LitCircuit.LitCircuitID FROM carries
JOIN CircuitLayoutItem ON carries.LitCircuitID = CircuitLayoutItem.TablePK
JOIN LitCircuit ON CircuitLayoutItem.CircuitLayoutID = LitCircuit.CircuitLayoutID
WHERE CircuitLayoutItem.TableName = 'LitCircuit'
)
SELECT DISTINCT LitCircuitID FROM carries;
I did not think of the CTE as a table to query against - rather just a result set, so I did not realize you have to SELECT from it - or in general treat it like a table.

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.

Entity Framework using SQL Server datastore modelling a one-to-many relationship

Can I get some help diagnosing the following?
IQueryable<MyComponentsViewModel> vcvm = from v1 in pg.MyComponents
join item in Items on v1.Item equals item into vc
select new MyComponentsViewModel
{ Cost = item.Cost };
The following error appears:
Error CS1941: The type of one of the expressions in the join clause is
incorrect. Type inference failed in the call to 'GroupJoin'.
Is this error state occuring as item is not the same type as v1.Item?
The keys are shown below
ALTER TABLE [dbo].[MyComponents] WITH CHECK ADD CONSTRAINT
[FK_MyComponents_Items] FOREIGN KEY([ItemId])
REFERENCES [dbo].[Items] ([Itemid])
GO
ALTER TABLE [dbo].[MyComponents] CHECK CONSTRAINT [FK_MyComponents_Items]
I assume like, you need inner join and while you doing equals you need to use their matching column Ids to filter records like below
IQueryable<MyComponentsViewModel> vcvm =
from myComp in pg.MyComponents
join item in Items on myComp.ItemId equals item.Id
select new MyComponentsViewModel { Cost = item.Cost };
I was able to get it to work.
There was an ambiguity between an action named Items. Fully specify the namespace resolved this.
Additionally I had to add the statement "from subitem in vc.DefaultIfEmpty"
I'm still working to learn the full semantics of this
IQueryable<MyComponentsViewModel> vcvm =
from v1 in pg.MyComponents
join item in pg.Items on v1.Item equals item into vc
from subitem in vc.DefaultIfEmpty()
select new MyComponentsViewModel { Cost = subitem.Cost };

selecting only max clause without group by properties in subquery using Nhibernate

I have SQL query like this:
select * from dbo.table1 where Id in
(
select max(id) as id from dbo.table1 group by prop1, prop2, prop3
)
I want to create NHibernate query which is be able to do this for me. I tried to use QueryOver but it doesn't work. Do you have any suggestions how to do it?
NHibernate supports even this kind of queries. Please, see more in documentation: 15.8. Detached queries and subqueries. We just have to split the query (as in your SQL snippet) into two parts:
inner select
the select with the IN clause
Let's assume, that the dbo.table1 in the Questin is mapped into MyEntity.
To create inner select, let's use the DetachedCriteria
EDIT (extended with the Group by, SqlGroupProjection)
There is an extract of the SqlGroupProjection method:
A grouping SQL projection, specifying both select clause and group by
clause fragments
// inner select
DetachedCriteria innerSelect = DetachedCriteria
.For(typeof(MyEntity))
.SetProjection(
Projections.ProjectionList()
.Add(
Projections.SqlGroupProjection(
" MAX(ID) ", // SELECT ... max(ID) only
" Prop1, Prop2, Prop3", // GROUP BY ... property1, p2...
new string[] {"ID"}, // could be empty, while not used for
new IType[] { NHibernate.NHibernateUtil.Int32 } // transformation
)
)
;
Note: I've provided even the last two paramters, but in this case they could be empty: new string[], new IType[] {}. These are used only for Transformation (materialization from data into entity). And this is not the case, we are just building inner select...
// the select with IN clause
var result = session.CreateCriteria(typeof(MyEntity))
.Add(Subqueries.PropertyIn("ID", innerSelect))
.List<MyEntity>();
Also related could be 15.7. Projections, aggregation and grouping

Simple.Data join without where clause including any primary table columns

I am using Simple.Data and have been trying to find an example that will let me do a join with the only condition in the WHERE clause be from the joined table. All of the examples I have seen always have at least one column in the primary table included in the WHERE. Take for example the following data:
private void TestSetup()
{
var adapter = new InMemoryAdapter();
adapter.SetKeyColumn("Events", "Id");
adapter.SetAutoIncrementColumn("Events", "Id");
adapter.SetKeyColumn("Doors", "Id");
adapter.SetAutoIncrementColumn("Doors", "Id");
adapter.Join.Master("Events", "Id").Detail("Doors", "EventId");
Database.UseMockAdapter(adapter);
db.Events.Insert(Id: 1, Code: "CodeMash2013", Name: "CodeMash 2013");
db.Events.Insert(Id: 2, Code: "SomewhereElse", Name: "Some Other Conf");
db.Doors.Insert(Id: 1, Code: "F7E08AC9-5E75-417D-A7AA-60E88B5B99AD", EventID: 1);
db.Doors.Insert(Id: 2, Code: "0631C802-2748-4C63-A6D9-CE8C803002EB", EventID: 1);
db.Doors.Insert(Id: 3, Code: "281ED88F-677D-49B9-84FA-4FAE022BBC73", EventID: 1);
db.Doors.Insert(Id: 4, Code: "9DF7E964-1ECE-42E3-8211-1F2BF7054A0D", EventID: 2);
db.Doors.Insert(Id: 5, Code: "9418123D-312A-4E8C-8807-59F0A63F43B9", EventID: 2);
}
I am trying to figure out the syntax I need to use in Simple.Data to get something similar to this T-SQL:
SELECT d.Code FROM Doors AS d INNER JOIN Events AS e ON d.EventID = e.Id WHERE e.Code = #EventCode
The final result should be only the three Door rows for EventId 1 when I pass in an event code of "CodeMash2013". Thanks!
First, a general point: since you've got criteria against the joined Events table, the LEFT OUTER is redundant; only rows with matching Event Codes will be returned, which implies only those rows where the join from Doors to Events was successful.
If you've got referential integrity set up in your database, with a foreign key relationship from Doors to Events, then Simple.Data can handle joins automatically. With that in mind, this code will work, both with the InMemoryAdapter and SQL Server:
List<dynamic> actual = db.Doors.FindAll(db.Doors.Events.Code == "CodeMash2013")
.Select(db.Doors.Id, db.Events.Name)
.ToList();
Assert.AreEqual(3, actual.Count);
If you don't have referential integrity set up then you should, but if you can't for some reason, then the following will work with SQL Server, but will trigger a bug in the InMemoryAdapter that I've just fixed but haven't done a release for yet:
dynamic eventAlias;
List<dynamic> actual = db.Doors.All()
.Join(db.Events, out eventAlias)
.On(db.Doors.EventID == eventAlias.Id)
.Select(db.Doors.Id, db.Events.Name)
.Where(eventAlias.Code == eventCode)
.ToList();
Assert.AreEqual(3, actual.Count);
UPDATE : This answer applies when using the Simple.Data SQL Server Provider, not the InMemoryAdapter
You could probably use the following for this:
db.Doors.All()
.Select(
db.Doors.Code)
.LeftJoin(db.Events).On(db.Doors.EventID == db.Events.Id)
.Where(db.Events.Code == eventCode);
You might need to experiment between using LeftJoin and OuterJoin depending on your provider. If you're using the ADO provider for example, the two functions both generate LEFT JOIN statements as LEFT JOIN and LEFT OUTER JOIN are synonymous in t-sql.
If you need to use aliases for some reason, the syntax is slightly different.
dynamic EventAlias;
db.Doors.All()
.LeftJoin(db.Events.As"e", out EventAlias).On(db.Doors.EventID == db.EventAlias.Id)
.Select(
db.Doors.Code)
.Where(db.EventAlias.Code == eventCode);
There's no reason why a where clause must contain a field from the primary key table. You can find more examples of Joins here on the Simple.Data doc site. Click Explicit Joins when you get there.

Resources