Recursive CTE ancestry (Employee with multiple Managers) - postgresql

I am having difficulties trying to output all managers above an employee. The employees table does not have a manager_id column, which makes this a bit complicated.
The hierarchy is defined in a references table.
Each employee belongs to a department and is assigned a position. A position with level 1 is a manager, level 0 is non-manager.
To get the manager of a manager, we take the parent_id of the current department and look for an employee assigned with a position with level 1.
The function I currently have only returns the employee's direct manager (from the non-recursive term).
CREATE OR REPLACE FUNCTION public.get_employee_managers(employees_row employees)
RETURNS SETOF employees
LANGUAGE sql
STABLE
AS $function$
WITH RECURSIVE managers AS (
SELECT e1.*
FROM
employees e1
JOIN "references" AS employees_row_department ON employees_row_department.id = employees_row.department_id
JOIN "references" AS e1_department ON e1_department.id = e1.department_id
JOIN "references" AS e1_position ON e1_position.id = e1.position_id
WHERE
e1_department.id = employees_row_department.parent_id AND e1_position.level = 1 AND e1.active = 1 AND e1.is_deleted = false
OR
e1_department.id = employees_row.department_id AND e1_position.level = 1 AND e1.active = 1 AND e1.is_deleted = false AND e1.id <> employees_row.id
UNION
SELECT m1.*
FROM
managers m1
JOIN "references" AS m1_department ON m1_department.id = m1.department_id
JOIN "references" AS m1_position ON m1_position.id = m1.position_id
INNER JOIN employees AS e2 ON (m1_department.parent_id = e2.department_id AND m1_position.level = 1 AND e2.active = 1 AND e2.is_deleted = false)
)
SELECT * FROM managers ORDER BY department_id ASC
$function$
Using the following recursive term gives me the same result as the one above
SELECT e2.*
FROM
employees e2
JOIN "references" AS e2_department ON e2_department.id = e2.department_id
JOIN "references" AS e2_position ON e2_position.id = e2.position_id
INNER JOIN managers m1 ON m1.id = e2.id
JOIN "references" AS m1_department ON m1_department.id = m1.department_id
WHERE
e2_department.id = m1_department.parent_id AND e2_position.level = 1 AND e2.active = 1 AND e2.is_deleted = false

Changing the second recursive term seems to give me the result I was looking for.
INNER JOIN managers m1 ON m1.id = e2.id
to
INNER JOIN managers m1 ON m1.id <> e2.id

Related

How can I use value from main select in Left Outer Join select - T-SQL

