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);
Related
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.
I need the corresponding query in Criteria language to this one (to retrieve all categories from my table but to distinct them):
SELECT DISTINCT categoryName
FROM Category
WHERE CategoryID IN (
SELECT CategoryID
FROM FoodCategory
)
ORDER BY categoryName
I have table FoodCategory table
id | FoodID | CategoryID
--------|---------------|------------
| |
| |
| |
Actually CategoryID is a foreign key that is pointing to this table here. This is table for Category:
CategoryID | categoryName | otherField
---------------|------------------|------------
| |
| |
| |
And this is table for Food:
FoodID | FoodName | otherField
---------------|------------------|------------
| |
| |
| |
Something like that should do the trick :
public List<String> retrieveFoodCategoryNames() {
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<String> criteriaQuery = builder.createQuery(String.class);
Root<FoodCategory> root = criteriaQuery.from(FoodCategory.class);
// by default an inner join so it will only get the categories which have their id in the FoodCategory table
Join<FoodCategory, Category> joinCategory = root.join(FoodCategory_.category);
Fetch<FoodCategory, Category> fetchCategory = root.fetch(FoodCategory_.category);
Path<String> categoryNamePath = fetchCategory.get(Category_.categoryName);
criteriaQuery.select(categoryNamePath).distinct(true);
criteriaQuery.orderBy(builder.asc(categoryNamePath));
return entityManager.createQuery(criteriaQuery).getResultList();
}
This is not the exact same SQL request because you used a subquery where I'm using a join but it seemed more suited to this particular case. The subquery syntax is a bit more complex and I will not try to write it without compiling! ^^
If something is unclear let me know :-)
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());
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.
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();