Two Select Queries on same table - mysql

I am struggling to find an answer to this issue.
I have a table where I can have multiple order ids for one customer (email)
email (unique), order_id
I need to make a query where I pass in any order_id for a customer, and it returns ALL their orders.
I have two select queries I need to combine into one query to save on load time.
SELECT email FROM orders WHERE order_id = '1234567890'
SELECT order_id FROM orders WHERE email = 'email_found_from_first_query'

An old school way of using in can be used here:
SELECT order_id
FROM orders
WHERE email IN (
SELECT email
FROM orders
WHERE order_id = '1234567890'
)
query is little expensive.

One method uses a correlated subquery:
select o.order_id
from orders o
where o.email = (select o2.email from orders o2 where order_id = '1234567890')

This seems best suited for a simple JOIN. https://www.mysqltutorial.org/mysql-join/
SELECT orig_order.email, all_orders.order_id
FROM orders as orig_order
JOIN orders as all_orders ON orig_order.email = all_orders.email
WHERE orig_order.order_id = '1234567890'

Related

SQL How to select original(distinct) values from table without using distinct, group by and over keywords?

Currently I'm studying and I received task to write query (join 4 tables: people, goods, orders and order details). So main table Order_details has two columns: Order_id and Good_id, in order to make possible to have many goods in one order (e.g. order with id = 1 has 3 rows in Order_details table but has different goods primary keys in each row).
So the problem is that I don't know any other possible methods(besides using group by, distinct or over()) to receive only one row of each order in Order_details table (like I would get by using for example Distinct keyword). I'm receiving completely same rows of each order (with same Order_id and Good_id) but i don't know how to get only one row of each order.
Here's my query(so basically i need to select sum of all goods in order but i don't think that it really matters in my problem) and scheme (if it'll help)
By the way I'm working with MYSQL.
SELECT
Order_details.Order_id,
Orders.Date, People.First_name,
People.Surname,
(
SELECT SUM(Goods.Price * Order_details.Quantity)
FROM Order_details, Goods
WHERE Order_details.Good_id = Goods.Good_id
AND Order_details.Order_id = Orders.Order_id
) AS Total_price
FROM Order_details, Goods, Orders, People
WHERE Order_details.Order_id = Orders.Order_id
AND Order_details.Good_id = Goods.Good_id
AND Order_details.Order_id = Orders.Order_id
AND Orders.Person_id = People.Person_id
ORDER BY Order_id ASC;
I have tried several methods, but still cant figure it out. Maybe somehow it is possible with subquery? But i'm not sure...
(I have tried method with UNION but it's not the key as well)
Remove the Goods and Order_details tables from the FROM clause and the corresponding conditions in the WHERE clause. You are not selecting anything from it anyway, except the SUM in the subselect. The Order_id can be selected from the Orders table. The join is just causing multiple rows per order.
Also please don't join with comma. Use the JOIN .. ON syntax. This makes it easier to see if the join conditions are reasonable.
SELECT
Orders.Order_id
Orders.Date,
People.First_name,
People.Surname,
(
SELECT SUM(Goods.Price * Order_details.Quantity)
FROM Order_details
JOIN Goods ON Order_details.Good_id = Goods.Good_id
WHERE Order_details.Order_id = Orders.Order_id
) AS Total_price
FROM Orders
JOIN People ON Orders.Person_id = People.Person_id
ORDER BY Orders.Order_id ASC;
you can use row_number() for this kind of thing it will assign a row number based on your criteria and then you can just pick the rows where the value is 1.
with t as (SELECT
Order_details.Order_id,
Orders.Date, People.First_name,
People.Surname,
row_number() over (
partition by order_id, good_id
order by order_id, good_id) rn,
(
SELECT SUM(Goods.Price * Order_details.Quantity)
FROM Order_details, Goods
WHERE Order_details.Good_id = Goods.Good_id
AND Order_details.Order_id = Orders.Order_id
) AS Total_price
FROM Order_details, Goods, Orders, People
WHERE Order_details.Order_id = Orders.Order_id
AND Order_details.Good_id = Goods.Good_id
AND Order_details.Order_id = Orders.Order_id
AND Orders.Person_id = People.Person_id
ORDER BY Order_id ASC)
select * from t where rn = 1

Mysql subquery slow

I have two tables that track sales:
orders order_line_items
------ ----------------
id id
customer_id order_id
created_datetime item_id
quantity
was_paid_for
An order can have many order_line_items. For some orders, all of the line items have been paid for. For other orders, they have not all been paid for.
I am trying to fetch a list of all the orders for a specific customer, and indicate if the order was fully paid for, or not. I have it working with this query:
SELECT o.id,
(SELECT count(*) from order_line_items WHERE order_id = o.id AND was_paid_for = 0) = 0 as isFullyPaid
FROM orders o
WHERE o.customer_id = 12345
However some customers have 1000+ orders and the query takes 70 seconds to run (this is a simplified example, the real one joins in five other tables).
Is indexes the only way to speed this up? Thanks.
You could try adding the following index to the order_line_items:
CREATE INDEX idx ON order_line_items(order_id, was_paid_for);
That being said, you also could try using the following join version of your query:
SELECT o.id, COALESCE(oli.cnt, 0) = 0 AS isFullyPaid
FROM orders o
LEFT JOIN
(
SELECT order_id, COUNT(*) AS cnt
FROM order_line_items
WHERE was_paid_for = 0
GROUP BY order_id
) oli
ON oli.order_id = o.id
WHERE o.customer_id = 12345;
The same index suggestion applied to the above join query.

SQL retrieving filtered value in subquery

in this cust_id is a foreign key and ords returns the number of orders for every customers
SELECT cust_name, (
SELECT COUNT(*)
FROM Orders
WHERE Orders.cust_id = Customers.cust_id
) AS ords
FROM Customers
The output is correct but i want to filter it to retrieve only the customers with less than a given amount of orders, i don't know how to filter the subquery ords, i tried WHERE ords < 2 at the end of the code but it doesn't work and i've tried adding AND COUNT(*)<2 after the cust_id comparison but it doesn't work. I am using MySQL
Use the HAVING clause (and use a join instead of a subquery).....
SELECT Customers.cust_id, Customers.cust_name, COUNT(*) ords
FROM Orders, Customers
WHERE Orders.cust_id = Customers.cust_id
GROUP BY 1,2
HAVING COUNT(*)<2
If you want to include people with zero orders you change the join to an outer join.
There is no need for a correlated subquery here, because it calculates the value for each row which doesn't give a "good" performance. A better approach would be to use a regular query with joins, group by and having clause to apply your condition to groups.
Since your condition is to return only customers that have less than 2 orders, left join instead of inner join would be appropriate. It would return customers that have no orders as well (with 0 count).
select
cust_name, count(*)
from
customers c
left join orders o on c.cust_id = o.cust_id
group by cust_name
having count(*) < 2

Converting Multiple subqueries with GROUP BY to JOIN

I'm working on a simple ordering system in MySQL and I came across this snag that I'm hoping some SQL genius can help me out with.
I have a table for Orders, Payments (with a foreign key reference to the Order table), and OrderItems (also, with a foreign key reference to the Order table) and what I would like to do is get the total outstanding balance (Total and Paid) for the Order with a single query. My initial thought was to do something simple like this:
SELECT Order.*, SUM(OrderItem.Amount) AS Total, SUM(Payment.Amount) AS Paid
FROM Order
JOIN OrderItem ON OrderItem.OrderId = Order.OrderId
JOIN Payment ON Payment.OrderId = Order.OrderId
GROUP BY Order.OrderId
However, if there are multiple Payments or multiple OrderItems, it messes up Total or Paid, respectively (eg. One OrderItem record with an amount of 100 along with two Payment Records will produce a Total of 200).
In order to overcome this, I can use some subqueries in the following way:
SELECT Order.OrderId, OrderItemGrouped.Total, PaymentGrouped.Paid
FROM Order
JOIN (
SELECT OrderItem.OrderId, SUM(OrderItem.Amount) AS Total
FROM OrderItem
GROUP BY OrderItem.OrderId
) OrderItemGrouped ON OrderItemGrouped.OrderId = Order.OrderId
JOIN (
SELECT Payment.OrderId, SUM(Payment.Amount) AS Paid
FROM Payment
GROUP BY Payment.OrderId
) PaymentGrouped ON PaymentGrouped.OrderId = Order.OrderId
As you can imagine (and as an EXPLAIN on this query will show), this is not exactly an optimal query so, I'm wondering, is there any way to convert these two subqueries with GROUP BY statements into JOINs?
The following is likely to be faster with the right indexes:
select o.OrderId,
(select sum(oi.Amount)
from OrderItem oi
where oi.OrderId = o.OrderId
) as Total,
(select sum(p.Amount)
from Payment p
where oi.OrderId = o.OrderId
) as Paid
from Order o;
The right indexes are OrderItem(OrderId, Amount) and Payment(OrderId, Amount).
I don't like writing aggregation queries this way, but it can sometimes help performance in MySQL.
Some answers have already suggested using a correlated subquery, but have not really offered an explanation as to why. MySQL does not materialise correlated subqueries, but it will materialise a derived table. That is to say with a simplified version of your query as it is now:
SELECT Order.OrderId, OrderItemGrouped.Total
FROM Order
JOIN (
SELECT OrderItem.OrderId, SUM(OrderItem.Amount) AS Total
FROM OrderItem
GROUP BY OrderItem.OrderId
) OrderItemGrouped ON OrderItemGrouped.OrderId = Order.OrderId;
At the start of execution MySQL will put the results of your subquery into a temporary table, and hash this table on OrderId for faster lookups, whereas if you run:
SELECT Order.OrderId,
( SELECT SUM(OrderItem.Amount)
FROM OrderItem
WHERE OrderItem.OrderId = OrderId
) AS Total
FROM Order;
The subquery will be executed once for each row in Order. If you add something like WHERE Order.OrderId = 1, it is obviously not efficient to aggregate the entire OrderItem table, hash the result to only lookup one value, but if you are returning all orders then the inital cost of creating the hash table will make up for itself it not having to execute the subquery for every row in the Order table.
If you are selecting a lot of rows and feel the materialisation will be of benefit, you can simplifiy your JOIN query as follows:
SELECT Order.OrderId, SUM(OrderItem.Amount) AS Total, PaymentGrouped.Paid
FROM Order
INNER JOIN OrderItem
ON OrderItem.OrderID = Order.OrderID
INNER JOIN
( SELECT Payment.OrderId, SUM(Payment.Amount) AS Paid
FROM Payment
GROUP BY Payment.OrderId
) PaymentGrouped
ON PaymentGrouped.OrderId = Order.OrderId;
GROUP BY Order.OrderId, PaymentGrouped.Paid;
Then you only have one derived table.
What about something like this:
SELECT Order.OrderId, (
SELECT SUM(OrderItem.Amount)
FROM OrderItem as OrderItemGrouped
where
OrderItemGrouped.OrderId = Order.OrderId
), AS Total,
(
SELECT SUM(Payment.Amount)
FROM Payment as PaymentGrouped
where
PaymentGrouped.OrderId = Order.OrderId
) as Paid
FROM Order
PS: You win again #Gordon xD
Select o.orderid, i.total, s.paid
From orders o
Left join (select orderid, sum(amount)
From orderitem) i
On i.orderid = o.orderid
Ieft join (select orderid, sum(amount)
From payments) s
On s.orderid = o.orderid

MySQL - Select last record from second table matching with first table

I have two tables customers and orders, below is the structure.
Table - customers
id
customer_name
Table - orders
id
order_id
customer_id
customers table have customers records and orders table have orders placed by customers,
customer_id in orders table is linked to the id field of customers table.
Now one customer can have zero or one or more than one orders, i want to get the last order placed by customers only.
when i run the following query a simple invisible join, it returns all the orders by the customer
SELECT customers.customer_name,orders.order_id FROM orders,customers WHERE orders.customer_id=customers.id
I have also tried different JOIN statements but cannot get the last order by the customer, i want to get it in one SQL query for all customers.
Thank you in advance for your help.
In MySQL there is just few ways to make it work (that I now actually). The first one is sort your table as desc before the join:
SELECT c.customer_name, o.customer_id, o.order_id,o.id FROM customers c
INNER JOIN orders o
ON o.id = (SELECT id FROM orders WHERE customer_id = c.id ORDER BY id DESC LIMIT 1)
Using in real time is the only way to get it done, but if you need to make some join on not real time you can create a temporary table or a alias table sorting it to make your select, like this:
CREATE TABLE tmp_your_table AS
SELECT * FROM orders ORDER BY id DESC
So now you are able to make this join work:
SELECT c.customer_name, o.customer_id, o.order_id,o.id FROM customers c
INNER JOIN tmp_your_table o ON o.id = tmp_your_table.id
Try this query
SELECT
c.customer_name,
max(o.order_id)
FROM
customers c
INNER JOIN
orders o
ON
o.customer_id = c.id
GROUP BY
c.customer_name
You don't have any date field in the order table so assuming the latest order will be the one which has max(order_id).
Try this query
SELECT
c.customer_name,
o.order_id
FROM
customers c
INNER JOIN
orders o
ON
o.customer_id = c.id
ORDER BY
o.id desc
LIMIT 1;