Mysql how can I select all orders with both attributes - mysql

I have a table call orders with the ordernumber and another table call order_detail with ordernumber and product number.
I tried to query:
Select a.* from orders as a
join
order_detail as b on a.ordernum = b.ordernum
where b.prodnum ='Bike28B' or b.prodnum = 'ridinggloves'
group by ordernum;
It will give the the result with orders having Bike28B but not the result with the order have both attributes.

You have unintentionally stumbled upon mysql's quirky grouping implementation, which does non-standard things. Try listing all columns from
select a.*
from orders as a
join order_detail as b
on a.ordernum = b.ordernum
and b.prodnum in ('Bike28B', 'ridinggloves')
group by <list every column in order>
having count(*) = 2
The having clause ensures both child rows were found.
Note also the use of in (...) instead of or, which will have a much better chance if using an index (if one exists) on that column. I also moved that condition into the join clause, because it's a join condition (although it would work in both places).

Related

mysql understanding group by with a joined table

So I have the "main" table (A) with fields: id, order_number, order_name and table (B) with fields: id, fk.order_number, tracking_number
Table (A) is responsible for keeping track of each order, while table (B) stores all associated tracking information per each order.
What I am trying to accomplish is to query each order from table A and join table B to show the first tracking number that has been stored for each order, almost like a limit 1 (return only the first stored tracking number for each order).
How I am doing this currently is a join between table A and table B on the order_number field, but I am using the GROUP BY tableA.order_number at the end of the statement.
select tablea.order_number, tablea.order_name, tableb.tracking_number
from tablea
join table b
on tablea.order_number = tableb.order_number
group by tablea.order_number
I guess the question revolves around, what is the default group by ordering when you return multiple rows back from the joined table?
For example, in table A, there is only 1 row, while in tale B there are 2 rows (2 tracking numbers for the order). So, when I group by in this case, does that always take the first match from the joined table where the condition matches the group by? If I removed group by, 2 rows would be returned.
I realize what is happening because I have the group by condition on tableA, and so it only shows the first row because both rows returned from the join have the same order number, which appears to be what I want (limit 1 tracking number per order), but I'm not sure if programmatically I actually did this correctly or if it happens this way because that is how the group by clause works and how I used it here. I just want to limit 1 tracking number from the tableB based on the order_number of table A.
Updated (with example query)
SELECT m.message_id, m.message_date, m.message_order_number, m.message_purchase_order, m.message_vendor_invoice, ve.vendor_email_display, concat(c.customer_first_name, ' ', c.customer_last_name) as customer_name,
min(ti.tracking_information_id) as tracking_information_id, ti.tracking_information_tracking_number, ti.tracking_information_tracking_number_status
FROM email.message m
JOIN email.customer c
ON m.message_tagged_customer_first = c.customer_id and m.message_tagged_customer_last = c.customer_id
JOIN vendor_email ve
ON m.message_sender = ve.vendor_email_id
LEFT JOIN tracking_information ti
ON m.message_order_number = ti.tracking_information_order_number
group by m.message_order_number
In this case, I want to return all information in message table, and the first matching row in table tracking
Group byis for aggregation function as MIN() ,, MAX() , COUNT() . .. and is for define respect which column the aggreagtion function must be performed..
If you are looking for a specific ordered result you should use ORDER BY that work for the columns value as is
select tablea.order_number, tablea.order_name, tableb.tracking_number
from tablea
join table b on tablea.order_number = tableb.order_number
order by tablea.order_number

sql SELECT query for 3 tables

I have 3 tables:
1. products(product_id,name)
2. orders(id,order_id,product_id)
3. factors(id,order_id,date)
I want to retrieve product names(products.name) where have similar order_id on a date in two last tables.
I use this query for this purpose:
select products.name
from products
WHERE products.product_id ~IN
(
SELECT distinct orders.product_id FROM orders WHERE
order_id IN (select order_id FROM factors WHERE
factors.datex ='2017-04-29') GROUP BY product_id
)
but no result. where is my mistake? how can I resolve that? thanks
Your query should be fine. I am rewriting it to make a few changes to the structure, but not the logic (this makes it easier for me to understand the query):
select p.name
from products p
where p.product_id in (select o.product_id
from orders o
where o.order_id in (select f.order_id
from factors f
where f.datex = '2017-04-29'
)
) ;
Notes on the changes:
When using multiple tables in a query, always qualify the column names.
Use table aliases. They make queries easier to write and to read.
SELECT DISTINCT and GROUP BY are unnecessary in IN subqueries. The logic of IN already handles (i.e. ignores) duplicates. And by explicitly including the operations, you run the risk of a less efficient query plan.
Why might your query not work?
factors.datex has a time component. If so, then this will work date(f.datex) = '2017-04-29'.
There are no factors on that date.
There are no orders that match factors on that date.
There are no products in the orders that match the factors on that date.
In factors table column name is date so it should be -
factors.date ='2017-04-29'
You have written -
factors.datex ='2017-04-29'

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

