MySQL - Alternative to GROUP BY without aggregate function? - mysql

Imagine an unordered table set up as below:
+----+------------+----------+-----------+
| ID | Project | Resource | StartDate |
+----+------------+----------+-----------+
| 1 | ExtraParts | Mark | 24/01 |
| 2 | ExtraParts | Sam | 22/01 |
| 3 | TimeLabour | Sally | 19/01 |
| 4 | TimeLabour | Sarena | 23/01 |
| 5 | Runway | Olly | 14/02 |
| 6 | Runway | Mary | 14/05 |
+----+------------+----------+-----------+
I would like to order by the earliest StartDate for each project, but still group the resources from each projects together(not sure if im explaining this right, but below is what I would like to achieve)
+----+-------------+-----------+-----------+
| ID | Project | Resource | StartDate |
+----+-------------+-----------+-----------+
| 1 | TimeLabor | Sally | 19/01 |
| 2 | TimeLabor | Sarena | 23/01 |
| 3 | ExtraParts | Sam | 22/01 |
| 4 | ExtraParts | Mark | 24/01 |
| 5 | Runway | Olly | 14/02 |
| 6 | Runway | Mary | 14/05 |
+----+-------------+-----------+-----------+
If I do ORDER BY StartDate, Project then the result will have jumbled up the projects. If i ORDER BY Project, StartDate then the result will be sorted alphabetically by project first and then sort by date within the same project(if that makes sense). As stated above, I would like to order by the earliest StartDate for each project while still grouping the projects together(not aggregate grouping, just one under the other).
Any help is greatly appreciated! :)

something like this? SQL FIDDLE to play with
SELECT project, resource, startdate
FROM testing
ORDER BY project, STR_TO_DATE(StartDate, '%d/%m')
basically your table is storing dates as a string and in order for you to order it you need to convert the strings to recognized dates... so the STR_TO_DATE() is a method to do that :)

You need to calculate the date used for the ordering:
select t.*
from table t join
(select project, min(startdate) as minsd
from table t
group by project
) tp
on t.project = tp.project
order by tp.minsd, t.project;
Note: this assumes that startdate is really stored as a date and not a string. If it is stored as a string, then you should convert it to a date first.

Related

MySQL emulate lag to reduce redundant items on older server

