SQL: After joined table query, put rows with data a both tables first - mysql

With 1001 possibilities to use MySQL, I've come up with a requirement for myself that I want to figure out, but don't know how. I also ran a Google Search of course and checked Stack Overflow and MySQL Docs, but I didn't get the answer I was looking for.
The situation:
I have 2 tables. One called and containing customers and one containing customer_progress . For your imaging, the customer_progress table can optionally contain data about the progress/status for a customer.
My goal was to join the data from the two tables, and put the customers that do have progress data on top, followed by the customers that do not have progress data; all in 1 query.
So I started writing a query, much like the following:
SELECT
*
FROM
customers AS c
LEFT JOIN
customer_progress AS p
ON
p.customer_id = c.id AND p.year = 2011 // Joining them and selecting the progress for a certain year.
WHERE
c.active = 1;
How can I put the customers that do have a progress record first, and then afterwards all the customers that do not have a progress record?
I don't know if I am going the right way with the query or that I need to approach this from a whole different angle. Using a ORDER BY made no sense.

You need to select a customer progress_field that you can order by so that you add something like
order by custer_progress_field desc;
at the end of your select statement.
For example:
SELECT
c.filed1, p.field1
FROM
customers AS c
LEFT JOIN
customer_progress AS p
ON
p.customer_id = c.id AND p.year = 2011 // Joining them and selecting the progress for a certain year.
WHERE
c.active = 1;
Order by p.field1 desc;

You need to add something like
ORDER BY IF(p.customer_id IS NULL,1,0), [other fields]
// or you can do just if sorting by customer_id is acceptable
ORDER BY p.customer_id DESC
You can check more details about how columns with NULL values are treated by ORDER BY on http://dev.mysql.com/doc/refman/5.0/en/working-with-null.html

Related

MySQL View in place of subquery does not return the same result

The query below is grabbing some information about a category of toys and showing the most recent sale price for three levels of condition (e.g., Brand New, Used, Refurbished). The price for each sale is almost always different. One other thing - the sales table row id's are not necessarily in chronological order, e.g., a toy with a sale id of 5 could have happened later than a toy with a sale id of 10).
This query works but is not performant. It runs in a manageable amount of time, usually about 1s. However, I need to add yet another left join to include some more data, which causes the query time to balloon up to about 9s, no bueno.
Here is the working but nonperformant query:
SELECT b.brand_name, t.toy_id, t.toy_name, t.toy_number, tt.toy_type_name, cp.catalog_product_id, s.date_sold, s.condition_id, s.sold_price FROM brands AS b
LEFT JOIN toys AS t ON t.brand_id = b.brand_id
JOIN toy_types AS tt ON t.toy_type_id = tt.toy_type_id
LEFT JOIN catalog_products AS cp ON cp.toy_id = t.toy_id
LEFT JOIN toy_category AS tc ON tc.toy_category_id = t.toy_category_id
LEFT JOIN (
SELECT date_sold, sold_price, catalog_product_id, condition_id
FROM sales
WHERE invalid = 0 AND condition_id <= 3
ORDER BY date_sold DESC
) AS s ON s.catalog_product_id = cp.catalog_product_id
WHERE tc.toy_category_id = 1
GROUP BY t.toy_id, s.condition_id
ORDER BY t.toy_id ASC, s.condition_id ASC
But like I said it's slow. The sales table has about 200k rows.
What I tried to do was create the subquery as a view, e.g.,
CREATE VIEW sales_view AS
SELECT date_sold, sold_price, catalog_product_id, condition_id
FROM sales
WHERE invalid = 0 AND condition_id <= 3
ORDER BY date_sold DESC
Then replace the subquery with the view, like
SELECT b.brand_name, t.toy_id, t.toy_name, t.toy_number, tt.toy_type_name, cp.catalog_product_id, s.date_sold, s.condition_id, s.sold_price FROM brands AS b
LEFT JOIN toys AS t ON t.brand_id = b.brand_id
JOIN toy_types AS tt ON t.toy_type_id = tt.toy_type_id
LEFT JOIN catalog_products AS cp ON cp.toy_id = t.toy_id
LEFT JOIN toy_category AS tc ON tc.toy_category_id = t.toy_category_id
LEFT JOIN sales_view AS s ON s.catalog_product_id = cp.catalog_product_id
WHERE tc.toy_category_id = 1
GROUP BY t.toy_id, s.condition_id
ORDER BY t.toy_id ASC, s.condition_id ASC
Unfortunately, this change causes the query to no longer grab the most recent sale, and the sales price it returns is no longer the most recent.
Why is it that the table view doesn't return the same result as the same select as a subquery?
After reading just about every top-n-per-group stackoverflow question and blog article I could find, getting a query that actually worked was fantastic. But now that I need to extend the query one more step I'm running into performance issues. If anybody wants to sidestep the above question and offer some ways to optimize the original query, I'm all ears!
Thanks for any and all help.
The solution to the subquery performance issue was to use the answer provided here: Groupwise maximum
I thought that this approach could only be used when querying a single table, but indeed it works even when you've joined many other tables. You just have to left join the same table twice using the s.date_sold < s2.date_sold join condition and make sure the where clause looks for the null value in the second table's id column.

