How to order, group, order with mySQL - mysql

Here is a simplified version of my table
tbl_records
-title
-created
-views
I am wondering how I can make a query where they are grouped by title, but the record that is returned for each group is the most recently created. I then will order it by views.
One way I guess is to do a sub query and order it by created and then group it by title and then from those results order it by views. I guess there is a better way though.
Thanks
EDIT:
SAMPLE DATA:
-title: Gnu Design
-created: 2009-11-11 14:47:18
-views: 104
-title: Gnu Design
-created:2010-01-01 21:37:09
-views:9
-title: French Connection
-created:2010-05-01 09:27:19
-views:20
I would like the results to be:
-title: French Connection
-created:2010-05-01 09:27:19
-views:20
-title: Gnu Design
-created:2010-01-01 21:37:09
-views:9
Only the most recent Gnu Design is shown and then the results are ordered by views.

This is an example of the greatest-n-per-group problem that appears frequently on StackOverflow.
Here's my usual solution:
SELECT t1.*
FROM tbl_records t1
LEFT OUTER JOIN tbl_records t2 ON (t1.title = t2.title AND
(t1.created < t2.created OR t1.created = t2.created AND t1.primarykey < t2.primarykey))
WHERE t2.title IS NULL;
Explanation: find the row t1 for which no other row t2 exists with the same title and a greater created date. In case of ties, use some unique key to resolve the tie, unless it's okay to get multiple rows per title.

select i.*, o.views from
(
select
title
, max(created) as last_created
from tbl_records
group by title
) i inner join tbl_records o
on i.title = o.title and i.last_created = o.created
order by o.views desc
I'm assuming that the aggregation to be applied to views is count(), but could well be wrong (you'll need to have some way of defining which measure of views you wish to have for the lastest created title). Hope that helps.
EDIT: have seen your sample data and edited accordingly.

SELECT title,
MAX(created),
views
FROM table
GROUP BY title
ORDER BY views DESC

Related

MySQL Inner join naming error?

