How to make a conditional join based on column value - sql

I have a tables:
Activities{ActivityId, ActivityTypeId, CreatedByUserId, ItemId, FollowId}
Items{ItemId, Title}
Followings{FollowId, FollowerId, FollowingId}
In Activity table ItemId or FollowId can be null (never both).
Now I need somehow based on or ActivityTypeId or if one of ItemId or FollowId if null to make join to Items table or Followings table.
How to write this kind of conditional query?
If ItemId is null I need to made join to Followings table.
If ItemId is not null I need to made join to Items table.
I also asked about ActivityTypeId because it's maybe easier to check activity type and based on that to make a join to Items or Followings table.

Each of the following will return different results. The first will return every relationship between (actvities and items) and (activities and followings). The second will return every combination of relationship between (actvities and items) and (activities and followings). Try them both and see which works best for you.
(Note: I haven't tested these - I wrote them freehand - there may be syntax errors)
SELECT a.ActivityId,
a.ActivityTypeId,
a.CreatedByUserId,
i.ItemId,
i.Title,
null as FollowId,
null as FollowerId,
null as FollowingId
FROM Activities a INNER JOIN Items i ON a.ItemId = i.ItemId
UNION ALL
SELECT a.ActivityId,
a.ActivityTypeId,
a.CreatedByUserId,
null as ItemId,
null as Title,
f.FollowId,
f.FollowerId,
f.FollowingId
FROM Activities a INNER JOIN Followings f ON a.FollowId = f.FollowId
OR:
SELECT a.ActivityId,
a.ActivityTypeId,
a.CreatedByUserId,
i.ItemId,
i.Title,
f.FollowId,
f.FollowerId,
f.FollowingId
FROM Activities a
LEFT OUTER JOIN Items i ON a.ItemId = i.ItemId
LEFT OUTER JOIN Followings f ON a.FollowId = f.FollowId

Related

How to perform a JOIN Query in my criteria in SQL Server?

I am trying avoid Duplicate. Some times it shows duplicate records, I don't understand What I did wrong.
Here is my query
select
itemid = case when JEDnineDays.ItemID is null and JEDthirtyDays.ItemID is null and MECnineDays.ItemID is null then MECthirtyDays.ItemID
when JEDnineDays.ItemID is null and JEDthirtyDays.ItemID is null then MECnineDays.ItemID
when JEDnineDays.ItemID is null then JEDthirtyDays.ItemID
else JEDnineDays.ItemID END
,convert(DECIMAL(10,2),JEDnineDays.NineDaysSold) JEDNineDaysSold
,convert(DECIMAL(10,2),MECnineDays.NineDaysSold) MECNineDaysSold
,convert(DECIMAL(10,2),JEDthirtyDays.ThirtyDaysSold) JEDThirtyDaysSold
,convert(DECIMAL(10,2),MECthirtyDays.ThirtyDaysSold) MECThirtyDaysSold
into #days
from
(select
itemid,
sum(qtysold) as NineDaysSold
from
[JC_ItemDSP10days]
Where StoreID IN ('1201','1302','1400','1500')
group by
ItemID
) as JEDnineDays
full outer join
(select
itemid,
sum(qtysold) as NineDaysSold
from
[JC_ItemDSP10days]
Where StoreID IN ('2001','2400','2200')
group by
ItemID
) as MECnineDays
on(JEDnineDays.itemid = MECnineDays.itemid)
full outer join
(select
itemid,sum(qtysold) as ThirtyDaysSold
from
[JC_ItemDSP30Days]
Where StoreID IN ('1201','1302','1400','1500')
group by
ItemID
) as JEDthirtyDays
on(JEDnineDays.itemid = JEDthirtyDays.itemid)
full outer join
(select
itemid,sum(qtysold) as ThirtyDaysSold
from
[JC_ItemDSP30Days]
Where StoreID IN ('2001','2400','2200')
group by
ItemID
) as MECthirtyDays
on(JEDnineDays.itemid = MECthirtyDays.itemid)
Here is my result with Duplicate
ItemID JEDNineDaysSold MECNineDaysSold JEDThirtyDaysSold JEDThirtyDaysSold
391 NULL NULL 0.75 NULL
391 NULL NULL NULL 2.50
Most of the records are correct. But some of them are duplicate. What is wrong with my join
I formatted the SQL so it was more readable. Also, the case statement at the top should just be a coalesce() or isnull() instead of a case statement.
The issue, I think, is that JEDnineDays doesn't actually contain all the ItemIds that are getting returned back so your other joins aren't joining with each other. You need to add multiple more conditions to your join statements.. something like:
on (JEDnineDays.itemid = MECnineDays.itemid OR JEDthirtyDays.itemid = MECnineDays.itemid OR ... etc)
Alternatively, and probably better, would be to do the FROM Item table and then join the rest of the tables. This will ensure that all tables can be joined back to an existing item. Once you KNOW that you will have all ItemIds in the FROM clause, you can change all your full outer joins to left joins if you want.
Fixed SQL:
select
itemid
,convert(DECIMAL(10,2),JEDnineDays.NineDaysSold) JEDNineDaysSold
,convert(DECIMAL(10,2),MECnineDays.NineDaysSold) MECNineDaysSold
,convert(DECIMAL(10,2),JEDthirtyDays.ThirtyDaysSold) JEDThirtyDaysSold
,convert(DECIMAL(10,2),MECthirtyDays.ThirtyDaysSold) MECThirtyDaysSold
into #days
from Items i
full outer join (
select itemid, sum(qtysold) as NineDaysSold
from [JC_ItemDSP10days]
where StoreID IN ('1201','1302','1400','1500')
group by ItemID
) as JEDnineDays on(JEDnineDays.itemid = i.itemid)
full outer join (
select itemid, sum(qtysold) as NineDaysSold
from [JC_ItemDSP10days]
where StoreID IN ('2001','2400','2200')
group by ItemID
) as MECnineDays on(i.itemid = MECnineDays.itemid)
full outer join (
select itemid, sum(qtysold) as ThirtyDaysSold
from [JC_ItemDSP30Days]
where StoreID IN ('1201','1302','1400','1500')
group by ItemID
) as JEDthirtyDays on(i.itemid = JEDthirtyDays.itemid)
full outer join (
select
itemid, sum(qtysold) as ThirtyDaysSold
from [JC_ItemDSP30Days]
where StoreID IN ('2001','2400','2200')
group by ItemID
) as MECthirtyDays on(i.itemid = MECthirtyDays.itemid)
Your first subquery might not return all ItemID values. A full join will put values from the other subqueries in different rows.
Start your query with a complete list, and join the rest to that:
from (
select distinct ItemID
from JC_ItemDSP10days
) all_items
full outer join
(
) sub1
on sub1.ItemID = all_items.ItemID
I agree they look like duplicates, but the initial ItemId CASE statement suggests similar/same values are coming from two different tables/views:
Specifically either:
MECnineDays.ItemID
JEDthirtyDays.ItemID
Also, not saying it's wrong, but the Full Outer Join usage looks fishy.
Suspect you might want to look at either, or both of the following:
Union or Union All
Group By, with Min()/Max()/Sum()
Once you've selected into #Days, would this produce the expected results?
select itemid,
Sum(IsNull(JEDNineDaysSold,0)) JEDNineDaysSold,
Sum(IsNull(MECNineDaysSold,0)) MECNineDaysSold,
Sum(IsNull(JEDThirtyDaysSold,0)) JEDThirtyDaysSold,
Sum(IsNull(MECThirtyDaysSold,0)) MECThirtyDaysSold
from #days
group by itemid
order by itemid

Returning ID's from two other tables or null if no IDs found using using a left join SQL Server

I am wondering if someone could hep me. I am trying to make a join on two tables and return an id if an id is there but if there is no id return null but still return the row for that product and not ignore it. My query below returns twice the amount the records to which I can not figure out why.
SELECT
T2.ProductID, FirstChild.SupplierID, SecondChild.AccountID
FROM
Products T2
LEFT OUTER JOIN
(
SELECT TOP(1) SupplierID, Reference,CompanyID, Row_Number() OVER (Partition By SupplierID Order By SupplierID) AS RowNo FROM Suppliers
)
FirstChild ON T2.SupplierReference = FirstChild.Reference AND RowNo = 1AND FirstChild.CompanyID =T2.CompanyID
LEFT OUTER JOIN
(
SELECT TOP(1) AccountID, SageKey,CompanyID, Row_Number() OVER (Partition By AccountID Order By AccountID) AS RowNo2 FROM Accounts
)
SecondChild ON T2.ProductAccountReference = SecondChild.Reference AND RowNo2 = 1 AND SecondChild.CompanyID =T2.CompanyID
Example of what I am trying to do
ProductID SupplierID AccountID
1 5 2
2 6 NULL
3 NULL NULL
OUTER APPLY and ditching the ROW_NUMBER Seems like a better choice here:
SELECT
p.ProductId
,FirstChild.SupplierId
,SecondChild.AccountId
FROM
Products p
OUTER APPLY (SELECT TOP (1) s.SupplierId
FROM
Suppliers s
WHERE
p.SupplierReference = s.SupplierReference
AND p.CompanyId = s.CompanyId
ORDER BY
s.SupplierId
) FirstChild
OUTER APPLY (SELECT TOP (1) a.AccountId
FROM
Accounts
WHERE
p.ProductAccountReference = a.Reference
AND p.CompanyId = a.CompanyId
ORDER BY
a.AccountID
) SecondChild
The way your query is written above there is no correlation for the derived tables. Which means you would always get what ever SupplierId SQL chooses based on optimization and if that doesn't happen to always be Row1 you wont get the value. You need to relate your Table and select top 1, adding an ORDER BY in your derived table is like identifying the row number you want.
If it's just showing duplicate records, wouldn't an inelegant solution just be to add distinct in the select line?

WHERE Clause for One-To-Many Association

I have two tables Products and ProductProperties.
Products
name - string
description - text
etc etc
ProductProperties
product_id - integer
property_id - integer
There is also a table Properties which basically stores the list of property names and their attributes
How can I implement a SQL command that finds a product with the property_ids (A or B or C) AND (X or Y or Z)
I've got upto here:
SELECT DISTINCT "products".*
FROM "products"
INNER JOIN "product_properties" ON "product_properties"."product_id" = "products"."id" AND "product_properties"."deleted_at" IS NULL
WHERE "products"."deleted_at" IS NULL
AND (product_properties.property_id IN ('504, 506, 403'))
AND (product_properties.property_id IN ('520, 501, 502'))
But it doesn't really work since it's looking for a Product Property which has both values 504 and 520, which will never exist.
Would appreciate some help!
You need to define intermediate resultsets on a property group basis:
SELECT DISTINCT p.*
FROM products p
JOIN product_properties groupA ON groupA.product_id = p.id AND groupA.deleted_at IS NULL AND groupA.property_id IN ('504')
JOIN product_properties groupB ON groupB.product_id = p.id AND groupB.deleted_at IS NULL AND groupB.property_id IN ('520')
WHERE p.deleted_at IS NULL
You see, you detected the problem yourself very nicely: "But it doesn't really work since it's looking for a Product Property which has both values 504 and 520, which will never exist."
Indeed, recordsets are immutable within a query, all single criteria applied to them are applied all at once. You need to duplicate each table and apply individual criteria to them.
One method uses exists or in:
select p.*
from products p
where p.id in (select pp.product_id
from product_properties pp
where pp.propertyid in ('504', '520')
);
This saves you from having to use distinct in the outer query.
If, perchance, you really mean finding the products that have all the properties, then a join and group by work:
select p.*
from products p join
product_properties pp
on p.id = pp.product_id
where pp.propertyid in ('504', '520')
group by p.id -- yes, this is allowed in Postgres
having count(*) = 2;
Hi try this queries i just thinking about it so i didn't try any of them check i got the idea i want to do
SELECT DISTINCT "products".*
FROM products pr
WHERE id IN
(
SELECT product_id FROM ProductProperties WHERE property_id IN (504,520)
GROUP BY product_id
HAVING Count(*) = 2
) AND "products"."deleted_at" IS NULL
SELECT DISTINCT "products".*
FROM products pr, INNER JOIN (
SELECT product_id,count(*) as nbr FROM ProductProperties WHERE property_id IN (504,520)
GROUP BY product_id
) as temp ON temp.product_id = pr.id
WHERE "products"."deleted_at" IS NULL AND temp.nbr = 2
and also you can check this one as well ( you can use also the join in where clause instead of using INNER JOIN)
SELECT DISTINCT products.* FROM products as p
INNER JOIN product_properties as p1 ON p1.product_id = p.id
INNER JOIN product_properties as p2 ON p2.product_id = p.id
WHERE p.deleted_at IS NULL
AND p1.property_id = '504' AND p1.deleted_at IS NULL
AND p2.property_id = '520' AND p2.deleted_at IS NULL

Selecting maximum value and inserting 0 if there are no entries

I have two tables, items and bids.
create table items (
id serial primary key,
store_id int,
min_bid int
);
create table bids (
item_id int,
amount int
);
I want to select items and include information about the max bid.
select items.*, max(bids.amount) from items
join bids on bids.item_id = items.id
where items.store_id = $store_id
group by items.id
However, when there are no bids for a particular item, the item just doesn't get selected. How can I make it so that when there are no bids, the item still gets selected and fills in the max(bids.amount) column with items.min_bid? (Or 0 is fine, too.)
I tried this:
select items.*, coalesce(max(bids.amount), items.min_bid) from items
join bids on bids.item_id = items.id
where items.store_id = $store_id
group by items.id
which doesn't work. I'm assuming it's because of the join that the items aren't getting selected in the first place.
What should I do?
The two crucial elements are LEFT JOIN and COALESCE().
#Adrian already commented on LEFT [OUTER ]JOIN. It preserves all rows at the left hand of the join and fills missing columns to the right with NULL values. The manual has more on the basics.
COALESCE() replaces NULL values with the provided alternative - 0 in this case.
SELECT i.*, COALESCE(max(b.amount), 0)
FROM items i
LEFT JOIN bids b ON b.item_id = i.id
WHERE i.store_id = $store_id
GROUP BY i.id
This alternative form is often faster when large parts of the sub-table are used: Aggregate in a subquery first, join later. This way you don't need an aggregation in the outer query. The second query also demonstrates how you can supply items.min_bid as replacement for NULL values.
SELECT i.*, COALESCE(b.max_amount, i.min_bid)
FROM items i
LEFT JOIN (
SELECT item_id, max(amount) AS max_amount
FROM bids
) b ON b.item_id = i.id
WHERE i.store_id = $store_id;
First, to display the items with no bids, you have to make use of a LEFT JOIN:
select items.*, max(bids.amount)
from items
left join bids on bids.item_id = items.id
where items.store_id = $store_id
group by items.id

Filter records based on groups of fields

Here's the thing - I need to filter records based on groups of fields. A prototype would look like this:
select distinct ID, Name from Item i
inner join (select ItemID from ItemD where ItemDID in (146,147)) idd1 on i.ItemID = idd1.ItemID
inner join (select ItemID from ItemD where ItemDID in (7641, 7648)) idd2 on i.ItemID = idd2.ItemID
(repeat inner join couple more times)
I know that I can create a stored procedure that uses sp_executesql and feed it those inner joins from my app, but I can't help wondering is there a better solution?
You could use a temporary table, probably faster than a lot of joins:
Conditions: GroupID, ItemDID
And fill it like:
1, 146
1, 147
2, 7641
2, 7648
Then demand that each condition group is satisfied:
select ID
, Name
from Item i
where not exists
(
select *
from Conditions c
left join
ItemID idd
on idd.ItemDID = c.ItemDID
and idd.ItemID = i.ItemID
group by
c.GroupID
having count(idd.ItemDTD) = 0
)
(Query not tested; there are many varieties.)