Second ORDER BY criteria, how to restrict it to one value only? - mysql

I have hard time to design a proper SQL request, I need to select and order (ascending) "scores" summed up over several dates (here 2 dates), and WHEN EQUALITY, I need to order the scores according to one date only.
Here is what I have tried so far:
SELECT SUM(score)
FROM TABLE1
WHERE planning_date = "2021/09/16" AND planning_date = "2021/09/15"
ORDER BY SUM(score), SUM(score (BUT only for the case of planning_date = "2021/09/16")) )
Any idea ?
Thank you in advance

AND should be OR if you want to return results for two different dates. You can also use IN to match a list of dates.
Use a CASE expression inside SUM() to restrict the sum to a particular date.
SELECT SUM(score)
FROM TABLE1
WHERE planning_date IN ("2021/09/16", "2021/09/15")
ORDER BY SUM(score), SUM(CASE WHEN planning_date = "2021/09/16" THEN score END)

Related

How To Find Duplicate, Count and Sum Values from different columns in MySQL?

I have my table
I want to get duplicates from the Name and Status column, count them, and sum values from the Sum column. I want to look like this:
I am new to SQL so that it may be an easy answer, but I can't seem to find a solution.
This is how far I got, but I can't seem to get the count and sum without errors.
SELECT name, COUNT(*) AS recovered
FROM complaints
WHERE status = "Recovered"
GROUP BY name
HAVING COUNT(name) > 0
myQuery
You can do conditional aggregation:
select
name,
sum(status = 'Recovered') recovered,
sum(status = 'Unrecovered') unrecovered,
sum(case when status = 'Recovered' then `sum` end) total_recovered_value,
sum(case when status = 'Unrecovered' then `sum` end) total_unrecovered_value
from mytable
group by name
order by name
Side note: sum is a language keyword, hence not a good choice for a column name.

MySQL - GROUP BY, select only the first row when grouping

I'm having a problem with grouping specific columns into one. When I use GROUP BY, the last row always gets selected when it should be the first row.
The main query is:
SELECT cpme_id,
medicine_main_tbl.med_id,
Concat(med_name, ' (', med_dosage, ') ', med_type) AS Medicine,
med_purpose,
med_quantity,
med_expiredate
FROM medicine_main_tbl
JOIN medicine_inventory_tbl
ON medicine_main_tbl.med_id = medicine_inventory_tbl.med_id
WHERE Coalesce(med_quantity, 0) != 0
AND Abs(Datediff(med_expiredate, Now()))
ORDER BY med_expiredate;
SELECT without GROUP BY
If I GROUP BY using any duplicate column value (in this case, I used med_id):
SELECT with GROUP BY
I'm trying to get this output
Expected Output
The output should only be the first two from the first query. Obviously, I cannot use LIMIT.
Since you are using MariaDB, I recommend using ROW_NUMBER here:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY med_id ORDER BY med_expireDate) rn
FROM yourTable
)
SELECT cpme_id, med_id, Medicine, med_purpose, med_quantity, med_expireDate
FROM cte
WHERE rn = 1;
This assumes that the "first" row for a given medicine is the one having the earliest expire date. This was the only interpretation of your data which agreed with the expected output.

Displaying data with respect to specific date?

I am trying to make a reporting system where I need to display report
for each date.
These is my table schema for selected_items
This is stock_list
I am using php in the back-end and java in the front end to display
the data. I tried a couple of queries to get the desired output but so
far I am not able to get it.These are some of the queries i used.
SELECT
COALESCE(stock_list.date, selected_items.date) AS date,
SUM( stock_list.qty ) AS StockSum,
SUM( stock_list.weight ) AS Stockweight,
COUNT( selected_items.barcode ) AS BilledItems,
SUM( selected_items.weight ) AS Billedweight
FROM stock_list join selected_items
ON stock_list.date = selected_items.date
GROUP BY COALESCE(stock_list.date, selected_items.date)
ORDER BY COALESCE(stock_list.date, selected_items.date);
This gives me the first five columns but the output gives me wrong values.
Then I also tried Union.
SELECT SUM( qty ) AS StockSum, SUM( weight ) AS Stockweight
FROM `stock_list`
WHERE DATE LIKE '08-Jan-2016'
UNION SELECT COUNT( barcode ) AS BilledItems, SUM( weight ) AS Billedweight
FROM `selected_items`
WHERE DATE LIKE '08-Jan-2016'
UNION SELECT SUM( qty ) AS TotalStock, SUM( weight ) AS TotalWeight
FROM `stock_list`;
Here I get the correct values for four columns but the problem is the >result is displayed in two columns when I would like it to be in 4 columns.
Can anyone guide me please I have figured the java part of it but I am not good at php and mysql.
Thank you
Unfortunately, SQL Fiddle crashed while I was trying to execute this query
SELECT sl.date AS date, B.qtySum AS StockSum, B.weightSum AS Stockweight,
C.barcodeCount AS BilledItems, C.weightSum AS Billedweight
FROM stock_list sl
JOIN (SELECT SUM(qty) as qtySum, SUM(weight) as weightSum
FROM STOCK_LIST GROUP BY date) AS B
ON B.date = sl.date
JOIN (SELECT SUM (weight) AS weightSum, COUNT(barcode) AS barcodeCount
FROM SELECTED_ITEMS GROUP BY date) AS C
ON C.date = sl.date;
As it was tried here. The problem with joins is that the rows will be joined multiple times and thus, the sum goes awry. For example, you have four rows that are joined from the second table and so the sum is four times higher as it should. With subqueries you can avoid this problem as you count and sum up variables before joining them and therefore, the numbers should fit. Alas, I couldn't run the query so I'm not 100% sure it works, but it should be the right approach.

