Can not join tables with LINQ - linq

I will glad if you help me
I have 4 tables:
http://i.stack.imgur.com/MMIdn.png
I need to get a ApptNumber from Condo and ContractID from Contract
If no contracts assigned to the condo, leave it blank.
Like this:
| Cnds | Cntrcts |
|--------|-----------|
| 101 | contr1 |
| 102 | contr2 |
| 103 | contr3 |
| 104 | |
| 105 | |
| 106 | contr4 |
| 107 | |
|--------|-----------|
I tried to do it like this, but failed to get the right result
var q =
from c in condos
join b in buildings on c.buildingID equals b.BuildingId
join t in tenants on b.BuildingId equals t.buildingID
join ct in contracts on t.TenantID equals ct.tenantID into result
from subresult in result.DefaultIfEmpty()
select new
{
c.apptNumber,
contract = (subresult == null ? 0 : subresult.ContractID)
};
Sincerely

from c in context.Condos
join ct in context.Contracts on c.apptNumber equals ct.SuiteNo into j
//it could be another field for joining
from d in j.DefaultIfEmpty()
select new {c.apptNumber, Contract = d != null ? d.ContractId : null}

Thank you guys. It was my fault to use multiple JOINS in first place.
I resolved the issue like that:
var q =
from c in condos
join ct in contracts on c.apptNumber equals ct.SuiteNo into j //it could be another field
from d in j.DefaultIfEmpty()
select new { c.apptNumber, Contract = d != null ? d.ContractID : 0 };
var q2 = q.GroupBy(c => c.apptNumber).Select(group => group.First());

Related

How to select based on child of child relationship using Laravel Eloquent?