Bottom Line Up Front: I am trying to find a way to format the output so that some data is left blank if it matches a preceding row.
I've edited as suggested by #philipxy because I really do want to learn how to write better code and write better questions to support that learning.
That being said, #ysth was able to solve my overly complex ask anyway. I changed the title and marked answered.
This is a sample table from my database:
(SELECT codename, dt_begin, id_alias FROM aliases GROUP BY codename;)
+--------------+------------+----------+
| codename | dt_begin | id_alias |
+--------------+------------+----------+
| Arachniblade | 1999-12-23 | 1 |
| Arachniblade | 2016-07-04 | 2 |
| Beta | 2015-06-03 | 1 |
| Beta | 2016-07-04 | 3 |
| Cyberwolf | 2016-07-04 | 1 |
+--------------+------------+----------+
I would like the second (and any subsequent) instances of 'Arachniblade' and 'Beta' to be blank when ORDER BY codename is used.
+--------------+------------+----------+
| codename | dt_begin | id_alias |
+--------------+------------+----------+
| Arachniblade | 1999-12-23 | 1 |
| | 2016-07-04 | 2 |
| Beta | 2015-06-03 | 1 |
| | 2016-07-04 | 3 |
| Cyberwolf | 2016-07-04 | 1 |
+--------------+------------+----------+
Similarly, if I ORDER BY id_alias I would like to see only id 1 printed once but still retain all three records for 'Arachniblade,' 'Beta,' and 'Cyberwolf.'
+--------------+------------+----------+
| codename | dt_begin | id_alias |
+--------------+------------+----------+
| Arachniblade | 1999-12-23 | 1 |
| Beta | 2015-06-03 | |
| Cyberwolf | 2016-07-04 | |
| Arachniblade | 2016-07-04 | 2 |
| Beta | 2016-07-04 | 3 |
+--------------+------------+----------+
As #ysth mentioned LAG() is a part of the solution. I'm not sure how COALESCE fits in yet.
So you don't want to GROUP BY anything, but you want Codename to be blank where it is equal to the previous row's Codename? You would select this instead of just aliases.codename:
IF(COALESCE(LAG(aliases.codename) OVER (),'')=aliases.codename,'',aliases.codename) AS 'Codename'
assuming mysql 8.0 or mariadb 10.2+. Full query:
SELECT IF(COALESCE(LAG(aliases.codename) OVER (),'')=aliases.codename,'',aliases.codename) AS 'Codename',
aliases.dt_begin,
public_ids.fname,
public_ids.suffix
FROM public_ids
JOIN aliases ON aliases.id_alias
WHERE aliases.id_alias = public_ids.id
ORDER BY aliases.codename, aliases.dt_begin
(You omitted ORDER BY in your query; it wouldn't make much sense to want to do this without a specified order.)
On older versions, you can emulate LAG() with a variable:
SELECT IF(COALESCE(#lag,'')=aliases.codename,'',#lag:=aliases.codename) AS 'Codename',
aliases.dt_begin,
public_ids.fname,
public_ids.suffix
FROM (select #lag:=NULL) initvars
CROSS JOIN public_ids
JOIN aliases ON aliases.id_alias
WHERE aliases.id_alias = public_ids.id
ORDER BY aliases.codename, aliases.dt_begin

Selecting the most recent result from one table joining to another

I have two tables.
One table contains customer data, like name and email address. The other table contains a log of the status changes.
The status log table looks like this:
+-------------+------------+------------+
| customer_id | status | date |
+-------------+------------+------------+
| 1 | Bought | 2018-07-01 |
| 1 | Bought | 2018-07-02 |
| 2 | Ongoing | 2018-07-03 |
| 3 | Ongoing | 2018-07-04 |
| 1 | Not Bought | 2018-07-05 |
| 4 | Bought | 2018-07-06 |
| 4 | Not Bought | 2018-07-07 |
| 4 | Bought | 2018-07-08 | *
| 3 | Cancelled | 2018-07-09 |
+-------------+------------+------------+
And the customer data:
+-------------+------------+
| id | name | email |
+-------------+------------+
| 1 | Alex | alex#home |
| 2 | John | john#home |
| 3 | Simon | si#home |
| 4 | Philip | phil#home |
+-------------+------------+
I would like to select the customer's who have "Bought" in July (07). But exclude customers who's status has changed from "Bought" anything other most recently.
The result should be just one customer (Philip) - all the others have had their status change to something other than Bought most recently.
I have the following SQL:
SELECT
a.customer_id
FROM
statuslog a
WHERE
DATE(a.`date`) LIKE '2018-07-%'
AND a.status = 'Bought'
ORDER BY a.date DESC
LIMIT 1
But that is as far as I have got! The above query only returns one result, but essentially there could be more than one.
Any help is appreciated!
Here is an approach that uses a correlated subquery to get the most recent status record:
SELECT sl.customerid
FROM wwym_statuslog sl
WHERE sl.date = (SELECT MAX(sl2.date)
FROM wwym_statuslog sl2
WHERE sl2.customer_id = sl.customer_id AND
sl2.date >= '2018-07-01' AND
sl2.date < '2018-08-01'
) AND
sl.status = 'Bought'
ORDER BY sl.date DESC
LIMIT 1;
Notes:
Use meaningful table aliases! That is, abbreviations for the table names, rather than arbitrary letters such as a and b.
Use proper date arithmetic. LIKE is for strings. MySQL has lots of date functions that work.
In MySQL 8+, you would use ROW_NUMBER().

Retrieve one row from set result if value in certain column is duplicate

I would like to retrieve one record from table if there is duplicate in certain column like it follows:
------------------------
| id | year | number |
| 1 | 2018 | I |
| 1 | 2018 | II |
| 1 | 2017 | I |
| 1 | 2016 | II |
| 2 | 2017 | I |
| 2 | 2017 | II |
| 2 | 2016 | I |
| 2 | 2016 | II |
| 2 | 2015 | II |
------------------------
desired output
------------------------
| id | year | number |
| 1 | 2018 | I |
| 1 | 2017 | I |
| 1 | 2016 | II |
| 2 | 2017 | I |
| 2 | 2016 | I |
| 2 | 2015 | II |
------------------------
I've googled and tried tricks with 'group by + having' and tried creating index on those columns with 'insert ignore' combined but without avail. Anyone to say if this is even possible?
UPDATE:
Finally came up with solution, I've created index on id and year, then made copy of existing table with 'INSERT IGNORE INTO ...'.
You could use aggregation for your required data set as
select id, `year`, min(`number`)
from your_table
group by id, `year`
order by id,`year` desc
Demo
MIN() may take a string argument; in such cases, it returns the minimum string value
you can try this to select unique values from table
SELECT DISTINCT column1, column2,.....columnN
FROM table_name
Good Luck
Try this
SELECT * FROM table_name GROUP BY id,year;
Note: it is not good practice to create tables without primary keys and use MySQL keywords for table names, such as year and number.
If you still insist on using year and number always encapsulate them with `.

Mysql retriving results from a list

I am trying to avoid making a huge amount of queries. I am sure that there is a simple solution that I don't know of. What I want to achieve is something like this
-------------------------------------------------------
| type | 06/03 | 08/03 | 15/03 | 04/04 |
-------------------------------------------------------
| single room | 0 | 2 | 3 | 0 |
| double room | 1 | 2 | 5 | 0 |
| suite | 2 | 2 | 1 | 2 |
-------------------------------------------------------
passing some room ids and a list of dates. is there anyway to do this on db side or do I have to go through each room and make a query for each day?
Thank you guys
//R
something like this?
SELECT room_type AS type, dt, count(*) FROM table_name GROUP BY room_type, dt;

how to fetch columns from multiple tables using sql

I am working on railway transport.
I have the following tables:
DAY table
| - id | name |
| 1 | sunday |
| 2 | monday |
| 3 | tuesday|
station table
|- id | name |
| 1 | zaria |
| 2 | kano |
| 3 | minna |
route table
| - id | source_station_id | destination_station_id|
| 1 | 1 | 2 |
| 2 | 1 | 3 |
| 3 | 2 | 3 |
| 4 | 3 | 2 |
| 3 | 3 | 1 |
departure table
| route_id | day_id | departure_time |
| 1 | 2 | 07:00 hrs |
| 1 | 3 | 07:00 hrs |
| 3 | 2 | 15:30 hrs |
route_id, day_id, source_station_id, destination_station_id are all foreign keys referenced from route, day, and station table respectively.
Now... how do I use sql to fetch from these tables..
output to look like the table below.
| source | destination | day | departure_time |
You want to use a JOIN:
SELECT
source.name AS source,
destination.name AS destination,
day.name AS day,
departure.time AS departure_time
FROM departure
JOIN station AS source
ON departure.source_station_id = source.id
JOIN station AS destination
ON departure.source_station_id = destination.id
JOIN day
ON departure.day_id = day.id
WHERE
# any specific criteria you may have, like:
departure.day_id = 1 # for a sunday
Should do it. For more information see MySQL JOIN Syntax
That said, this is a pretty standard question, and I would not be surprised to see it get voted down.
Further, from a structural point of view, you would really be better off storeing the "day" as a DATETIME, and converting this to and from a Human Readable date string on the fly.
As it stands, since you are only doing the day of the week, you have no way of differentiating between this week and last or last year. Have a look at the bottom-half of this comment, where I start talking about spanning periods of time, and discuss the use of DATETIME. Using native data-types is a preferable, both for performance, and ease of use.