Related
I'm trying to get data from the database using Room, I want to get the data in the format {registration_number, List, List} but I'm getting an error:
"Cannot find the parent entity column area_name in ... and my intermediate class"
and in fact I hide that maybe I am taking the wrong approach, please guide me, because I am new in this area
to extract the data I use an intermediate class
my class is:
data class LastConfiscats(
#ColumnInfo(name = "registration_number")
var slaugh_num: String,
// #ColumnInfo(name = "area_name",
#Relation(entity = Area::class, parentColumn = "area_name", entityColumn = "name")
var areaName: List<String>,
// #ColumnInfo(name = "confiscation_name")
#Relation(entity = Confiscation::class, parentColumn = "confiscation_name", entityColumn = "name")
var confiscationName: List<String>
and DAO method to select data:
#Query("SELECT registration_number, area.[name] AS area_name, confiscations.[name] AS confiscation_name " +
"FROM car_body, car_body_confiscations" +
"INNER JOIN area ON car_body_confiscations.area_id == area.id " +
"INNER JOIN confiscations ON car_body_confiscations.confiscation_id == confiscations.id " +
"WHERE car_body.id == car_body_confiscations.car_body_id ORDER BY car_body.id DESC LIMIT :row_count")
fun getLastConfiscats(row_count: Int): LiveData<List<LastConfiscats>>
The linkage scheme between the tables that I am trying to implement is as follows:
There are examples on the internet how to make a relationship between 2 tables but I need to create a relationship between 4 tables.
Please help me to get the data in the right way
UPDATE :
My Area entity is:
#Entity(tableName = "area")
data class Area( #PrimaryKey(autoGenerate = true) var id: Int?, var name: String? )
but in my Confiscation entity I also have "name" column:
#Entity(tableName = "confiscations")
data class Confiscation( #PrimaryKey(autoGenerate = true) var id: Int?, var name: String? )
The actual message you are getting is because when you use #Relation the parent MUST exist and be annotated with #Embedded.
The parent and entity columns MUST be columns in the respective classes.
As an example the following will enable you to get a List of Confiscations, with the related CarBody and the respective Areas (note colum names based upon the screen shots):-
data class LastConfiscats(
#Embedded
var carBodyConfiscations: Car_Body_Confiscations,
#Relation(entity = CarBody::class, parentColumn = "car_body_id", entityColumn = "id")
var carBody: CarBody,
#Relation(entity = Area::class, parentColumn = "areaId", entityColumn = "id")
var area: List<Area>
)
You could use the above with a query such as:-
#Query("SELECT * FROM Car_Body_Confiscations")
fun getCardBodyJoinedWithStuff(): List<LastConfiscats>
No JOINS needed. That is because Room builds the underlying SQL. First is basically the copy of the supplied query. After retrieving the Car_Body_Confiscations it then uses queries based upon the field names/#ColumnInfo and runs queries for each Car_Body_Connfiscation.
For each #Relationship it populates the respective fields (1 carBody and the List of Areas) using queries that it builds. Here's and example of part of the code, for the above from the java(generated) for the query above :-
Main (parent query)
#Override
public List<LastConfiscats> getCardBodyJoinedWithStuff() {
final String _sql = "SELECT * FROM Car_Body_Confiscations";
final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
....
Later Nn (getting the CarBody(s) there will only be 1)
StringBuilder _stringBuilder = StringUtil.newStringBuilder();
_stringBuilder.append("SELECT `id`,`registrationNumber`,`datetime`,`userId`,`testId` FROM `CarBody` WHERE `id` IN (");
final int _inputSize = _map.size();
Even Later On (Areas)
StringBuilder _stringBuilder = StringUtil.newStringBuilder();
_stringBuilder.append("SELECT `id`,`name` FROM `Area` WHERE `id` IN (");
Now if you want to code your own JOINS etc and alias columns then you will have to consider a few things.
The receiving class MUST be able to be built from the result set and thus column names MUST match the fields in the POJO (unless using #Prefix annotation).
You also need to be aware that the result set will be the cartesian product, thus in the case of doing the above, bypassing how Room does it, the for each combination/permutation of confiscation/carbody/area you get a row (unless grouped/excluded by where clause). So if you have 1 confiscation joined to 1 car but with 10 areas then you would get 10 rows all with the same confiscation and carbody.
You may wish to consider having a look at Room #Relation annotation with a One To Many relationship. Which explains this a little more and includes an example of using a JOINs
Additional - User and TestLists
You may well want to include the CarBody's User and the Test_Lists so you have a result with all of the related data.
This needs to be looked at from a hierarchical perspective. That is the confiscation has a direct link/reference/map to the CarBody but underneath that are the links/references/mappings to the User from the CarBody and to the Test_Lists.
So to incorporate this you need a POJO for a CarBody with it's User and it's Test_Lists. So, for example:-
data class CarBodyWithUserAndWithTestList(
#Embedded
var carBody: CarBody,
#Relation(
entity = Users::class,
parentColumn = "userId",
entityColumn = "id"
)
var users: Users,
#Relation(
entity = Test_List::class,
parentColumn = "testId",
entityColumn = "id"
)
var testList: List<Test_List>
)
With this you can then amend the LastConfiscats to include a CarBodyWithUserAndWithTestList instead of just a CarBody e.g.:
data class LastConfiscats(
#Embedded
var carBodyConfiscations: Car_Body_Confiscations,
#Relation(entity = CarBody::class, parentColumn = "car_body_id", entityColumn = "id")
//var carBody: CarBody, /* REMOVED */
var carBodyWithUserAndWithTestList: CarBodyWithUserAndWithTestList, /* ADDED */
#Relation(entity = Area::class, parentColumn = "areaId", entityColumn = "id")
var area: List<Area>
)
Note that the #Relation has the CarBody class as the entity. That is because the CarBody is the class that needs to be inspected in order for Room to ascertain the columns used for the links/references/,mappings.
*Working Example/Demo
Here's the entire code for a Working example that inserts some data into all the tables and then extracts the data using the getCardBodyJoinedWithStuff query, it then writes the data to the Log.
the code includes ForeignKey constraints which enforces and helps to maintain referential integrity.
for id's Long rather than Int has been used as Long properly reflects the potential size of the field/value.
autoGenerate = true has not been used as this is inefficient and not needed see https://sqlite.org/autoinc.html, which includes as the very first statement The AUTOINCREMENT keyword imposes extra CPU, memory, disk space, and disk I/O overhead and should be avoided if not strictly needed. It is usually not needed. (autoGenerate = true results in AUTOINCREMENT)
So all the classes/interfaces :-
#Entity(
foreignKeys = [
ForeignKey(
Users::class,
parentColumns = ["id"],
childColumns = ["userId"],
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE
),
ForeignKey(
Test_List::class,
parentColumns = ["id"],
childColumns = ["testId"],
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE
)
]
)
data class CarBody(
#PrimaryKey
var id: Long?=null,
var registrationNumber: Int,
var datetime: String,
#ColumnInfo(index = true)
var userId: Long,
#ColumnInfo(index = true)
var testId: Long
)
#Entity
data class Users(
#PrimaryKey
var id:Long?=null,
var name: String,
var lastName: String,
var email: String,
var password: String
)
#Entity
data class Test_List(
#PrimaryKey
var id: Long?=null,
var date: String,
var is_saved: Boolean
)
#Entity(
foreignKeys = [
ForeignKey(
entity = CarBody::class,
parentColumns = ["id"],
childColumns = ["car_body_id"],
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE
),
ForeignKey(
entity = Confiscation::class,
parentColumns = ["id"],
childColumns = ["confiscation_id"],
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE
),
ForeignKey(
entity = Area::class,
parentColumns = ["id"],
childColumns = ["areaId"],
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE
)
]
)
data class Car_Body_Confiscations(
#PrimaryKey
var id: Long?=null,
#ColumnInfo(index = true)
var car_body_id: Long,
#ColumnInfo(index = true)
var confiscation_id: Long,
#ColumnInfo(index = true)
var areaId: Long
)
#Entity
data class Area(
#PrimaryKey
var id: Long?=null,
var name: String
)
#Entity
data class Confiscation(
#PrimaryKey
var id: Long?=null,
var name: String
)
#Dao
interface AllDao {
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(area: Area): Long
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(carBodyConfiscations: Car_Body_Confiscations): Long
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(carBody: CarBody): Long
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(confiscation: Confiscation): Long
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(users: Users): Long
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(testList: Test_List): Long
#Transaction
#Query("SELECT * FROM Car_Body_Confiscations")
fun getCardBodyJoinedWithStuff(): List<LastConfiscats>
}
#Database(entities = [
Area::class,
Car_Body_Confiscations::class,
CarBody::class,
Confiscation::class,
Users::class,
Test_List::class
],
exportSchema = false, version = 1)
abstract class TheDatabase: RoomDatabase() {
abstract fun getAllDao(): AllDao
companion object {
private var instance: TheDatabase?=null
fun getInstance(context: Context): TheDatabase {
if (instance==null) {
instance = Room.databaseBuilder(context,TheDatabase::class.java,"the_database.db")
.allowMainThreadQueries()
.build()
}
return instance as TheDatabase
}
}
}
data class LastConfiscats(
#Embedded
var carBodyConfiscations: Car_Body_Confiscations,
#Relation(entity = Confiscation::class, parentColumn = "confiscation_id", entityColumn = "id")
var confiscation: Confiscation,
#Relation(entity = CarBody::class, parentColumn = "car_body_id", entityColumn = "id")
//var carBody: CarBody, /* REMOVED */
var carBodyWithUserAndWithTestList: CarBodyWithUserAndWithTestList, /* ADDED */
#Relation(entity = Area::class, parentColumn = "areaId", entityColumn = "id")
var area: List<Area>
)
data class CarBodyWithUserAndWithTestList(
#Embedded
var carBody: CarBody,
#Relation(
entity = Users::class,
parentColumn = "userId",
entityColumn = "id"
)
var users: Users,
#Relation(
entity = Test_List::class,
parentColumn = "testId",
entityColumn = "id"
)
var testList: List<Test_List>
)
The following activity code (note that main thread used for brevity and convenience):-
class MainActivity : AppCompatActivity() {
lateinit var db: TheDatabase
lateinit var dao: AllDao
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
db = TheDatabase.getInstance(this)
dao = db.getAllDao()
dao.insert(Users(100,"Fred","Bloggs","fredBloggs#mail.com","password"))
dao.insert(Users(200,"Jane","Doe","janeDoe#email.org","password"))
/* example where id is autogenerated */
val marySmithId = dao.insert(Users(name = "Mary", lastName = "Smith", email = "marysmith#mailit.co.uk", password = "1234567890"))
dao.insert(Test_List(1,"2022-01-01",false))
dao.insert(Test_List(2,"2022-02-02",true))
dao.insert(CarBody(1000,1234,"2022-01-01",100 /* Fred Bloggs*/,2 ))
dao.insert(CarBody(2000,4321,"2021-12-05",100,1))
dao.insert(CarBody(3000,1111,"2021-09-10",200,2))
dao.insert(Area(100,"Area100"))
dao.insert(Area(200,"Area200"))
dao.insert(Area(300,"Area300"))
dao.insert(Area(400,"Area400"))
dao.insert(Confiscation(901,"C1"))
dao.insert(Confiscation(902,"C2"))
dao.insert(Confiscation(903,"C3"))
dao.insert(Confiscation(904,"C4"))
dao.insert(Car_Body_Confiscations(500,1000,901,100))
dao.insert(Car_Body_Confiscations(510,2000,904,400))
dao.insert(Car_Body_Confiscations(520,3000,902,300))
/* Extract the data and output to the Log */
for(cbc in dao.getCardBodyJoinedWithStuff()) {
val areaList = StringBuilder()
for (a in cbc.area) {
areaList.append("\n\t\tArea is ${a.name} ID is ${a.id}")
}
val testList = StringBuilder()
testList.append("\n\t\tThere are ${cbc.carBodyWithUserAndWithTestList.testList.size} TestLists, they are:")
for (t in cbc.carBodyWithUserAndWithTestList.testList) {
testList.append("\n\t\t\t${t.date} Save is ${t.is_saved} ID is ${t.id}")
}
Log.d(
"DBINFO",
"CBC ID =${cbc.carBodyConfiscations.id}" +
"\n\tConfiscation Name is ${cbc.confiscation.name}" +
"\n\tAreas (there is/are ${cbc.area.size}) they are $areaList}" +
"\n\tCarBody Reg is ${cbc.carBodyWithUserAndWithTestList.carBody.registrationNumber} " +
"Date is ${cbc.carBodyWithUserAndWithTestList.carBody.datetime}" +
"\n\t\tUser is ${cbc.carBodyWithUserAndWithTestList.users.name}" +
",${cbc.carBodyWithUserAndWithTestList.users.lastName} " +
"email is ${cbc.carBodyWithUserAndWithTestList.users.email}" +
"$testList"
)
}
}
}
Result
The Log after running:-
D/DBINFO: CBC ID =500
Confiscation Name is C1
Areas (there is/are 1) they are
Area is Area100 ID is 100}
CarBody Reg is 1234 Date is 2022-01-01
User is Fred,Bloggs email is fredBloggs#mail.com
There are 1 TestLists, they are:
2022-02-02 Save is true ID is 2
D/DBINFO: CBC ID =510
Confiscation Name is C4
Areas (there is/are 1) they are
Area is Area400 ID is 400}
CarBody Reg is 4321 Date is 2021-12-05
User is Fred,Bloggs email is fredBloggs#mail.com
There are 1 TestLists, they are:
2022-01-01 Save is false ID is 1
D/DBINFO: CBC ID =520
Confiscation Name is C2
Areas (there is/are 1) they are
Area is Area300 ID is 300}
CarBody Reg is 1111 Date is 2021-09-10
User is Jane,Doe email is janeDoe#email.org
There are 1 TestLists, they are:
2022-02-02 Save is true ID is 2
Re the Comment
I actually have a Cartesian product, I had to process it somehow, although I do not know how yet.
You may find that the above is fine and processes the product pretty easily.
Where Room's relationship handling can become restrictive is if you want to selectively retrieve related data. The way Room handles #Relation means that it retrieves ALL children irrespective of any JOINS and WHERE clauses. They are only effective if they affect the result of the topmost parent.
In your case, where you don't actually cater for lists (such as multiple users per carbody) then Room should suffice.
The original Query - revisited
Changing your query a little to (largely to suit the previous classes ) to:-
#Query("SELECT " +
"registrationNumber, " +
"area.[name] AS area_name, " +
"confiscation.[name] AS confiscation_name " +
"FROM carbody, car_body_confiscations " +
"INNER JOIN area ON car_body_confiscations.areaId == area.id " +
"INNER JOIN confiscation ON car_body_confiscations.confiscation_id == confiscation.id " +
"WHERE carbody.id == car_body_confiscations.car_body_id " +
"ORDER BY carbody.id DESC " +
"LIMIT :row_count"
)
fun getLastConfiscats(row_count: Int): /*LiveData<*/List<MyQueryPOJO>/*>*/
see the following re MyQueryPOJO
And adding a suitable class (no #Embeddeds or #Relations needed, so Room doesn't get confused with column names) :-
data class MyQueryPOJO(
/* The output columns of the query */
var registrationNumber: Int,
#ColumnInfo(name = "area_name")
var not_the_area_name: String,
var confiscation_name: String
)
note how the not_the_area_name field has the #ColumnInfo annotation to tell it to use the area_name output column
In the activity, using:-
for (mqo in dao.getLastConfiscats(10)) {
Log.d("DBINFO","Reg = ${mqo.registrationNumber} Confiscation = ${mqo.confiscation_name} Area Name = ${mqo.not_the_area_name}")
}
Results in (with the same data) :-
D/DBINFO: Reg = 1111 Confiscation = C2 Area Name = Area300
D/DBINFO: Reg = 4321 Confiscation = C4 Area Name = Area400
D/DBINFO: Reg = 1234 Confiscation = C1 Area Name = Area100
as the relationships all all basically 1-1 (the references are back to front for a 1-many) the cartesian product is fine as there will not be any duplicates.
I'm using subselect in hibernate to return an object that contains id on all related table instead of object.
this the dto that I defined
#Entity
#Subselect("select di.id as id, user.id as userId, client.id as clientId, controller.id as controllerId,"
+ "supplier.id as supplierId, grade.id as gradeId, packing.id as packingId, warehouse.id as warehouseId,"
+ "qualityController.id as qualityControllerId,"
+ "companyMasterByPledger.id as pledgerId,"
+ "di.refNumber as refNumber,"
+ "di.clientRef as clientRef,"
+ "di.date as date,"
+ "di.supplierRef as supplierRef,"
+ "di.tons as tons,"
+ "di.kgPerBag as kgPerBag,"
+ "di.noOfBags as noOfBags,"
+ "di.deliveryDate as deliveryDate,"
+ "di.fromTime as fromTime,"
+ "di.toTime as toTime,"
+ "di.markingOnBags as markingOnBags,"
+ "di.originId as originId,"
+ "di.qualityId as qualityId,"
+ "di.remarks as remarks,"
+ "di.status as status,"
+ "di.log as log "
+ "from DeliveryInstruction as di "
+ "left join di.user as user "
+ "left join di.companyMasterByClientId as client "
+ "left join di.companyMasterByWeightControllerId as controller "
+ "left join di.companyMasterBySupplierId as supplier "
+ "left join di.gradeMaster as grade "
+ "left join di.packingMaster as packing "
+ "left join di.companyMasterByQualityControllerId as qualityController "
+ "left join di.companyMasterByPledger as pledger "
+ "left join di.warehouse as warehouse")
#Synchronize({"DeliveryInstruction"})
public class DeliveryView implements Serializable{
private Integer id;
private Integer userId;
private Integer clientId;
private Integer controllerId;
private Integer supplierId;
private Integer gradeId;
private Integer packingId;
private Integer warehouseId;
private Integer qualityControllerId;
private Integer pledgerId;
private String refNumber;
private String clientRef;
private Date date;
private String supplierRef;
private Double tons;
private Float kgPerBag;
private Integer noOfBags;
private Date deliveryDate;
private String fromTime;
private String toTime;
private String markingOnBags;
private Integer originId;
private Integer qualityId;
private String remark;
private Byte status;
private String log;
public DeliveryView() {
}
public DeliveryView(Integer id, Integer userId, Integer clientId, Integer controllerId, Integer supplierId, Integer gradeId, Integer packingId, Integer warehouseId, Integer qualityControllerId, Integer pledgerId, String refNumber, String clientRef, Date date, String supplierRef, Double tons, Float kgPerBag, Integer noOfBags, Date deliveryDate, String fromTime, String toTime, String markingOnBags, Integer originId, Integer qualityId, String remark, Byte status, String log) {
this.id = id;
this.userId = userId;
this.clientId = clientId;
this.controllerId = controllerId;
this.supplierId = supplierId;
this.gradeId = gradeId;
this.packingId = packingId;
this.warehouseId = warehouseId;
this.qualityControllerId = qualityControllerId;
this.pledgerId = pledgerId;
this.refNumber = refNumber;
this.clientRef = clientRef;
this.date = date;
this.supplierRef = supplierRef;
this.tons = tons;
this.kgPerBag = kgPerBag;
this.noOfBags = noOfBags;
this.deliveryDate = deliveryDate;
this.fromTime = fromTime;
this.toTime = toTime;
this.markingOnBags = markingOnBags;
this.originId = originId;
this.qualityId = qualityId;
this.remark = remark;
this.status = status;
this.log = log;
}
#Id
public Integer getId() {
return id;
}
// ... others getter and setter
}
and in the DAO class, the method looks like below
public DeliveryView getDiById(int id) {
return (DeliveryView) getHibernateTemplate().get(DeliveryView.class, id);
}
However when I use the above method, it returned null.
When I run the method, the script that is printed to the console is
select deliveryvi0_.id as id36_0_, deliveryvi0_.clientId as clientId36_0_, deliveryvi0_.clientRef as clientRef36_0_, deliveryvi0_.controllerId as controll4_36_0_, deliveryvi0_.date as date36_0_, deliveryvi0_.deliveryDate as delivery6_36_0_, deliveryvi0_.fromTime as fromTime36_0_, deliveryvi0_.gradeId as gradeId36_0_, deliveryvi0_.kgPerBag as kgPerBag36_0_, deliveryvi0_.log as log36_0_, deliveryvi0_.markingOnBags as marking11_36_0_, deliveryvi0_.noOfBags as noOfBags36_0_, deliveryvi0_.originId as originId36_0_, deliveryvi0_.packingId as packingId36_0_, deliveryvi0_.pledgerId as pledgerId36_0_, deliveryvi0_.qualityControllerId as quality16_36_0_, deliveryvi0_.qualityId as qualityId36_0_, deliveryvi0_.refNumber as refNumber36_0_, deliveryvi0_.remark as remark36_0_, deliveryvi0_.status as status36_0_, deliveryvi0_.supplierId as supplierId36_0_, deliveryvi0_.supplierRef as supplie22_36_0_, deliveryvi0_.toTime as toTime36_0_, deliveryvi0_.tons as tons36_0_, deliveryvi0_.userId as userId36_0_, deliveryvi0_.warehouseId as warehou26_36_0_ from DeliveryView deliveryvi0_ where deliveryvi0_.id=?
it gets the data from DeliveryView table which does not exist, what I want is to get the data from DeliveryInstruction table. Please help me to correct it, thanks
P/s: I'm using spring and hibernate and I do this way to work with jackson to prevent it load a lot of redundant information (I used jackson hibernate module, but it returned a lot of unneccessary information, instead of only id). So if you have any better idea, please tell me, thanks.
Update: I saw that the script on #subselect didn't run, it execute the default script "select * from deliveryView" when I call "get(DeliveryView.class, id)" method.
Update: This my native script that I've checked
select di.id as id, user.id as userId, client.id as clientId, controller.id as controllerId,
supplier.id as supplierId, grade.id as gradeId, packing.id as packingId, warehouse.id as warehouseId,
qualityController.id as qualityControllerId,
pledger.id as pledgerId,
di.ref_number as refNumber,
di.client_ref as clientRef,
di.date as date,
di.supplier_ref as supplierRef,
di.tons as tons,
di.kg_per_bag as kgPerBag,
di.no_of_bags as noOfBags,
di.delivery_date as deliveryDate,
di.from_time as fromTime,
di.to_time as toTime,
di.marking_on_bags as markingOnBags,
di.origin_id as originId,
di.quality_id as qualityId,
di.remark as remarks,
di.status as status,
di.log as log
from delivery_instruction di
left join user on user.id = di.user_id
left join company_master client on client.id = di.client_id
left join company_master controller on controller.id = di.weight_controller_id
left join company_master supplier on supplier.id = di.supplier_id
left join grade_master grade on grade.id = di.grade_id
left join packing_master packing on packing.id = di.packing_id
left join company_master qualityController on qualityController.id = di.quality_controller_id
left join company_master pledger on pledger.id = di.pledger
left join warehouse on warehouse.id = di.warehouse_id
where di.id = 21
1 Create simple class(without annotations) that holds all properties you need with getters and setters
2 Execute native sql:
public DeliveryView getDiById(int id) {
DeliveryView dV = (DeliveryView) sessionFactory.getCurrentSession()
.createSQLQuery(yourQueryHere).setResultTransformer(
new AliasToBeanResultTransformer(DeliveryView.class)).uniqueResult();
return dV;
}
I need to join two objects (tables) A and B. For any A there can be zero to many B's. The query needs the return one row per A.
The B's I want to order before the join to be able to select the needed row from B's following a certain condition. Say B has a column Type. If there is a Type 1 then that's the B I need, if not: Type 2 must be selected, etc.
Now I think about it, I am not sure how I would to this even in T-sql.
I think something like this:
SELECT A.*
FROM A LEFT JOIN (
SELECT * FROM B AS B1 WHERE B1.Type = (SELECT TOP 1 B2.Type FROM B AS B2
WHERE B2.JoinID = B1.JoinID
ORDER BY B2.Type )
) AS B ON B.JoinID = A.JoinID
[edit]
With the answer of sgtz I've tried to make it work. If have to make an additional step because the field I want to order by is not present. I add this field in step 1, in step 2 I make a selection of the keys and join everything in step 3, but there I receive an error "The type of one of the expressions in the join clause is incorrect. Type inference failed in the call to 'GroupJoin'." on join "join a in adressen1 on new { b.TopRelatieID..."
var adressen1 = from a in db.Adres
select new
{
RelatieAdres = a,
Sortering = (int)(a.AdresType.Code == codeVestAdres ?
1 : a.AdresType.Code == codePostAdres ?
2 : (100 + (int)a.AdresType.Code.ToCharArray()[0]))
};
var adressen2 = from b in adressen1
group b by new { RelatieID = b.RelatieAdres.RelatieID } into p
let TopAdresType = p.Min(at => at.Sortering)
select new { TopRelatieID = p.Key.RelatieID, TopAdresType };
var q = from k in db.Klants
join b in adressen2 on k.RelatieID equals b.TopRelatieID into b_join
from b in b_join.DefaultIfEmpty()
join a in adressen1 on new { b.TopRelatieID, b.TopAdresType } equals new { a.RelatieAdres.RelatieID, a.Sortering } into a_join
from a in a_join.DefaultIfEmpty()
Here's a worked example. I did it two stages.
[Test]
public void Test333()
{
List<Order> O;
var M = Prepare333Data(out O);
var OTop = from o in O
group o by new {id=o.id, orderid=o.orderid}
into p
let topType = p.Min(tt => tt.type)
select new Order(p.Key.id, p.Key.orderid, topType);
var ljoin = from m in M
join t in OTop on m.id equals t.id into ts
from u in ts.DefaultIfEmpty()
select new {u.id, u.orderid, u.type};
}
public class Manufacturer
{
public Manufacturer(int id, string name)
{
this.id = id;
this.name = name;
}
public int id { get; set; }
public string name { get; set; }
}
public class Order
{
public Order(int id, int orderid, int type)
{
this.orderid = orderid;
this.id = id;
this.type = type;
}
public int orderid { get; set; }
public int id { get; set; }
public int type { get; set; }
}
private List<Manufacturer> Prepare333Data(out List<Order> O)
{
var M = new List<Manufacturer>() {new Manufacturer(1, "Abc"), new Manufacturer(2, "Def")};
O = new List<Order>()
{
new Order(1, 1, 2),
new Order(1, 2, 2),
new Order(1, 2, 3),
new Order(2, 3, 1)
,
new Order(2, 3, 1)
,
new Order(2, 3, 2)
};
return M;
}
response to comments:
your "new {" creates a new anonymous type. Two anonymous types created by difference processes are said to have the same signature if types are declared in the same order and they have the same type definition (i.e. int matches int, not int matches short). I haven't tested this scenario extensively in LINQ.
That's why I worked with real concrete classes, and not anon types within the JOIN portion. There's probably a way to rework it with pure LINQ, but I don't know what that is yet. I'll post you a response if it occurs to me okay.
I'd suggest using concrete classes too for now.
i.e. instead of
*new {*
when doing joins, always use
*new CLASSNAME(){prop1="abc",prop2="123"*
It's a little bit longer, but safer... safer at least until we work out what is going on inside the LINQ internals.
To be meaningful, you should add at least something to query result, not only A.*. Otherwise you'll have a copy of A with some rows possibly duplicated. If I understood the question correctly, this SQL query should work:
SELECT DISTINCT A.*, B.Type
FROM A LEFT JOIN
(SELECT TOP (1) JoinID, Type
FROM B
ORDER BY Type
GROUP BY JoinID, Type
) AS B ON A.JoinID = B.JoinID
Translated to LINQ, it is (UPDATED)
(from a in As
join b in
(from b1 in Bs
orderby b1.Type
group b1 by b1.JoinID into B1
from b11 in B1
group b11 by b11.Type into B11
from b111 in B11
select new { b111.JoinID, b111.Type }).Take(1)
on a.JoinID equals b.JoinID into a_b
from ab in a_b.DefaultIfEmpty()
select new { a_b.JoinID, /*all other a properties*/ a_b.Type }).Distinct()
LINQ may not work 100% correct, but you should grab the idea.
SELECT StudentHistoryId, StudentId, Grade, CreatedAt, ModifiedAt, ModifiedBy, Active
FROM TABLENAME TN
INNER JOIN ( SELECT StudentId, MAX(ModifiedAt) AS ModifiedAt FROM TABLENAME GROUP BY StudentId) M
ON TN.StudentId = M.StudentId AND TN.ModifiedAt = M.ModifiedAt
Here's a direct translaton:
var subquery = from tn in dc.TABLENAME
group tn by tn.StudentId into g
select new { StudentId = g.Key, ModifiedAt = g.Max(x => x.ModifiedAt) };
var query = from tn in dc.TABLENAME
join m in subquery
on new { tn.StudentId, tn.ModifiedAt }
equals new { m.StudentId, m.ModifiedAt }
select new
{
tn.StudentHistoryId,
tn.StudentId,
tn.Grade,
tn.CreatedAt,
tn.ModifiedAt,
tn.ModifiedBy,
tn.Active
};
I have sql as below
SELECT Q.MaterialID AS MaterialID, Q.ProductID AS ProductID, QB.Quantity AS Quantity,
Q.ParameterID AS ParameterID, SUM((Q.ParameterValue * Q.Quantity)/Q.TotalTonnes) AS ParameterValue
FROM #Quality Q
INNER JOIN #QuantityBreakdown QB
ON ((Q.MaterialID = QB.MaterialID) OR (Q.MaterialID IS NULL AND QB.MaterialID IS NULL))
AND ((Q.ProductID = QB.ProductID) OR (Q.ProductID IS NULL AND QB.ProductID IS NULL))
GROUP BY Q.MaterialID, Q.ProductID, ParameterID, QB.Quantity
conversion to LINQ..... struck at ???
var enumerable = from final in (from q in qualities
from qb in quantityBreakDowns
where q.ProductID == qb.ProductID && q.MaterialID == qb.MaterialID
select new
{
q.MaterialID,
q.ProductID,
q.ParameterID,
qb.Quantity,
ParameterValue = ((q.ProductID*q.Quantity)/q.TotalTonnes)
}
)
group final by new
{
final.MaterialID,
final.ProductID,
final.ParameterID,
???
}
into finalresult select finalresult;
Is there some other good way to do this.
Thanks
Ok solved this as:
from final in
(from q in qualities
from qb in quantityBreakDowns
where q.ProductID == qb.ProductID && q.MaterialID == qb.MaterialID
select new
{
q.MaterialID,
q.ProductID,
q.ParameterID,
qb.Quantity,
ParameterValue = ((q.ActualValue*q.Quantity)/q.TotalTonnes)
}
)
group final by new
{
final.MaterialID,
final.ProductID,
final.ParameterID,
final.Quantity
}
into finalresult
select new
{
finalresult.Key.MaterialID,
finalresult.Key.ProductID,
finalresult.Key.ParameterID,
finalresult.Key.Quantity,
ActualValue = finalresult.Sum(fq => fq.ParameterValue)
};