I have the following tables structure for merchants & dynamic filters
Merchant Table
id | name
-------------------------
1 | Food Supplier
Filters
id | name
-------------------------
1 | Service Area
2 | Service Cost
Filter Values
id | filter_id | value
-------------------------
1 | 1 | Africa
2 | 1 | Europe
3 | 2 | 2000-4000
4 | 2 | 4000-6000
Merchant Filter Values
id | filter_id | value_id | merchant_id
----------------------------------------
1 | 1 | 1 | 1
2 | 1 | 2 | 1
3 | 2 | 4 | 1
I run the following query to search through merchants using filters:
select *
from `merchants`
where `name` LIKE "%search_term%"
and exists (
select *
from `filter_values`
inner join `merchant_filter_values`
on `filter_values`.`id` = `merchant_filter_values`.`value_id`
where `merchants`.`id` = `merchant_filter_values`.`merchant_id`
and (`filter_values`.`id` = 1 and filter_values.filter_id = 2)
or (`filter_values`.`id` = 2 and filter_values.filter_id = 3)
or etc.
)
The query works fine, and returns results as expected, the only problem that it is not eloquent so it doesn't return appends variables/functions from the model for example that featured_img_url is not returned in the result:
protected $appends = array('featured_img_url');
public function getFeaturedImgUrlAttribute()
{
$img_path = Config::get('app.asset_url') . "/merchants/featured/" . $this->id . ".";
return $img_path . $this->featured_image_ext . $this->getLastUpdatedUrl();
}
The question is, how do I write such query using laravel eloquent?
You have to use hydrate() function to convert result of select query to eloquent collection. you can use something like this:
$merchants = \DB::select( "select *
from `merchants`
where `name` LIKE '%search_term%'
and exists (
select *
from `filter_values`
inner join `merchant_filter_values`
on `filter_values`.`id` = `merchant_filter_values`.`value_id`
where `merchants`.`id` = `merchant_filter_values`.`merchant_id`
and (`filter_values`.`id` = 1 and filter_values.filter_id = 2)
or (`filter_values`.`id` = 2 and filter_values.filter_id = 3)
or etc.
)" );
return Merchant::hydrate($merchants);
You have to define Merchant Model too.

Use UNION with DISTINCT in Laravel Eloquent query builder with relations

I have 2 tables,
properties
+----+-----------+
| id | parent_id |
+----+-----------+
| 1 | null |
| 2 | 1 |
| 3 | null |
| 4 | 3 |
| 5 | 3 |
| 6 | null |
+----+-----------+
and
sale_services
+----+-------------+------+
| id | property_id | rank |
+----+-------------+------+
| 1 | 2 | 5 |
| 2 | 4 | 4 |
| 3 | 5 | 6 |
| 4 | 6 | 7 |
+----+-------------+------+
and corresponding Eloquent Models (SaleService and Property) linked to each other by relation (sale_service.property_id = property.id). Property can be linked to another Property in the same table.
I need to get a collection of SaleService instances, where related property.parent_id is null OR if there are some records in sale_services table sharing same parent_id through properties table, distincted by that field and order by rank.
a result should be
+----+-------------+------+
| id | property_id | rank |
+----+-------------+------+
| 1 | 2 | 5 |
| 3 | 5 | 6 |
| 4 | 6 | 7 |
+----+-------------+------+
- all items from sale_services table except (sale_service.id = 2), because it's property shares same parent_id with item (sale_service.id = 3) and item with (sale_service.id = 3) has highest rank value
I figured out SQL code to get desired result,
SELECT *
FROM
(SELECT DISTINCT ON (properties.parent_id) *
FROM "sale_services"
INNER JOIN "properties" ON "sale_services"."property_id" = "properties"."id"
WHERE ("properties"."parent_id") IS NOT NULL
ORDER BY "properties"."parent_id", "sale_services"."rank" DESC) AS sub
UNION
SELECT *
FROM "sale_services"
INNER JOIN "properties" ON "sale_services"."property_id" = "properties"."id"
WHERE ("properties"."parent_id") IS NULL
but I cant reach same with Eloquent Builder.
I tried something like this
$queryWithParent = SaleService::query()
->select(\DB::raw('DISTINCT ON (properties.parent_id) *'))
->whereNotNull('properties.parent_id')
->join('properties', 'sale_services.property_id', '=', 'properties.id')
->orderBy('parent_id')
->orderBy('sale_services.index_range', 'desc');
$queryWithoutParent = SaleService::query()
->join('properties', 'sale_services.property_id', '=', 'properties.id')
->whereNull('properties.parent_id');
$query = $queryWithParent->union($queryWithoutParent);
but got an error
SQLSTATE[42601]: Syntax error: 7 ERROR: syntax error at or near "union" LINE 1: ...perties.type <> 'hotel') order by "parent_id" asc union sele... ^ (SQL: select DISTINCT ON (properties.parent_id) * from "sale_services" inner join "properties" on "sale_services"."property_id" = "properties"."id" where ("properties"."parent_id") is not null and ("sale_services"."deleted_at") is null and "published" = 1 and exists (select 1 from "properties" where properties.id = sale_services.property_id AND properties.type <> 'hotel') order by "parent_id" asc union select * from "sale_services" inner join "properties" on "sale_services"."property_id" = "properties"."id" where ("properties"."parent_id") is null and ("sale_services"."deleted_at") is null and "published" = 1 and exists (select 1 from "properties" where properties.id = sale_services.property_id AND properties.type <> 'hotel') order by "index_range" desc limit 12 offset 0)
If I remove ordering from first query ($queryWithParent) it seems working, but with random item selected in distinct query.
Is there any other way to achieve same result, or what I'm doing wrong?
The laravel query creator for "Distinct" is distinct() as in :
$queryWithParent = SaleService::query()
->distinct('properties.parent_id')
->whereNotNull('properties.parent_id')
->join('properties', 'sale_services.property_id', '=', 'properties.id')
->orderBy('parent_id')
->orderBy('sale_services.index_range', 'desc');
Does that work ?
Finally found a solution.
Use Laravel ^5.7.28 (The UNION issue has been fixed in Laravel 5.7.28: github.com/laravel/framework/pull/27589) and you will not need a subquery!
Specify table columns to select to avoid column name collision sale_services.*
$queryWithParent = SaleService::query()
->select(\DB::raw('DISTINCT ON (properties.parent_id) sale_services.*'))
->from('sale_services')
->join('properties', 'sale_services.property_id', '=', 'properties.id')
->whereNotNull('properties.parent_id')
->orderBy('properties.parent_id')
->orderBy('sale_services.index_range', 'desc');
$queryWithoutParent = SaleService::query()
->select(\DB::raw('sale_services.*'))
->join('properties', 'sale_services.property_id', '=', 'properties.id')
->whereNull('properties.parent_id');
$query = $queryWithParent->union($queryWithoutParent);

How to get rows by LINQ