MYSQL join on multiple criteria not working

I have a query which uses LEFT JOIN with multiple criteria to fetch the data. However, the query does not return any data when I use more than one AND clause in the conditions.
Eg:
LEFT JOIN tblName
on x.ABC=y.ABC
AND x.MNO="AnyValue"
AND x.UserId=1
does not return any data.
However when I remove the last AND clause (i.e LEFT JOIN tblName on x.ABC=y.ABC AND x.MNO="AnyValue"), it gives me wrong result. But yes it atleast return some data.
Let me know how to add multiple conditions to get the query working.
I want to get results using the above first condition, i.e LEFT JOIN tblName on x.ABC=y.ABC AND x.MNO="AnyValue" AND x.UserId=1
Your AND ... criteria needs to be in the WHERE part of the query.
First, lets make sure you understand what a LEFT JOIN is. Try to think of it this way. The left side is how I think in the sequential order of the FROM clause. You read left-to-right, so the first table is my LEFT side which is what I ALWAYS want. The right side (next table in the query) is optional, but if it exists, great, I have more detail.
So take a scenario of people and orders. I want a list of all people. But if they have an order, thats even better.
select
P.LastName,
O.OrderID,
O.OrderDate
from
People P
LEFT JOIN Order O
on P.PersonID = O.PersonID
This will do just that. Show all people and IF they have an order, that comes along for the ride. If they have multiple orders, it will show a row for ALL orders they had.
Now, expand on criteria to the next table. Lets say I only wanted to show orders since 2018. That would become part of the LEFT JOIN "ON" clause because I only care about orders portion of the requirement.
LEFT JOIN Order O
on P.PersonID = O.PersonID
AND O.OrderDate >= '2018-01-01'
I would still get ALL people, but only order info will show for those with an order date on/after Jan 1, 2018.
Now, if you only cared about ONLY PEOPLE who HAD ORDERS AND the orders were on after 2018, that would basically eliminate the need for a LEFT JOIN and become a standard (INNER) JOIN
JOIN Order O
on P.PersonID = O.PersonID
AND O.OrderDate >= '2018-01-01'
JOIN Implies the record in the ORDER side table MUST be found
So in your scenario, where is your "x" and "y" alias name coming from. Completely unclear given the context, but hopefully the above clarification will help you resolve your query. If not, update it with more detail and clarification what you are trying to get out of it.

Join all link table entries in one single row

I've been trying to get a query which would allow me to put all music genres of an event (Link table) in a single row but I have been quite unsuccessful so far; it constantly returns 2 rows with the same information about the event in each but changing the music genre entries at the LEFT JOIN. Here's my query:
SELECT
events.*
, music_genres_link.*
FROM events
LEFT JOIN music_genres_link
ON events.id = music_genres_link.event_id
WHERE events.id=1
ORDER BY date DESC
And here's what it returns:
How do I get these two rows together in a single one? I need both the genre_title and genre_id columns.
I'd like to get the whole event row and left join all the music genres found on the link table to the left of the result, as such:
[event result] [music_genre_1 (id, title)] [music_genre_2 (id, title)] etc
I suspect you just want group_concat():
SELECT e.*, GROUP_CONCAT(mgl.genre_title) as genres
FROM events e LEFT JOIN
music_genres_link mgl
ON e.id = mgl.event_id
WHERE e.id = 1
GROUP BY e.id;
This assumes that you want the genre_title only. If you want the genre_id, then you can add another column.
It seems that you have 2 rows that match the join in music_genres_link. You can see that from the two 'gender_id' values - 5 and 295.
Ok now that you have edited your question, you want something that is not typical for SQL. You can achieve that with PIVOT but you dont have limit for the amount of genres and i wouldn't recommend it. You need to rethink your tables and what you want to achieve.