In my below T-SQL Query I need to use EFP_MessageCenter.MessageSender from my main SELECT in the SELECT in my LEFT OUTER JOIN as the value where I have placed <MessageSenderInitials>.
When I set (EFP_MessageCenter_1.MessageSender = EFP_MessageCenter.MessageSender) or (EFP_MessageCenter_1.MessageSender = MessageSenderInitials) I get the error The multi-part identifier "EFP_MessageCenter.MessageSender" could not be bound.
How can I get this to work?
SELECT LOWER(EFP_MessageCenter.MessageSender) AS MessageSenderInitials
, MAX(SenderInfo.FullName) AS SenderFullName
, MAX(SenderInfo.ProfilePicture) AS SenderProfilePicture
, MAX(EFP_MessageCenter_Receiver.UserID) AS ReceiverID
, MAX(EFP_MessageCenter.MessageTimestamp) AS ChangeDate
, COUNT(DisplayCountSelect.Displayed) AS CountNonReadMessages
FROM EFP_MessageCenter_Receiver
INNER JOIN EFP_MessageCenter ON EFP_MessageCenter_Receiver.MessageID = EFP_MessageCenter.id
INNER JOIN EFP_EmploymentUser AS SenderInfo ON EFP_MessageCenter.MessageSender = SenderInfo.Initials
LEFT OUTER JOIN
(SELECT EFP_MessageCenter_Receiver_1.Displayed, EFP_MessageCenter_Receiver_1.UserID, EFP_MessageCenter_1.MessageSender
FROM EFP_MessageCenter AS EFP_MessageCenter_1
INNER JOIN EFP_MessageCenter_Receiver AS EFP_MessageCenter_Receiver_1 ON EFP_MessageCenter_1.id = EFP_MessageCenter_Receiver_1.MessageID
WHERE (EFP_MessageCenter_Receiver_1.Displayed = 0) AND (EFP_MessageCenter_Receiver_1.UserID = 65) AND (EFP_MessageCenter_1.MessageSender = '<MessageSenderInitials>'))
AS DisplayCountSelect
ON DisplayCountSelect.UserID = EFP_MessageCenter_Receiver.UserID
WHERE (EFP_MessageCenter_Receiver.UserID = 65) AND (EFP_MessageCenter.MessageType = 'SPECIFIC')
GROUP BY EFP_MessageCenter.MessageSender
ORDER BY ChangeDate DESC
I've made a slight refactor of your query and changed the outer join to an an outer apply
It's not going to be 100% working I'm sure but should allow you to tweak it and include the correlation you need to.
I suspect you could move the CountNonReadMessages to a count(*) in the apply and possibly remove the aggregation, but that's just a guess.
select Lower(mc.MessageSender) as MessageSenderInitials
, Max(s.FullName) as SenderFullName
, Max(s.ProfilePicture) as SenderProfilePicture
, Max(mr.UserID) as ReceiverID
, Max(mc.MessageTimestamp) as ChangeDate
, Count(s.Displayed) as CountNonReadMessages
from EFP_MessageCenter_Receiver mr
join EFP_MessageCenter mc on mr.MessageID = mc.id
join EFP_EmploymentUser eu on mc.MessageSender = eu.Initials
outer apply (
select mr.Displayed
from EFP_MessageCenter mcx
join EFP_MessageCenter_Receiver mrx on mcx.id = mrx.MessageID
where mrx.Displayed = 0
and mrx.UserId=mr.UserId
and mcx.UserID = 65 /* this should probably be correlated */
and mcx.MessageSender = '<MessageSenderInitials>'
) s
where mr.UserID = 65 and mc.MessageType = 'SPECIFIC'
group by mc.MessageSender
order by ChangeDate desc

linq subquery join and group by

