SQL Join - get rows that meet criteria from foreign key table - sql

I need to find the rows in one table that meet specific attributes specified in a foreign key table.
I have these two tables:
ITEM
PK: ItemId
Text: nvarchar
ITEMATTRIBUTE
PK: ItemAttributeId
FK: ItemId
AttributeText: nvarchar
Value: int
ItemAttribute has a foreign key to Items and defines dynamic attributes given for an item.
e.g:
ITEM:
ItemId Text
1 ItemA
2 ItemB
3 ItemC
ITEMATTRIBUTE:
ItemAttributeID ItemId AttributeText Value
1 1 AttributeA 10
2 1 AttributeB 10
3 2 AttributeA 8
I need to dynamically find items which have certain itamAttributes.
For example the desired query shall return items that have
AttributeA AND AttributeB: result should show only ItemA.
When I (left) join both tables I get a row for each item with the joined itemattribute:
select * from item i left join itemattribute a on i.itemid=a.itemid
However I need a dynamic way to get items where defined itemattributes are set:
get all items that have AttributeA with a value > 5 and AttributeB with value > 5: result ItemA
As I don't know how many itemAttributes are set I can't do the query with stacked subselects or hardcoded as in:
select distinct i2.itemid from item i2, itemattribute a2,
(select i.itemid, a.attributetext from item i left join itemattribute a on i.itemid=a.itemid where AttributeText='AttributeA' and value > 5) ii
where i2.itemid=a2.itemid and a2.attributetexT='AttributeB' and value > 5

You need to aggregate your values to the Item level, then do the logic to filter for the Items you want.
In some dbs (like Snowflake) you can run a PIVOT, but in most you need to do conditional aggregations.
SELECT item_id, att_a_value, att_b_value
FROM (
SELECT
i.item_id
,max(CASE WHEN ia.AttributeText = 'Attribute A' THEN ia.value END) as att_a_value
,max(CASE WHEN ia.AttributeText = 'Attribute B' THEN ia.value END) as att_b_value
FROM Items i
LEFT JOIN ItemAttributes ia ON i.item_id = ia.item_id
GROUP BY i.item_id
) z
WHERE z.att_a_value > 5 AND z.att_b_value > 5

Related

How add more rows when find string in column Oracle

Would it be possible to add more rows base on Keyword string in SQL ?
table A
PID PromotionName
1 OUT_EC_D10_V500K_FamilyCare_PROCO
2 OUT_EC_D5_V50K_Lunchbox_PROCO
3 OUT_EC_D5_V50K_PROCO
table B
promotion_code itm_name quantity
Lunchbox Item name 1 1
FamilyCare Item name 2 1
FamilyCare Item name 3 1
BUY1FREE6 Item name 4 1
HiSummer Item name 5 1
FamilyCare Item name 6 1
Example:
SELECT * FROM A where pid = '1';
Output of the SQL should be -
PID PromotionName Itm_name quantity
1 OUT_EC_D10_V500K_FamilyCare_PROCO
2 FamilyCare Item name 2 1
3 FamilyCare Item name 3 1
4 FamilyCare Item name 6 1
How to find string with keyword 'FamilyCare' in PromotionName of table A base on promotion_code of table B? If it exist it will add more rows in output
Any help with the SQL?
Here is how you can achieve this:
SELECT PID,PromotionName, '' as Itm_name, NULL as quantity
FROM A
WHERE pid = '1'
UNION
SELECT PID, PROMOTION_NAME, Itm_name, quantity
FROM
(SELECT * FROM A inner join B on a.promotionName LIKE '%'||b.promotion_name||'%')
WHERE pid='1'
You have to update your pid in both the places (before and after UNION).
Notice that tables were joined using LIKE operator with % before and after the word. Hence this joins if a part of a string is present in another column.
db<>fiddle link here
An option would be starting to construct a subquery factoring along with joining tables through a.promotionName LIKE '%'||b.promotion_code||'%' condition while filtering by b.promotion_code = 'FamilyCare', then add another query to combine the result sets by UNION ALL, and then enumerate with an id column by ROW_NUMBER() analytic function such as
WITH ab AS
(
SELECT a.*, b.*
FROM a
JOIN b
ON a.promotionName LIKE '%'||b.promotion_code||'%'
WHERE b.promotion_code = 'FamilyCare'
), ab2 AS
(
SELECT promotion_code, itm_name, quantity
FROM ab
UNION ALL
SELECT DISTINCT promotionName, NULL, NULL
FROM ab
)
SELECT ROW_NUMBER() OVER (ORDER BY itm_name NULLS FIRST) AS pid,
a.*
FROM ab2 a
if there's mismatch for the topmost query, then no row will be returned. eg. that query will check for the existence for the literal you provide
Demo

Get total count and filtered count from single subquery

product table
product_id
name
1
t-shirt
2
shirt
users table
user_id
name
1
sphinx
favorite table with a UNIQUE constraint on (user_id, product_id):
user_id
product_id
1
2
What is best way to query this table for user_id = 1
favorite_count: how many users added this product in favorite?
isfavorite: did user with user_id = 1 add this product as favorite?
Expected result:
product_id
product_name
isfavorite
favorite_count
1
t-shirt
false
0
2
shirt
true
1
Typically, it's cheapest to aggregate rows in the n-table (favorite in this case) before the join:
SELECT p.*
, COALESCE(f.is_favorite, false) AS is_favorite
, COALESCE(f.favorite_count, 0) AS favorite_count
FROM product p
LEFT JOIN (
SELECT product_id
, count(*) AS favorite_count -- total count
, bool_or(true) FILTER (WHERE user_id = 1) AS is_favorite -- for given user
FROM favorite
GROUP BY 1
) f USING (product_id);
We don't need the users table for the query.
LEFT JOIN keeps all products in the result, while only adding counts for products that have favorite entries.
About the FILTER clause:
Aggregate columns with additional (distinct) filters
Since not every product has entries in table favorite, use COALESCE() in the outer query to replace resulting NULL values with your defaults.