http://sqlfiddle.com/#!9/e6effb/1
I'm trying to get a top 10 by revenue per brand for France on december.
There are 2 tables (first table has date, second table has brand and I'm trying to join them)
I get this error "FUNCTION db_9_d870e5.SUM does not exist. Check the 'Function Name Parsing and Resolution' section in the Reference Manual"
Is my use of Inner join there correct?
It's because you had an extra space after SUM. Please change it from
SUM (o1.total_net_revenue)to SUM(o1.total_net_revenue).
See more about it here.
Also after correcting it, your query still had more error as you were not selecting order_id on your intermediate table i2 so edited here as :
SELECT o1.order_id, o1.country, i2.brand,
SUM(o1.total_net_revenue)
FROM orders o1
INNER JOIN (
SELECT i1.brand, SUM(i1.net_revenue) AS total_net_revenue,order_id
FROM ordered_items i1
WHERE i1.country = 'France'
GROUP BY i1.brand
) i2
ON o1.order_id = i2.order_id AND o1.total_net_revenue = i2.total_net_revenue
AND o1.total_net_revenue = i2.total_net_revenue
WHERE o1.country = 'France' AND o1.created_at BETWEEN '2016-12-01' AND '2016-12-31'
GROUP BY 1,2,3
ORDER BY 4
LIMIT 10`
--EDIT stack Fan is correct that the o2.total_net_revenue exists. My confusion was because the data structure duplicated three columns between the tables, including one that was being looked for.
There were a couple errors with your SQL statement:
1. You were referencing an invalid column in your outer-select-SUM function. I believe you're actually after i2.total_net_revenue.
The table structure is terrible, the "important" columns (country, revenue, order_id) are duplicated between the two tables. I would also expect the revenue columns to share the same name, if they always have the same values in them. In the example, there's no difference between i1.net_revenue and o1.total_net_revenue.
In your inner join, you didn't reference i1.order_id, which meant that your "on" clause couldn't execute correctly.
PROTIP:
When you run into an issue like this, take all the complicated bits out of your query and get the base query working correctly first. THEN add your functions.
PROTIP:
In your GROUP BY clause, reference the actual columns, NOT the column numbers. It makes your query more robust.
This is the query I ended up with:
SELECT o1.order_id, o1.country, i2.brand,
SUM(i2.total_net_revenue) AS total_rev
FROM orders o1
INNER JOIN (
SELECT i1.order_id, i1.brand, SUM(i1.net_revenue) AS total_net_revenue
FROM ordered_items i1
WHERE i1.country = 'France'
GROUP BY i1.brand
) i2
ON o1.order_id = i2.order_id AND o1.total_net_revenue = i2.total_net_revenue
AND o1.total_net_revenue = i2.total_net_revenue
WHERE o1.country = 'France' AND o1.created_at BETWEEN '2016-12-01' AND '2016-12-31'
GROUP BY o1.order_id, o1.country, i2.brand
ORDER BY total_rev
LIMIT 10

Incorrect group by and order by merge

I have couple tables joined in MySQL - one has many others.
And try to select items from one, ordered by min values from another table.
Without grouping in seems to be like this:
Code:
select `catalog_products`.id
, `catalog_products`.alias
, `tmpKits`.`minPrice`
from `catalog_products`
left join `product_kits` on `product_kits`.`product_id` = `catalog_products`.`id`
left join (
SELECT MIN(new_price) AS minPrice, id FROM product_kits GROUP BY id
) AS tmpKits on `tmpKits`.`id` = `product_kits`.`id`
where `category_id` in ('62')
order by product_kits.new_price ASC
Result:
But when I add group by, I get this:
Code:
select `catalog_products`.id
, `catalog_products`.alias
, `tmpKits`.`minPrice`
from `catalog_products`
left join `product_kits` on `product_kits`.`product_id` = `catalog_products`.`id`
left join (
SELECT MIN(new_price) AS minPrice, id FROM product_kits GROUP BY id
) AS tmpKits on `tmpKits`.`id` = `product_kits`.`id`
where `category_id` in ('62')
group by `catalog_products`.`id`
order by product_kits.new_price ASC
Result:
And this is incorrect sorting!
Somehow when I group this results, I get id 280 before 281!
But I need to get:
281|1600.00
280|2340.00
So, grouping breaks existing ordering!
For one, when you apply the GROUP BY to only one column, there is no guarantee that the values in the other columns will be consistently correct. Unfortunately, MySQL allows this type of SELECT/GROUPing to happen other products don't. Two, the syntax of using an ORDER BY in a subquery while allowed in MySQL is not allowed in other database products including SQL Server. You should use a solution that will return the proper result each time it is executed.
So the query will be:
For one, when you apply the GROUP BY to only one column, there is no guarantee that the values in the other columns will be consistently correct. Unfortunately, MySQL allows this type of SELECT/GROUPing to happen other products don't. Two, the syntax of using an ORDER BY in a subquery while allowed in MySQL is not allowed in other database products including SQL Server. You should use a solution that will return the proper result each time it is executed.
So the query will be:
select CP.`id`, CP.`alias`, TK.`minPrice`
from catalog_products CP
left join `product_kits` PK on PK.`product_id` = CP.`id`
left join (
SELECT MIN(`new_price`) AS "minPrice", `id` FROM product_kits GROUP BY `id`
) AS TK on TK.`id` = PK.`id`
where CP.`category_id` IN ('62')
order by PK.`new_price` ASC
group by CP.`id`
The thing is that group by does not recognize order by in MySQL.
Actually, what I was doing is really bad practice.
In this case you should use distinct and by catalog_products.*
In my opinion, group by is really useful when you need group result of agregated functions.
Otherwise you should not use it to get unique values.

Alternative to mysql WHERE IN SELECT GROUP BY when wanting max value in group by

I have the following query, which was developed from a hint found online because of a problem with a GROUP BY returning the maximum value; but it's running really slowly.
Having looked online I'm seeing that WHERE IN (SELECT.... GROUP BY) is probably the issue, but, to be honest, I'm struggling to find a way around this:
SELECT *
FROM tbl_berths a
JOIN tbl_active_trains b on a.train_uid=b.train_uid
WHERE (a.train_id, a.TimeStamp) in (
SELECT a.train_id, max(a.TimeStamp)
FROM a
GROUP BY a.train_id
)
I'm thinking I possibly need a derived table, but my experience in this area is zero and it's just not working out!
you can move that to a SUBQUERY and also select only required columns instead of All (*)
SELECT a.train_uid
FROM tbl_berths a
JOIN tbl_active_trains b on a.train_uid=b.train_uid
JOIN (SELECT a.train_id, max(a.TimeStamp) as TimeStamp
FROM a
GROUP BY a.train_id )T
on a.train_id = T.train_id
and a.TimeStamp = T.TimeStamp

improving MySQL related articles query

For a related topic list I use a query using tags. It displays a list of 5 articles that have 1 or more tags in common and that are older than the viewed one.
Is it possible to write a query that produce more relevant results by giving more weight to articles that have 2,3,4... tags in common?
I saw this topic on more or less the same subject:
MySQL Find Related Articles
but it produces 0 results in the case there are less than 3 tags in common.
The query I use now:
SELECT DISTINCT
AAmessage.message_id, AAmessage.title, AAmessage.date
FROM
AAmessage
LEFT JOIN
AAmessagetagtable
AS child ON child.message_id = AAmessage.message_id
JOIN AAmessagetagtagtable
AS parent ON parent.tag_id = child.tag_id
AND
parent.message_id = '$message_id'
AND AAmessage.date < '$row[date]'
ORDER BY
AAmessage.date DESC LIMIT 0,5
using tables:
AAmessage (message_id, title, date...)
AAmessagetable (key, message_id, tag_id)
AAtag (tag_id, tag.... not used in this query but needed to store names of tags)
First of all, please excuse that I changed the table names a bit to message and message_tag for readability.
Second, I didn't test this. Use it rather as a pointer than a definite answer.
The query uses two subqueries, which might not be so efficient, there is probably a room for improvement. First, the innermost query looks for the tags of the current message. Then, the middle query looks for messages which are marked with at least one common tag. The grouping is used to get unique message_id and order them by number of common tags. Last, the JOIN is used to load additional details and to filter out the old messages.
You may notice I used question marks instead of '$xyz'. This is to avoid the care about escaping the variable contents.
SELECT message_id, title, date
FROM message
RIGHT JOIN (SELECT message_id, COUNT(*)
FROM message_tag
WHERE tag_id IN
(SELECT MT.tag_id FROM message_tag MT WHERE MT.message_id = ?)
GROUP BY message_id
ORDER BY COUNT(*) DESC) RELATED_MESSAGES
ON message.message_id = RELATED_MESSAGES.message_id
WHERE date < ?
I use HAVING for this situations.
SELECT DISTINCT m.message_id, m.title, m.date
FROM AAmessage AS `m`
LEFT JOIN AAmessagetagtable AS `mt` ON mt.message_id = mt.message_id
GROUP m.message_id
HAVING COUNT(mt.key) >= 1
WHERE m.message_id = '$message_id'
AND m.date < '$row[date]'
ORDER BY m.date DESC
LIMIT 5

How can I pull out only records that have FAIL against them?

I'm sure this will be quite simple for some one clued up in SQL but I think it needs a sub query or something. I have a table which basically has a load of order numbers in it and a reply column from an XML API. Either FAIL or SUCCESS.
A brand new row is inserted into the DB after every request. So there may be 5 FAILS for one order number, and on the 6th attempt a record is inserted saying SUCCESS.
How can I put out order numbers that ONLY have a FAIL status next to them?
This will allow me to figure out what records need looking into that continuously fail in the API request.
Try this, by grouping your orders with primary key (order_id)
SELECT * FROM
(
SELECT GROUP_CONCAT(status) as status_combined, order_id
FROM orders
GROUP BY order_id
) AS order_tmp
WHERE status_combined NOT LIKE '%SUCCESS%'
Edit (As per asker comments)
SELECT * FROM
(
SELECT GROUP_CONCAT(status) as status_combined, order_id
FROM orders
JOIN certificates ON certificates.Ordernumber = orders.OrderNumber
GROUP BY order_id
) AS order_tmp
WHERE status_combined NOT LIKE '%SUCCESS%'
please make sure you need to join based on "Ordernumber" or "order_id"
Try this
select m.*
from Main m
join Transactiontable tt
on m.orderid = tt.orderid
group by tt.status , m.orderid
having count(case when tt.status = "failed") = count(tt.status)
You can use simple sql query using a where clause:
select *
from some_table
where Column_From_some_table_has_value = your_particular_value
thats enough.
You can have a look at How to use where clause in sql
Thanks
This is probably the cleanest way to do it:
select *
from mytable
where id in (
select id
from mytable
group by id
having sum(status = 'SUCCESS') = 0)
I'm not a fan of #Minesh's answer because it uses both an aggregate function and the LIKE operator. Both of those can cause performance issues since there won't be any indexes to help the query out with the difficult part of the work. The LIKE clause particularly is a lot of work for the database since it will need to scan every result.
I'm more familiar with SQL Server, but this should work well for you:
SELECT *
FROM Orders
WHERE OrderNumber NOT IN (
SELECT OrderNumber
FROM Orders
WHERE Status = 'SUCCESS')
AND OrderNumber NOT IN (
SELECT OrderNumber
FROM Certificates
WHERE OrderStatus = 'CANCELLED')