Use SELECT through three table

I tried to write a query, but unfortunately I didn't succeed.
I want to know how many packages delivered over a given period by a person.
So I want to know how many packages were delivered by John (user_id = 1) between 01-02-18 and 28-02-18. John drives another car (another plate_id) every day.
(orders_drivers.user_id, plates.plate_name, orders.delivery_date, orders.package_amount)
I have 3 table:
orders with plate_id delivery_date package_amount
plates with plate_id plate_name
orders_drivers with plate_id plate_date user_id
I tried some solutions but didn't get the expected result. Thanks!
Try using JOINS as shown below:
SELECT SUM(o.package_amount)
FROM orders o INNER JOIN orders_drivers od
ON o.plate_id=od.plate_id
WHERE od.user_id=<the_user_id>;
See MySQL Join Made Easy for insight.
You can also use a subquery:
SELECT SUM(o.package_amount)
FROM orders o
WHERE EXISTS (SELECT 1
FROM orders_drivers od
WHERE user_id=<user_id> AND o.plate_id=od.plate_id);
SELECT sum(orders.package_amount) AS amount
FROM orders
LEFT JOIN plates ON orders.plate_id = orders_drivers.plate_id
LEFT JOIN orders_driver ON orders.plate_id = orders_drivers.plate_id
WHERE orders.delivery_date > date1 AND orders.delivery_date < date2 AND orders_driver.user_id = userid
GROUP BY orders_drivers.user_id
But seriously, you need to ask questions that makes more sense.
sum is a function to add all values that has been grouped by GROUP BY.
LEFT JOIN connects all tables by id = id. Any other join can do this in this case, as all ids are unique (at least I hope).
WHERE, where you give the dates and user.
And GROUP BY userid, so if there are more records of the same id, they are returned as one (and summed by their pack amount.)
With the AS, your result is returned under the name 'amount',
If you want the total of packageamount by user in a period, you can use this query:
UPDATE: add a where clause on user_id, to retrieve John related data
SELECT od.user_id
, p.plate_name
, SUM(o.package_amount) AS TotalPackageAmount
FROM orders_drivers od
JOIN plates p
ON o.plate_id = od.plate_id
JOIN orders o
ON o.plate_id = od.plate_id
WHERE o.delivery_date BETWEEN convert(datetime,01/02/2018,103) AND convert(datetime,28/02/2018,103)
AND od.user_id = 1
GROUP BY od.user_id
, p.plate_name
It groups rows on user_id and plate_name, filter a period of delivery_date(s) and then calculate the sum of packageamount for the group

Query grouped by month on multiple JOINs

Ok, I'm grouping a query by month so I get a record for each month of the year, but I need to figure out how to do this: basically grouping by month on two different JOIN statements. I'm trying to get the count, or sum, of quantities in each child table if possible in one query. I could just loop through in php, but I'm trying to find a way to do in mysql.
Here's a sample
SELECT SUM(purchase_quantity),SUM(sales_quantity)
FROM products p
INNER JOIN purchase_order_lines sl ON pl.product_id = p.id
INNER JOIN purchase_orders po ON pl.purchase_order_id = po.id
INNER JOIN sales_order_lines sl ON sl.product_id = p.id
INNER JOIN sales_orders so ON sl.sales_order_id = so.id
GROUP BY YEAR(so.posted & po.posted), MONTH(so.posted & po.posted)
I know there's no way this would work, and the logic is ridiculous, and this isn't exactly the structure of my database, but just an idea of how things are linked and wwhat I'm trying to do. I'm thinking it would have to be done with subqueries in the SELECT statement, but I haven't come up with anything yet. I'll keep thinking about it, but if anyone has any ideas, that would be awesome.
because your grouping your result, you will get a count of one. why don't you try running a separate statement that will count the result and a separate statement to group it.