Hi is it possible to translate the below queries into linq ? ...
I am using entity frameworks core and tried to use the stored procedure but it seems like i have to create a model that applies to the metadata of the stored procedure. So i am trying to understand whether this kinda such query can be translated into linq so i don't have to create a separate db model.
SELECT
Stock.stockID ProductID,
stockName ProductName,
categoryName ProductCategory,
typeName ProductType,
sizeName ProductSize,
currentQuantity CurrentQuantity,
standardQuantity QuantityPerBox,
CONVERT(VARCHAR(255),CONVERT(INT,Stock.price)) AvgUnitCost,
CONVERT(VARCHAR(255),CONVERT(INT,x.lastUnitCost)) LastOrderUnitCost,
CONVERT(VARCHAR(10),CONVERT(DATE,x.lastOrderDate)) LastOrderDate
FROM dbo.Stock
LEFT JOIN
(
SELECT stockID,unitPrice lastUnitCost ,orderDate lastOrderDate, ROW_NUMBER() OVER (PARTITION BY stockID ORDER BY orderDate DESC) rn FROM dbo.SalesOrder
JOIN dbo.SalesOrderDetail ON SalesOrderDetail.salesOrderID = SalesOrder.salesOrderID
WHERE customerID = #customerID AND salesStatus = 'S'
) x ON x.stockID = Stock.stockID AND rn = 1
LEFT JOIN dbo.StockCategory ON StockCategory.stockCategoryID = Stock.stockCategoryID
LEFT JOIN dbo.StockType ON StockType.stockTypeID = Stock.stockTypeID
LEFT JOIN dbo.StockSize ON StockSize.stockSizeID = Stock.stockSizeID
WHERE disStock = 0
Almost everything is possible. you just need to be careful with performance.
var query =
from stock in db.Stocks
join x in (
from grp in (
from so in db.SalesOrders
join sod in db.SalesOrderDetails on so.SalesOrderId equals sod.SalesOrderId
where so.CustomerId == customerId && so.SalesStatus == "S"
orderby so.OrderDate descending
select new {
sod.StockId,
LastUnitCost = sod.UnitPrice,
LastOrderDate = so.OrderDate
} into inner
group inner by inner.StockId)
select grp.Take(1)) on x.StockId equals stock.StockId into lastStockSales
from x in lastStockSales.DefaultIfEmpty()
join sc in db.StockCategories on stock.StockCatergotyId equals sc.StockCategoryId into scLeft
from sc in scLeft.DefaultIfEmpty()
join st in db.StockTypes on stock.StockTypeId equals st.StockTypeId into stLeft
from st in stLeft.DefaultIfEmpty()
join ss in db.StockSizes on stock.StockSizeId equals ss.StockSizeId into ssLeft
from ss in ssLeft.DefaultIfEmpty()
where stock.DisStock == 0
select new MyDTO {
ProductId = stock.StockId,
ProductName = stock.StockName,
ProductType = st.TypeName,
ProductSize = ss.SizeName,
CurrentQuantity = stock.CurrentQuantity,
QuantityPerBox = stock.StandardQuantity,
AvgUnitCost = stock.Price,
LastOrderUnitCost = x.LastUnitCost,
LastOrderDate = x.LastOrderDate
};
As you can see is easy to rewrite these queries, I had to change a little bit the logic on how to get the latest sales for a stock item since ROW_NUMBER() OVER (PARTITION... is not supported from a LINQ perspective. Again, you would have to consider performance when rewriting queries.
Hope this helps.

Postgres get rows which hasnt match in other table

I need your help. I need an advanced Query to my database. Im showing part of my database following:
Place (id, name, address)
Local (id, place_id, name)
PlaceReservation(id, local_id, date)
Media_Place (id, place_id, type)
Now I need a query, which gets all places with logo, which have AT LEAST ONE local which hasn't been reserved on a specific day e.g: 2015-07-01.
Help me please, because I haven't an idea how to do it. I thought about an outer join but I don't know how use it.
I was trying by:
$query = 'SELECT DISTINC *,
(SELECT sum(po.rating)/count(po.id)
FROM "Place_Opinion" po
WHERE po.place_id = p.id AND po.deleted = false) AS rating,
mp.path as logo_path
FROM "Place" p
INNER JOIN "Media_Place" mp ON mp.place_id = p.id
JOIN Local ON Local.place_id = Place.id
LEFT JOIN (
SELECT id AS rr, local_id
FROM PlaceReservation
WHERE date_start = \'2015-07-01\') Reserved ON Reserved.local_id = Local.id
WHERE mp.type = ' . Model_Row_MediaPlace::LOGO_TYPE . '
AND mp.deleted = false
AND p.deleted = false
AND rr IS NULL';
Looking for things that do not exist in a database is usually very inefficient. But you can change the logic around by finding places that do have a booking for the specified date, then LEFT JOIN that to all places with a logo and filter out the records with a reservation:
SELECT DISTINCT p.*, po.rating, mp.path as logo_path
FROM "Place" p
JOIN "Media_Place" mp ON mp.place_id = p.id AND mp.deleted = false AND mp.type = ?
JOIN Local ON Local.place_id = p.id
LEFT JOIN (
SELECT id AS rr, local_id
FROM PlaceReservation
WHERE date_start = '2015-07-01') reserved ON reserved.local_id = Local.id
LEFT JOIN (
SELECT place_id, avg(rating) AS rating
FROM "Place_Opinion"
WHERE deleted = false
GROUP BY place_id) po ON po.place_id = p.id
WHERE p.deleted = false
AND reserved.rr IS NULL;
The average rating per places is calculated in a separate sub-query. The error you had was because you referenced the "Place" table (p.id) before it was defined. For simple columns you can do that, but for sub-queries you can't.

Has-Many-Through: How to select records with no relation OR by some condition in relation?

There are three tables: businesses, categories, categorizations,
CREATE TABLE businesses (
id SERIAL PRIMARY KEY,
name varchar(40)
);
CREATE TABLE categories (
id SERIAL PRIMARY KEY,
name varchar(40)
);
CREATE TABLE categorizations (
business_id integer,
category_id integer
);
So business has many categories through categorizations.
If I want to select businesses without categories, I would do something
like this:
SELECT businesses.* FROM businesses
LEFT OUTER JOIN categorizations
ON categorizations.business_id = businesses.id
LEFT OUTER JOIN categories
ON categories.id = categorizations.category_id
GROUP BY businesses.id
HAVING count(categories.id) = 0;
The question is: How do I select businesses without categories AND
businesses with category named "Media" in one query?
You can use a union:
SELECT businesses.*
FROM businesses
LEFT OUTER JOIN categorizations
ON categorizations.business_id = businesses.id
GROUP BY businesses.id
HAVING count(categorizations.business_id) = 0
UNION
SELECT businesses.*
FROM businesses
INNER JOIN categorizations
ON categorizations.business_id = businesses.id
INNER JOIN categories
ON categories.id = categorizations.category_id
WHERE categories.name = 'Media';
Note that in the first instance (businesses with no categories at all) that you won't need to join as far as categories - you can detect the lack of category in the junction table. If it is possible for the same business to have the same category more than once, you'll need to introduce the second query with DISTINCT.
I would try:
SELECT b.* FROM businesses b
LEFT JOIN categorizations cz ON b.business_id = cz.business_id
LEFT JOIN categories cs ON cz.category_id = cs.category_id
WHERE COALESCE(cs.name, 'Media') = 'Media';
... in the hope that businesses with no categorizations would get NULL entries on their joins.
The double-negation trick works for this kind of selections:
SELECT * FROM businesses b
WHERE NOT EXISTS (
SELECT *
FROM categorizations bc
JOIN categories c ON bc.category_id = c.category_id
WHERE bc.business_id = b.business_id
AND c.name <> 'Media'
);

TSQL efficiency - INNER JOIN replaced by EXISTS

Can the following be rewritten to be more efficient?
I would use EXISTS if I didn't need fields from country but I do need those fields, and am not sure how to write this to make it more efficient.
SELECT distinct
p.ProvinceID,
p.Abbv as RegionCode,
p.name as RegionName,
cn.Code as CountryCode,
cn.Name as CountryName
FROM dbo.provinces AS p
INNER JOIN dbo.Countries AS cn ON p.CountryID = cn.CountryID
INNER JOIN dbo.Cities c on c.ProvinceID = p.ProvinceID
INNER JOIN dbo.Listings AS l ON l.CityID = c.CityID
WHERE l.IsActive = 1 AND l.IsApproved = 1
There are two things to note:
You're joining to dbo.Listings which results in many records, so you need to use DISTINCT (usually an expensive operator)
For any tables with columns not in the select you can move into an EXISTS (but the query planner effectively does this for you anyway)
So try this:
SELECT
p.ProvinceID,
p.Abbv as RegionCode,
p.name as RegionName,
cn.Code as CountryCode,
cn.Name as CountryName
FROM dbo.provinces AS p
INNER JOIN
dbo.Countries AS cn
ON p.CountryID = cn.CountryID
WHERE EXISTS (SELECT 1 FROM
dbo.Listings l
INNER JOIN dbo.Cities c
on l.CityID = c.CityID
WHERE c.ProvinceID = p.ProvinceID
AND l.IsActive = 1 AND l.IsApproved = 1
)
Check the query plans before and after - the query planner might be smart enough to do this anyway, but you have removed your distinct
The following will often perform even better by providing the optimizer more useful information:
SELECT
p.ProvinceID,
p.Abbv as RegionCode,
p.name as RegionName,
cn.Code as CountryCode,
cn.Name as CountryName
FROM dbo.provinces AS p
INNER JOIN
dbo.Countries AS cn
ON p.CountryID = cn.CountryID
INNER JOIN (
SELECT
p.ProvinceID
FROM
dbo.Listings l
INNER JOIN dbo.Cities c
on l.CityID = c.CityID
WHERE l.IsActive = 1 AND l.IsApproved = 1
GROUP BY
p.ProvinceID
) list
on list.ProvinceID = p.ProvinceID