Find rows in a table based on the existance of two different rows in a 1:N-related table

Say I have a table Clients, with a field ClientID, and that client has orders that are loaded in another table Orders, with foreign key ClientID to link both.
A client can have many orders (1:N), but orders have different types, described by the field TypeID.
Now, I want to select the clients that have orders of a number of types. For instance, the clients that have orders of type 1 and 2 (both, not one or the other).
How do I build this query? I'm really at lost here.
EDIT: Assume I'm on SQL Server.
This is query upon the assumption that TypeId can be either 1 or 2. This will return ClientId that have both a Type1 and Type2 no matter how many of them.
Select ClientId, COUNT(distinct TypeId) as cnt
from tblOrders o
group by ClientId
Having COUNT(distinct TypeId) >= 2
COUNT(distinct TypeId) is how this really works. It will count the distinct number of TypeId's for a particular ClientId. If you had say 5 different Types, then change the condition in the Having Clause to 5
This is a small sample DataSet
ClientId TypeId
1 1
1 2
1 2
2 2
2 1
3 1
3 1
Here is the resulting Query, it will exclude client 3 because it only has orders with Type1
Result Set
ClientId cnt
1 2
2 2
If you have many different TypeId's, but only want to check Type1 and Type2 put those Id's in a where clause
where TypeId in (1,2)
Here's one solution:
select * from clients c
where exists (select 1 from orders o where typeid = 1 and o.clientid = c.clientid)
and exists (select 1 from orders o where typeid = 2 and o.clientid = c.clientid)
and exists (select 1 from orders o where typeid = 3 and o.clientid = c.clientid)
-- additional types ...
You can use INTERSECT which will give the intersection of the resultsets.

showing items when 'x' is present

Ok, so hoping I can get some help here after searching with no joy.
So I have a key 'orderno' and each 'orderno' has multiple items. Each item has a status. I want to pull a Q that shows only the orderno's that contain an item that has status of 'x'
So If there are 3 items and only 1 is showing status 'x' I want to see all three items not just the one.
Essentially removing any order/items that do not show the x value.
So table1
orderno / Itemno / Itemstatus
1 1 y
1 2 x
2 1 z
3 1 y
3 2 x
3 3 y
4 1 y
4 1 y
EDIT:
So basically the letters represent open, closed, or inprogress... I want to see only order that have and item closed as well as an item in progress so I can see why the order is only showing partially complete from there. Still probably not making sense here. Grrrr.
I need to return the ORDER# and all item#'s for any order that contains an item with status of 'x'.
SELECT * FROM Order_Table
WHERE orderno IN
(SELECT orderno FROM Order_Table WHERE Itemstatus = 'x')
The Inner query returns all the orders with the status 'x' and the outer one return all details of those orders.
I prefer EXISTS to the IN or JOIN versions. It general faster.
Added a sqlfiddle.
CREATE TABLE table1(orderno INT, Itemno INT, Itemstatus CHAR(1))
INSERT INTO table1 VALUES
(1,1,'y')
,(1,2,'x')
,(2,1,'z')
,(3,1,'y')
,(3,2,'x')
,(3,3,'y')
,(4,1,'y')
,(4,1,'y')
SELECT *
FROM table1 a
WHERE EXISTS(SELECT 1
FROM table1 b
WHERE b.OrderNo = a.OrderNo
AND b.Itemstatus='x')

How do I use a join to query two tables and get all rows from one, and related rows from the other?

Simplified for example, I have two tables, groups and items.
items (
id,
groupId,
title
)
groups (
id,
groupTitle,
externalURL
)
The regular query I'm goes something like this:
SELECT
i.`id`,
i.`title`,
g.`id` as 'groupId',
g.`groupTitle`,
g.`externalURL`
FROM
items i INNER JOIN groups g ON (i.`groupId` = g.`id`)
However I need to modify this now, because all the groups which specify an externalURL will not have any corresponding records in the items table (since they're stored externally). Is it possible to do some sort of join so that the output looks kinda like this:
items:
id title groupId
----------------------
1 Item 1 1
2 Item 2 1
groups
id groupTitle externalURL
-------------------------------
1 Group 1 NULL
2 Group 2 something
3 Group 3 NULL
Query output:
id title groupId groupTitle externalURL
---------------------------------------------------
1 Item 1 1 Group 1 NULL
2 Item 2 1 Group 1 NULL
NULL NULL 2 Group 2 something
-- note that group 3 didn't show up because it had no items OR externalURL
Is that possible in one SQL query?
This is exactly what an outer join is for: return all the rows from one table, whether or not there is a matching row in the other table. In those cases, return NULL for all the columns of the other table.
The other condition you can take care of in the WHERE clause.
SELECT
i.`id`,
i.`title`,
g.`id` as 'groupId',
g.`groupTitle`,
g.`externalURL`
FROM
items i RIGHT OUTER JOIN groups g ON (i.`groupId` = g.`id`)
WHERE i.`id` IS NOT NULL OR g.`externalURL` IS NOT NULL;
Only if both i.id and g.externalURL are NULL, then the whole row of the joined result set should be excluded.