Order by and Index on a Join mySQL - mysql

I have a table named "MI_APR". I am bringing in data from a "calendar" table using a LEFT JOIN. This data being transferred is a column of DATES with column name "First_Date".
ALTER TABLE MI_APR
ADD COLUMN `Profile` CHAR(8) NOT NULL DEFAULT 'MI',
ADD COLUMN First_Date DATE,
CHANGE `Location Label` `Label`TINYTEXT;
UPDATE MI_APR LEFT JOIN calendar on MI_APR.`Period` = calendar.`Unique_Period`
SET
MI_APR.`First_Date` = calendar.`First_Date`
After the join has happened, I need some help sorting the "FIRST_DATE" column in ASC , which is a simple date column in format YYYY-MM-DD.
I then need to add a new column named INDEX, that has values starting from 0 for the earliest month, counting upwards, based on the Month number in that DATES column in the MI_APR table. The index will run continue to run upwards even across years. See example below :
EXPECTED OUTPUT
FIRST_DATE INDEX
2018/10/01 0
2018/11/01 1
2018/12/01 2
2019/01/01 3
Thank you

You can either calculate your index on the fly with this query:
SELECT First_Date, TIMESTAMPDIFF(MONTH, earliest_date, First_Date) AS Index
FROM MI_APR
, (SELECT First_Date AS earliest_date FROM MI_APR ORDER BY First_Date ASC LIMIT 1) fancy_alias;
If the result pleases you, you can transform it into an update. My advice is though, that you shouldn't store data, which can easily be calculated on the fly!
UPDATE MI_APR
, (SELECT First_Date AS earliest_date FROM MI_APR ORDER BY First_Date ASC LIMIT 1) fancy_alias
SET `Index` = TIMESTAMPDIFF(MONTH, earliest_date, First_Date);
Regarding your comment "Also I would like the table to be sorted and then updated".
There is no order in a table you can rely on. If you want a result from a select sorted, use the ORDER BY clause.

Related

Update a column from a SELECT of another table

I have been attempting to update a column based on a group by a select from one table into another table. The below subquery on the set statement works but only for one date because if I use a date range I get an error of "subquery returns more than 1 row".
I instead want to run that on a date range fetching the group by for each day (from "Monthly" table) inserting each matching row by day into "Dayfile" table. The dayfile table has a row for each date with the "LogDate column" as date and the monthly table is a log file of minute-by minute values where the "LogDateTime" data type is datetime.
UPDATE
Dayfile
SET
MaxFeelsLike =
(SELECT MAX(FeelsLike)
FROM Monthly, Dayfile
WHERE DATE(LogDateTime) = "2018-10-04"
AND DATE(LogDateTime) = DATE(LogDate)
GROUP BY DATE(LogDateTime)
);
You should use a JOIN rather using the subquery as a value.
UPDATE Dayfile AS d
JOIN (
SELECT DATE(LogDateTime) AS date, MAX(FeelsLike) AS feels
FROM Monthly
GROUP BY date
) AS m ON DATE(d.LogDate) = m.date
SET d.MaxFeelsLike = m.feels
Include LIMIT 1 at the end of your subquery

Select NULL otherwise latest date per group