Everybody.
I have following data.
DATE|COLUMN1|COLUMN2|VALUE
--------------------------
1 |A |D |100
--------------------------
2 |A |D |200
--------------------------
1 |B |D |150
I need get rows where DATE does have max value. Like that
DATE|COLUMN1|COLUMN2|VALUE
--------------------------
2 |A |D |50
--------------------------
1 |B |D |150
Sorry for my bad English.
As I can understand, you need max date for every group of column1 -
for that you can go with
var custsLastAccess = from c in db.data //data is your table
group c by c.COLUMN1 into grp
select grp.OrderByDescending(c => c.DATE).FirstOrDefault();
You can first select the max from the LINQ and then select by this max:
var max = rows.Max(a => a.DATE);
var result = rows.Where(a => a.DATE == max).ToList();
Simplier:
var result = rows.Where(a => a.DATE == rows.Max(b => b.DATE)).ToList();
Optimizer/compiler should optimize the query above.
Expression:
var result = from r in rows
where r.DATE == rows.Max(a => a.DATE)
select r;
I found solve. The following code decided mine task.
public override IQueryable<ENTITY> Get()
{
return base.Get().GroupBy(x => new { x.COLUMN1, x.COLUMN2 }, (key, group) => new
{
row = group.OrderByDescending(x => x.DATE).FirstOrDefault()
}).Select(x => x.row);
}

How to write a left join with many to many relationship

I have the following entities:
Entity: Department
DepartmentId (int)
Name (int)
SuperiorDepartmentId (int, foreign key to department entity)
DepartmentPermissions (ICollection)
Entity: DepartmentPermission
DepartmentId (int)
UserId (int)
Permission (String)
Departments (ICollection)
Entity: User
UserId (int)
Name (string)
DepartmentPermissions (ICollection)
I need to return in my query all of the departments (including the ones where the user don't have permission) and the name of the permission when the user has any.
Department
DepartmentId | Name | SuperiorDepartmentId
1 | Sales | null
2 | Internal Sales | 1
3 | Marketing | null
DepartmentPermissions
DepartmentId | User Id | Permission
1 | 2 | r
2 | 2 | rw
1 | 3 | rw
User
UserId | Name
1 | John
2 | Mary
3 | Paul
If I ask for the data for user Mary (id=2), we shoud have as result set:
DepartmentId | Name | SuperiorDepartmentId | Permission
1 | Sales | null | r
2 | Internal Sales | 1 | rw
3 | Marketing | null | null
How can I do this?
I'm going to presume the existence of a navigation property Department.DepartmentPermissions.
var query = from d in context.Departments
select new {
Department = d,
Permissions = d.DepartmentPermissions
.Where(dp => dp.UserId == 2)
.Select(p => p.Permission)
};
var result = query.AsEnumerable()
.Select(x =>
new {
x.Department.DepartmentId,
x.Department.Name,
x.Department.SuperiorDepartmentId,
Permissions =
string.Join(", ", x.Permissions.DefaultIfEmpty("null")
}
First the raw data are collected (query), then the final results are composed in memory. The latter is done because EF won't allow string.Join in a LINQ query. Your data structure allows for more than one permission per user in one department, hence the string.Join.
If you're absolutely sure that there will always be one permission you can do
Permission = d.DepartmentPermissions.Where(dp => dp.UserId == 2)
.Select(p => p.Permission)
.FirstOrDefault()
in query, and your done.

Select nested list

I have three tables in DB like this:
products
+----+------+
| id | name |
+----+------+
| 1 | prod1|
+----+------+
values
+----+---------+---------+
| id | value | alias |
+----+---------+---------+
| 1 | 10 g | 10m |
+----+---------+---------+
prdoucts_values
+---------------+---------+
| product_id | value_id|
+---------------+---------+
| 1 | 1 |
+---------------+---------+
How select all products from DB, that have any value from List<Values.alias> ?
List<decimal> aliases = ...
var query = db.Products.Where(p => p.Values.Any(v => aliases.Contains(v.Alias)));
Or (if you don't have navigation properties)
var query = from p in db.Products
join pv in db.ProductsValues on p.ProductId equals v.ProductId
join v in db.Values on pv.ValueId equals v.ValueId into values
where values.Any(v => aliases.Contains(v.Alias))
select p
If you are using EF (database first) then the table prdoucts_values is not part of the conseptual model.
instead EF givs you a direct path from Products to Values
Therefore you can write a query that look like this:
var lst = new List<Products>();
lst = db.Products.Where(c => c.Values.Any()).ToList();

Resources