ORDER BY and LIMIT 1 in joined table

I've been trying to figure this one out for a long time but am starting to give up.
To simplify the case, let's say I've got 2 tables. Main table is articles and I'm left joining it with contracts. The contracts table has an end date. I only want to pick 1 (one) row from here per article, selecting the latest contract_to date.
I've tried tried something like LEFT JOIN contracts ON (contracts.article = articles.id) ORDER BY contract_to DESC LIMIT 1 but obviously it's not working.
How do I go about doing this?
Please pretend that the date ranges on each row in the contracts table below are different.
Also the latest date is not the same for all article contracts, so I can't just determine what the latest date is and then stick it into a WHERE clause.
To get the latest contract_to value, you'll need a MAX() aggregate. The proper way to do this is to use a subquery join to get only the article and MAX(contract_to) values, then join that with the remaining values of the row. Finally, that whole structure can be joined against the articles table.
SELECT
articles.*,
contracts.*
FROM
articles
/* Join against a subquery which returns only the article and latest contract_to */
LEFT JOIN (
SELECT article, MAX(contract_to) AS contract_to
FROM contracts
GROUP BY article
) maxcontract ON articles.article_id = maxcontract.article
/* and join that against the rest of the contracts table, on those two column values */
JOIN contracts
ON maxcontract.article = contracts.article
AND maxcontract.contract_to = contracts.contract_to
Since MySQL is lenient about the contents of the GROUP BY clause, this method may not actually be necessary, joining separately against the contracts table, and you could probably do it with the subquery join alone, but that won't work in most other RDBMS and this is really the right way to do it without relying on MySQL's weird behavior.
MySQL doesn't have the nice analytic functions that some DBMSes offer for this, but you could write (for example):
SELECT ...
FROM articles
LEFT
OUTER
JOIN ( SELECT article,
MAX(contract_to) AS contract_to
FROM contracts
GROUP
BY article
) articles_to_max_contracts
ON articles_to_max_contracts.article = articles.id
LEFT
OUTER
JOIN contracts
ON contracts.article = articles.id
AND contracts.contract_to = articles_to_max_contracts.contract_to
;
To get just the price, you can also do this with a correlated subquery:
select a.*,
(select price
from contracts c
where a.article = c.article
order by contract_from desc
limit 1
) as lastPrice
from articles a;
With an index on contracts(article, contract_from) this should even be relatively efficient.

Select corresponding records from another table, but just the last one

I have 2 tables authors and authors_sales
The table authors_sales is updated each hour so is huge.
What I need is to create a ranking, for that I need to join both tables (authors has all the author data while authors_sales has just sales numbers)
How can I create a final table with the ranking of authors ordering it by sales?
The common key is the: authorId
I tried with LEFT JOIN but I must be doing something wrong because I get all the authors_sales table, not just the last.
Any tip in the right direction much appreciated
If you're looking for aggregate data of the sales, you'd want to join the tables, group by the authorId. Something like...
select authors.author_id, SUM(author_sales.sale_amt) as total_sales
from authors
inner join author_sales on author_sales.author_id = authors.author_id
group by authors.author_id
order by total_sales desc
However (I couldn't distinguish from your question whether the above scenario or next is true), if you're only looking for the max value of the author_sales table (if the data in this table is already aggregated), you can join on a nested query for author_sales, such as...
select author.author_id, t.sales from authors
inner join
(select top 1 author_sales.author_id,
author_sales.sale_amt,
author_sales.some_identifier
from author_sales order by some_identifier desc) t
on t.author_id = author.author_id
order by t.sales desc
The some_identifier would be how you determine which record is the most recent for author_sales, whether it is a timestamp of when it was inserted or an incremental primary key, however it is set up. Depending on if the data in author_sales is aggregated already, one of these two should do it for you...
select a.*, sum(b.sales)
from authors as a
inner join authors_sales as b
using authorId
group by b.authorId
order by sum(b.sales) desc;
/* assuming column sales = total for each row in authors_sales */