I am trying to pickup Account with End Date NULL first then latest date if there are more accounts with the same item
Table Sample
Result expected
Select distinct *
from Sample
where End Date is null
Need help to display the output.
Select *
from Sample
order by End_Date is not null, End_date desc
According to sample it seems to me you need union and not exists corelate subquery
select * from table_name t where t.enddate is null
union
select * from table_name t
where t.endate=( select max(enddate) from table_name t1 where t1.Item=t.Item and t1.Account=t.Account)
and not exists ( select 1 from table_name t2 where enddate is null and
t1 where t2.item=t.item
)
SELECT * FROM YourTable ORDER BY End_Date IS NOT NULL, End_Date DESC
In a Derived Table, you can determine the end_date_to_consider for every Item (using GROUP BY Item). IF() the MIN() date is NULL, then we consider NULL, else we consider the MAX() date.
Now, we can join this back to the main table on Item and the end_date to get the required rows.
Try:
SELECT t.*
FROM
Sample AS t
JOIN
(
SELECT
Item,
IF(MIN(end_date) IS NULL,
NULL,
MAX(end_date)) AS end_date_to_consider
FROM Sample
GROUP BY Item
) AS dt
ON dt.Item = t.Item AND
(dt.end_date_to_consider = t.end_date OR
(dt.end_date_to_consider IS NULL AND
t.end_date IS NULL)
)
First of all you should state clearly which result rows you want: You want one result row per Item and TOU. For each Item/TOU pair you want the row with highest date, with null having precedence (i.e. being considered the highest possible date).
Is this correct? Does that work with your real accounts? In your example it is always that all rows for one account have a higher date than all other account rows. If that is not the case with your real accounts, you need something more sophisticated than the following solution.
The highest date you can store in MySQL is 9999-12-31. Use this to treat the null dates as desired. Then it's just two steps:
Get the highest date per item and tou.
Get the row for these item, tou and date.
The query:
select * from
sample
where (item, tou, coalesce(enddate, date '9999-12-31') in
(
select item, tou, max(coalesce(enddate, date '9999-12-31'))
from sample
group by item, tou
)
order by item, tou;
(If it is possible for your enddate to have the value 9999-12-31 and you want null have precedence over this, then you must consider this in the query, i.e. you can no longer simply use this date in case of null, and the query will get more complicated.)

How do I get the sum of a column across multiple keys?

I have data that looks like this:
id int (11) primary key auto_increment
key int (2)
type int (2)
data int (4)
timestamp datetime
There are 5 different keys - 1,2,3,4,5 and three types - 1,2,3
Data is put in continuously against a key and of a particular type.
What I need to extract is a sum of the data for a particular type (say, type 1) across all 5 keys (1,2,3,4,5) so it is a sum of exactly 5 records. I only want to sum the latest (max(timestamp) values (there are 5 of them) of data for each key, but they may all have different timestamps.
Something like this....
SELECT sum(data) FROM table WHERE type='1' AND timestamp=(SELECT max(timestamp FROM table WHERE type='1' GROUP BY key)
Or something like that. That isn't even close of course. I am completely lost on this one. it feels like I need to group by key but the syntax eludes me. Any suggestions are appreciated.
EDIT: additional info:
if: 'data' is temperature. 'key' is day of the week. 'type' is morning, noon or night
So the data might look like
morning mon 70 (timestamp)
noon tue 78 (timestamp)
morning wed 72 (timestamp)
night tue 74 (timestamp)
morning thu 76 (timestamp)
noon wed 77 (timestamp)
night fri 78 (timestamp)
noon tue 79 (timestamp)
If these are in timestamp order (desc) and I want the sum of most recent noon temps for all five days, the result would be: 155 in this case since the last noon was also tuesday and it was earlier and thus, not included. Make sense? I want sum of 'data' for any key, specific type, latest timestamp only. In this example, I would be summing at most 7 pieces of data.
If the timestamp column is guaranteed to be unique for each (key,type) (That is, there's a UNIQUE constraint ON (key,type,timestamp), then this query will return the specified resultset. (This isn't the only approach, but it is a familiar pattern):
SELECT SUM(t.data) AS latest_total
FROM mytable t
JOIN ( SELECT h.type
, h.key
, MAX(h.timestamp) AS max_ts
FROM mytable h
WHERE h.type='1'
GROUP
BY h.type
, h.key
) m
ON m.type = t.type
AND m.key = t.key
AND m.max_ts = t.timestamp
The inline view assigned an alias of m returns the "latest" timestamp for type=1 for all 5 key values (if at least one row exists)
That is joined to the original table, to retrieve the row that has that "latest" timestamp.
A suitable index with leading columns of type,key,timestamp will likely improve performance.
(That's based on my understanding of the specification; I may not be totally clear on the specification. What this query is doing is getting the latest timestamp for the type=1 rows. If there happen to be two (or more) rows with the same latest timestamp value for a given key and type, this query will retrieve both (or all) of those rows, and include them in the sum.
We could add a GROUP BY t.type on that query, and that wouldn't change the result, since we are guaranteed that the t.type will be equal to the constant 1 (specified in the predicate in the WHERE clause of the inline view query.)
But we would need to add the GROUP BY if we wanted to get totals for all three type in the same query:
SELECT t.key
, SUM(t.data) AS latest_total
FROM mytable t
JOIN ( SELECT h.type
, h.key
, MAX(h.timestamp) AS max_ts
FROM mytable h
WHERE h.type IN ('1','2','3')
GROUP
BY h.type
, h.key
) m
ON m.type = t.type
AND m.key = t.key
AND m.max_ts = t.timestamp
GROUP
BY t.key
NOTE:
Using reserved words as identifiers (e.g. TIMESTAMP and KEY isn't illegal, but those identifiers (usually) need to be enclosed in backticks. But changing the names of these columns so that they aren't reserved words is best practice.
SELECT SUM(data)
FROM ( SELECT CONCAT(MAX(timestamp), '_', type) AS customId
FROM table
WHERE type = '1'
GROUP BY key ) a
JOIN table b ON a.customId = CONCAT(b.timestamp, '_', type)
GROUP BY type;
This would probably do the trick...
SQL-Fiddle
I would for simplicity and maintainability use a temp-table and fill it with several statements. The solution with "union-subselect" looks a bit long for me.
So
drop tamporary table if exists tmp_data;
create temporary table tmp_data (type int, value int);
insert into tmp_data select 1, value from data_table where type=1 order by timestamp desc limit 5;
insert into tmp_data select 2, value from data_table where type=2 order by timestamp desc limit 5;
insert into tmp_data select 3, value from data_table where type=3 order by timestamp desc limit 5;
select type, sum(value) as total from tmp_data group by type;
EDIT:
The subselect-solution would be similar, and since there are only 3 types not too bad
select type, sum(value) as total from
(select 1 as type, value from data_table where type=1 order by timestamp desc limit 5
union
select 2 as type, value from data_table where type=2 order by timestamp desc limit 5
union
select 3 as type, value from data_table where type=3 order by timestamp desc limit 5) as subtab group by type;
Hope that helps.

SQL Row grouping

I've a table like (I am omitting unnecessary columns)
id:int | name:string | ts:DateTime
There are multiple entries. now What I want in my resultset is
date:Date | entries:int
e.g. how many entries were made on all dates. actually I gonna make a chart of it.
What SQL Query I need to use for this ? I can create a view of it
This should do the job:
SELECT
DATE(ts) AS date,
COUNT(*) AS entries
FROM table
GROUP BY date
You can try
SELECT ts AS `date`, COUNT(id) AS entries
FROM your_table
GROUP BY ts
If you just need date part, then try
SELECT DATE(ts) AS `date`, COUNT(id) AS entries
FROM your_table
GROUP BY DATE(ts)
To strip off the time part of your timestamps, use DATE(). Sort by the date, ascending to get them in the right order. Note that this will omit dates for which there are no entries. If you need to fill those in, it becomes somewhat more complicated.
SELECT
DATE(ts) AS date,
COUNT(*) AS entries
FROM tbl
GROUP BY DATE(ts)
ORDER BY DATE(ts) ASC

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 :)