I have a 1..n relation between person and orders.
Order can have ACTIVE or CANCELLED status.
One person can have at most one ACTIVE order, but can have inifinite number of CANCELLED orders.
Lets assume that content of my join table looks like this:
To get persons with ACTIVE order my query is just:
select * from persons p inner join orders o on p.id = o.person_id where o.status = 'ACTIVE';
The result should be persons with id 1 and 3. That's simple.
But now, I need to get all those persons which don't have any ACTIVE shipping, so the result of such query should be person with id 2 (does not necessarily have to be distinct row). I can't just change to != 'ACTIVE' in above query, because then I get all those 3 persons, as all of them have orders with status other than ACTIVE.
If I were to explain it by words I'd say that for every row I need to check also other row's with the same p.id and check if ACTIVE status does not occur in any of them.
Any suggestions how can I achive that?
I need to get all those persons which don't have any ACTIVE shipping
You can use not exists:
select *
from persons p
where not exists (
select 1
from orders o
where o.person_id = p.id and o.status = 'ACTIVE'
);
For performance with this query, consider an index on orders(person_id, status).
Note that this also allows persons that have no order at all. If you want to avoid that, then use aggregation instead:
select p.*
from persons p
inner join orders o on o.person_id = p.id
group by p.id
having max(o.status = 'ACTIVE') = 0
You can use aggregation:
select p.*
from persons p inner join
orders o
on p.id = o.person_id
group by p.id
having sum(o.status = 'ACTIVE') = 0;
Related
I have written the following two queries for the below requirement. Please let me know which method is correct or both methods are wrong? Thanks a lot
There were two tables -
'Orders' with - order_id(PK), item id, quantity, order_date [Transactional Table]
'Catalog' with-item id, product group, location [Dimension Table]
They asked to write a SQL code that will return the product groups of US that has no sale in any unit(i.e all the item id from an individual product group has no sale).
1st Method:
with cte as
(
select c.*,o.order_id,
case when o.order_id is not null then 1 else 0 end sale_ind
from Catalog c
left join Orders o
on c.item_id = o.item_id
and c.location = 'US'
)
select product_group
from cte
group by product_group having sum(sale_ind) = 0
2nd Method:
select c.*
from Catalog c
where c.location='US'
and item_id not in (
select item_id
from Orders)
They asked to write a SQL code that will return the product groups of US that has no sale in any unit(i.e all the item id from an individual product group has no sale).
I would tend to go with not exists for this:
select distinct c.product_group
from catalog c
where c.location = 'US' and
not exists (select 1
from orders o
where o.item_id = c.item_id
);
That said, both your queries look okay, but the first is correct. The second is returning all catalog records not all product_groups. As for the second, I would discourage you from ever using not in with a subquery. No rows are returned if item_id returned by the subquery is ever NULL.
SELECT DISTINCT c.product_group
FROM Catalog c
LEFT OUTER JOIN Orders o
on c.item_id = o.item_id
WHERE c.location='US'
AND o.item_id is null
Left join: because you want catalog records (left side) even if there are no order records (right side). The second part of the WHERE clause filters out instances where there are orders.
You can’t use an inner join as that would return only records where the Catalog record had corresponding orders, which is not what you want
Let's say we have a table booking where a peopleid can have multiple bookings. What's the best way to find people with at least 1 booking of active? Other booking values are cancelled,closed
I was thinking this, but I feel like there's gotta be a more efficient method:
select a.peopleid
, sum(case status.status_desc when 'active' then 1 else 0 end) as counted
from booking a
inner join people
on a.peopleid = people.peopleid
inner join status
on a.statusid = status.statusid
group by a.personid
I plan on doing a left join to this to get a simple yes or no indicator for if a person has at least one active booking or not
grain of booking is bookingid|statusid|peopleid and 1 person can have multiple bookings
If you just want to find the people with at least one active booking, just do a simple JOIN and put the desired status in the WHERE clause.
SELECT DISTINCT a.peopleid
FROM booking AS b
JOIN status AS s ON b.statusid = s.statusid
WHERE s.status_desc = 'active'
There's no need to join with people (unless you want to get other information about the person, which isn't shown in the question). And there's no need to count the matches; if there are no rows that match the status_desc = 'active' condition they won't be returned by the JOIN. SELECT DISTINCT gets rid of the duplicates when someone has multiple bookings.
You can use EXiSTS to check which user has at least one booking that match the condition.
select p.peopleid
from people p
where exists (select 1
from booking b
join status s
where b.peopleid = p.peopleid
s.statusid = b.statusid
and s.status_desc = 'active');
Just use count with a WHERE clause.
select a.peopleid
, COUNT(*) as counted
from booking a
inner join people
on a.peopleid = people.peopleid
inner join status
on a.statusid = status.statusid
where status.status_desc = 'active'
group by a.personid
Why not a simple join:
select p.* from people p
join booking b on b.peopleid = a.peopleid
join status on status.statusid = b.statusid
where status.status_desc = 'active'
If you don't need any other info from the the people like their name, address.. then you can skip the people table and get the ID from the booking table.
I'm trying to pull unique emails that have matching IDs across two tables.
SELECT line_items.order_id, line_items.id, orders.email, orders.name
FROM orders INNER JOIN
line_items
ON orders.id = line_items.order_id
WHERE line_items.order_id IN (SELECT DISTINCT email FROM orders WHERE status = 0 AND created_at BETWEEN '2018-01-10' AND NOW() )
LIMIT 50;
I know my error is based upon the fact that the line_items.order_is is an INT and therefore the IN parameter is looking for another int column to match against. However, I'm not sure how to modify this to get pull the proper results. Any and all help is greatly appreciated.
I'm trying to pull unique emails that have matching IDs across two tables.
If you mean distinct emails, then your subquery would appear to do this:
SELECT DISTINCT o.email
FROM o.orders
WHERE o.status = 0 AND o.created_at BETWEEN '2018-01-10' AND NOW();
Because an order should have at least one line item, I don't see why that table is necessary.
Your query comes close to answering the question: "Pull all orders for emails that have made a recent order with status 0". If that is what you want:
SELECT li.order_id, li.id, o.email, o.name
FROM orders o INNER JOIN
line_items li
ON o.id = li.order_id
WHERE o.email IN (SELECT o2.email FROM orders o2 WHERE o2.status = 0 AND o2.created_at BETWEEN '2018-01-10' AND NOW() )
LIMIT 50;
Hard to follow but I think you want:
SELECT sub.order_id, sub.line_item_id, sub.email, o.name
FROM
(SELECT o.email, MIN(o.id) AS order_id, MIN(i.id) AS line_item_id
FROM orders o
INNER JOIN line_items i
ON o.id = i.order_id
WHERE o.status = 0
AND o.created_at BETWEEN '2018-01-10' AND NOW()
GROUP BY o.email) sub
LEFT JOIN orders o
ON sub.order_id = o.id
In the sub-query, select each email along with the first order ID and line item ID. Then join this back to orders to pull the order name. This does assume that the MIN(line_item) will show up with the MIN(order_id) for each email, so you'll have to let me know if that is not a valid assumption.
Select distinct is returning indistinct rows. Why?
I want to return distinct shops.
Here is my sql statement:
SELECT
DISTINCT s.*, p.p_id
FROM
shop s
INNER JOIN product_shop ps on s.s_id = ps.s_id
INNER JOIN product p ON p.p_id = ps.p_id
WHERE
s.country = 'new zealand'
Here is the result:
The product (p.p_id) needs to not be distinct, as I want to return a list of shops that have a specific product. But the Shop needs to be distinct.
What am I doing wrong?
Returned rows are distinct. Distinct is applied to all returned row, not to single column. Yes, p_id is same for two rows. But if you compare all columns, there are differences between them.
If you want distinct shops - don't include in select columns from other tables, because it can cause duplicates as in your example.
Simply don't include p.p_id within your selection.
I.e.
SELECT DISTINCT
s.*
FROM shop s
....
Well, If you will look at your entire output, you can see the p_id(the last column) is different for each row. Distinct applies to the entire record, not just one column.
You can either drop the p_id from your select, or use group by and decide which one of the p_id you want, perhaps max? :
SELECT
s.*, max(p.p_id)
FROM
shop s
INNER JOIN product_shop ps on s.s_id = ps.s_id
INNER JOIN product p ON p.p_id = ps.p_id
WHERE
s.country = 'new zealand'
GROUP BY s.id
What i would like to archieve:
Getting the correct sum of the total amount of the orders that has been cancelled of user id 2002.
Some pre information:
I am having deals which that has its price in deals.price and its id in deals.ID
I then have orders with a foreign key to deals.ID
Running this SQL:
select SUM(deals.price), orders.* from orders
JOIN deals ON deals.ID = orders.deal_id
where orders.user_id = 2002
and orders.cancelled = 1
Works just fine.
Here is where i get stuck:
As an addition to deals, each deals has products with their own prices.
Table is called deal_products, deal_products.price hold the price and deal_products.product_id has the ID of it.
A order is attached to a deal product in another table called order_products, where order_products.product_id = deal_products.product_id
To sum up: I would like to do is including a if inside the above SQL.
If a order has a row in order_products, get the order_products.product_id and find the price in deal_products (price) and use this instead of deals.price when SUM()'ing.
If there is no row it should use deals.price.
How can this be archieved? To first look in another table if there is a entry, and then further look in to a third table and get a value to use?
You can use COALESCE + LEFT JOIN:
select SUM(coalesce(dp.price, d.price)), o.*
from orders o JOIN deals d ON d.ID = o.deal_id
LEFT JOIN order_products op on op.order_id = o.id
LEFT JOIN deal_products dp on op.product_id = dp.product_id
where o.user_id = 2002 and o.cancelled = 1
group by ...;
COALESCE function returns first not null operand
LEFT [OUTER] JOIN = [INNER] JOIN + all rows of the structure on the left side of the LEFT JOIN keyword, which don't match the ON clause in the right structure.