Why is 'ORDER BY' needed to get correct result from MySQL join?

I have the following query:
SELECT t.ID, t.caseID, time
FROM tbl_test t
INNER JOIN (
SELECT ID, MAX( TIME )
FROM tbl_test
WHERE TIME <=1353143351
GROUP BY caseID
ORDER BY caseID DESC -- ERROR HERE!
) s
USING (ID)
It seems that I only get the correct result if I use the ORDER BY in the inner join. Why is that? I am using the ID for the join, so the order should take no effekt.
If I remove the order by, I get too old entries from the database.
ID is the primary key, the caseID is a kind of object with multiple entries with different timestamps.
This query is ambiguous:
SELECT ID, MAX( TIME )
FROM tbl_test
WHERE TIME <=1353143351
GROUP BY caseID
It's ambiguous because it does not guarantee that it returns the ID of the row where the MAX(TIME) occurs. It returns the MAX(TIME) for each distinct value of caseID, but the value of other columns (like ID) is chosen arbitrarily from members of the group.
In practice, MySQL chooses the row that it finds first in the group as it scans rows in storage order.
Example:
caseID ID time
1 10 15:00
1 12 18:00
1 14 13:00
The max time is 18:00, which is the row with ID 12. But the query will return ID 10, simply because it's the first one in the group. If you were to reverse the order with ORDER BY, it would return ID 14. Still not the row where the max time is found, but it's from the other end of the group of rows.
Your query works with ORDER BY caseID DESC because, by coincidence, your Time values increase with the increasing ID.
This sort of query is actually an error in standard SQL and most other brands of SQL database. MySQL permits it, trusting that you know how to form an unambiguous query.
The fix is to use columns in the select-list only if they are unambiguous, that is, if they are in the GROUP BY clause, then each group is guaranteed to have only one distinct value:
SELECT caseID, MAX( TIME )
FROM tbl_test
WHERE TIME <=1353143351
GROUP BY caseID
SELECT t.ID, t.caseID, time
FROM tbl_test t
INNER JOIN (
SELECT caseID, MAX( TIME ) maxtime
FROM tbl_test
WHERE TIME <=1353143351
GROUP BY caseID
) s
ON t.caseID = s.caseID and t.time = s.maxtime
You are seeing that issue because you are getting the MAX(TIME) per caseID, but since you are grouping by caseID and NOT ID, you are getting an arbitrary ID. That happens because when you use an aggregate function, like MAX, you must, for every non-grouped field in the select specify how you want to aggregate it. That means, if it's in the SELECT and NOT in the GROUP BY, you have to tell MySQL how to aggregate. If you don't then you get a RANDOM row (well, not random per se, but it's not going to be in an order that you necessarily expect).
The reason ORDER BY is working for you, is that it kind of tricks the query optimizer into sorting the results before grouping, which just so happens to produce the result you want, but be warned, that will not always be the case.
What you want is the ID that has the MAX(TIME) given a caseID. Which means your INNER join needs to connect by caseID (not ID) and time (which will give you 1 row per each 1 row in the outer table).
Barmar beat me to the actual query, but that's the way you want to go.

ORDER BY date and time BEFORE GROUP BY name in mysql

i have a table like this:
name date time
tom | 2011-07-04 | 01:09:52
tom | 2011-07-04 | 01:09:52
mad | 2011-07-04 | 02:10:53
mad | 2009-06-03 | 00:01:01
i want oldest name first:
SELECT *
ORDER BY date ASC, time ASC
GROUP BY name
(->doesn't work!)
now it should give me first mad(has earlier date) then tom
but with GROUP BY name ORDER BY date ASC, time ASC gives me the newer mad first because it groups before it sorts!
again: the problem is that i can't sort by date and time before i group because GROUP BY must be before ORDER BY!
Another method:
SELECT *
FROM (
SELECT * FROM table_name
ORDER BY date ASC, time ASC
) AS sub
GROUP BY name
GROUP BY groups on the first matching result it hits. If that first matching hit happens to be the one you want then everything should work as expected.
I prefer this method as the subquery makes logical sense rather than peppering it with other conditions.
As I am not allowed to comment on user1908688's answer, here a hint for MariaDB users:
SELECT *
FROM (
SELECT *
ORDER BY date ASC, time ASC
LIMIT 18446744073709551615
) AS sub
GROUP BY sub.name
https://mariadb.com/kb/en/mariadb/why-is-order-by-in-a-from-subquery-ignored/
I think this is what you are seeking :
SELECT name, min(date)
FROM myTable
GROUP BY name
ORDER BY min(date)
For the time, you have to make a mysql date via STR_TO_DATE :
STR_TO_DATE(date + ' ' + time, '%Y-%m-%d %h:%i:%s')
So :
SELECT name, min(STR_TO_DATE(date + ' ' + time, '%Y-%m-%d %h:%i:%s'))
FROM myTable
GROUP BY name
ORDER BY min(STR_TO_DATE(date + ' ' + time, '%Y-%m-%d %h:%i:%s'))
This worked for me:
SELECT *
FROM your_table
WHERE id IN (
SELECT MAX(id)
FROM your_table
GROUP BY name
);
Use a subselect:
select name, date, time
from mytable main
where date + time = (select min(date + time) from mytable where name = main.mytable)
order by date + time;
If you wont sort by max date and group by name, you can do this query:
SELECT name,MAX(date) FROM table group by name ORDER BY name
where date may by some date or date time string. It`s response to you max value of date by each one name
Another way to solve this would be with a LEFT JOIN, which could be more efficient. I'll first start with an example that considers only the date field, as probably it is more common to store date + time in one datetime column, and I also want to keep the query simple so it's easier to understand.
So, with this particular example, if you want to show the oldest record based on the date column, and assuming that your table name is called people you can use the following query:
SELECT p.* FROM people p
LEFT JOIN people p2 ON p.name = p2.name AND p.date > p2.date
WHERE p2.date is NULL
GROUP BY p.name
What the LEFT JOIN does, is when the p.date column is at its minimum value, there will be no p2.date with a smaller value on the left join and therefore the corresponding p2.date will be NULL. So, by adding WHERE p2.date is NULL, we make sure to show only the records with the oldest date.
And similarly, if you want to show the newest record instead, you can just change the comparison operator in the LEFT JOIN:
SELECT p.* FROM people p
LEFT JOIN people p2 ON p.name = p2.name AND p.date < p2.date
WHERE p2.date is NULL
GROUP BY p.name
Now, for this particular example where date+time are separate columns, you would need to add them in some way if you want to query based on the datetime of two columns combined, for example:
SELECT p.* FROM people p
LEFT JOIN people p2 ON p.name = p2.name AND p.date + INTERVAL TIME_TO_SEC(p.time) SECOND > p2.date + INTERVAL TIME_TO_SEC(p2.time) SECOND
WHERE p2.date is NULL
GROUP BY p.name
You can read more about this (and also see some other ways to accomplish this) on the The Rows Holding the Group-wise Maximum of a Certain Column page.
I had a different variation on this question where I only had a single DATETIME field and needed a limit after a group by or distinct after sorting descending based on the datetime field, but this is what helped me:
select distinct (column) from
(select column from database.table
order by date_column DESC) as hist limit 10
In this instance with the split fields, if you can sort on a concat, then you might be able to get away with something like:
select name,date,time from
(select name from table order by concat(date,' ',time) ASC)
as sorted
Then if you wanted to limit you would simply add your limit statement to the end:
select name,date,time from
(select name from table order by concat(date,' ',time) ASC)
as sorted limit 10
In Oracle, This work for me
SELECT name, min(date), min(time)
FROM table_name
GROUP BY name
work for me mysql
select * from (SELECT number,max(date_added) as datea FROM sms_chat group by number) as sup order by datea desc
This is not the exact answer, but this might be helpful for the people looking to solve some problem with the approach of ordering row before group by in mysql.
I came to this thread, when I wanted to find the latest row(which is order by date desc but get the only one result for a particular column type, which is group by column name).
One other approach to solve such problem is to make use of aggregation.
So, we can let the query run as usual, which sorted asc and introduce new field as max(doc) as latest_doc, which will give the latest date, with grouped by the same column.
Suppose, you want to find the data of a particular column now and max aggregation cannot be done.
In general, to finding the data of a particular column, you can make use of GROUP_CONCAT aggregator, with some unique separator which can't be present in that column, like GROUP_CONCAT(string SEPARATOR ' ') as new_column, and while you're accessing it, you can split/explode the new_column field.
Again, this might not sound to everyone. I did it, and liked it as well because I had written few functions and I couldn't run subqueries. I am working on codeigniter framework for php.
Not sure of the complexity as well, may be someone can put some light on that.
Regards :)