Order between multiple GROUP_CONCAT columns - mysql

I can't seem to find any information about GROUP_CONCAT function default behavior, maily when i use multiple of those, will the returned values have the same order in between them?
For this example table & data:
CREATE TABLE `test` (
`id` int(11) NOT NULL,
`parentId` int(11) NOT NULL,
`weight` int(11) NOT NULL,
`color` varchar(7) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `test` (`id`, `parentId`, `weight`, `color`) VALUES
(1, 1, 500, '#aa11dd'),
(2, 1, 770, '#ffffff'),
(3, 2, 100, '#ff00ff'),
(4, 2, 123, '#556677');
If I do this select:
SELECT `parentId`,
GROUP_CONCAT(`weight`),
GROUP_CONCAT(`color`),
GROUP_CONCAT(`id`)
FROM `test`
GROUP BY `parentId`
It returns:
parentId GROUP_CONCAT(weight) GROUP_CONCAT(color) GROUP_CONCAT(id)
1 500,770 #aa11dd,#ffffff 1,2
2 79798,123 #ff00ff,#556677 3,4
Is it ever possible that for example in the first line values 500,770 will flip into 770,500 but the rest of columns remain the same (#aa11dd,#ffffff; 1,2)? I don't really care about the overall order (DESC / ASC), all i want to know is, if each column has always the same order as the others?

In the absence of the ORDER BY clause inside the GROUP_CONCAT() function the engine is free to assemble the value in any order, and this order is not stable over time. Each GROUP_CONCAT() function may have a different ordering as well.
To ensure a stable ordering use ORDER BY inside the GROUP_CONCAT() function.
For example;
SELECT
`parentId`,
GROUP_CONCAT(`weight` order by `id`),
GROUP_CONCAT(`color` order by `id`),
GROUP_CONCAT(`id` order by `id`)
FROM `test`
GROUP BY `parentId`
This example orders all values by id to ensure a stable, known order, and also to ensure that each column has always the same order as the others.

According to docs, you can specify the exact order:
GROUP_CONCAT(DISTINCT `weight` ORDER BY `weight` ASC SEPARATOR ', ')
The default order is unknown

Related

Is it possible to COUNT if one value occured more than the other value in a column using SQL

I have this table called task_status which has the following structure:
CREATE TABLE `task_status` (
`task_status_id` int(11) NOT NULL,
`status_id` int(11) NOT NULL,
`task_id` int(11) NOT NULL,
`date_recorded` varchar(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `task_status`
ADD PRIMARY KEY (`task_status_id`);
ALTER TABLE `task_status`
MODIFY `task_status_id` int(11) NOT NULL AUTO_INCREMENT;
COMMIT;
INSERT INTO `task_status` (`task_status_id`, `status_id`, `task_id`, `date_recorded`) VALUES
(1, 1, 16, 'Wednesday 6th of January 2021 09:20:35 AM'),
(2, 2, 17, 'Wednesday 6th of January 2021 09:20:35 AM'),
(3, 3, 18, 'Wednesday 6th of January 2021 09:20:36 AM');
and a status_list table that has the possible statuses available
CREATE TABLE `status` (
`statuses_id` int(11) NOT NULL,
`status` varchar(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `status`
ADD PRIMARY KEY (`statuses_id`);
ALTER TABLE `status`
MODIFY `statuses_id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4;
COMMIT;
INSERT INTO `status` (`statuses_id`, `status`) VALUES
(1, 'Yes'),
(2, 'Inprogress'),
(3, 'No');
Now what I want to do is check which number occurred more inside the status_id column 1 occurred more, 2 occurred more or 3 occurred more? using SQL.
Is it possible to do and if so how to?
You can try OVER and PARTITION BY clauses, you simply specify the column you want to partition your aggregated results by.
Example code
select status_id,count(*) over (partition by status_id) as Count_1 from task_status
You can count the column first then filter with max
there is a lot of different way to do this but i prefer using cte.
Here is a example :
with cte as(
select status_id,count(*) cnt from task_status
group by status_id
)
select * from cte
where cnt = (select max(cnt) from cte)
also here is db<>fiddle for better examine.
I modify some data to show the much more understandable output. But idea is same.
also I don't really think status table have any work doing here, but remind me if I misunderstand what you mean.
If you want exactly one status that occurs more often than the others, then I would recommend group by with order by and limit:
select status_id, count(*) as cnt
from task_status
group by status_id
order by cnt desc
limit 1;
This always returns one row, so if there are ties for the most common, then you only get one of the ties.

Select rows by part of composite key that all match the remainder of composite key?

I have a table called "emoji" which stores the code points for each emoji.
CREATE TABLE `emoji` (
`emoji_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`code_point` int(10) unsigned NOT NULL,
`order` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`emoji_id`,`code_point`),
KEY `code_point, order` (`code_point`,`order`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO emoji (emoji_id, code_point, `order`) VALUES(1, 127467, 0), (1, 127479, 1), (2, 127472, 0), (2, 127479, 1);
SQLFiddle
Given code points 127467 and 127479, how would I be able to fetch only the rows that share the same emoji_id (1 in this case)?
I've tried the following:
SELECT
emoji_id,
code_point,
`order`
FROM
emoji
WHERE
code_point IN (127467, 127479) AND
emoji_id IN (
SELECT
emoji_id
FROM
emoji
GROUP BY
emoji_id
HAVING
COUNT(emoji_id) > 1
)
but 127479 is a shared code point among a few different emojis, therefore rendering the count filtering useless and also returning the last record in the set in this example.
You would get the list of emoji's by doing:
SELECT emoji_id
FROM emoji
WHERE code_point IN (127467, 127479)
GROUP BY emoji_id
HAVING COUNT(emoji_id) = 2;
You can incorporate this into a query to get the details:
select e.*
from emoji
where e.emoji_id in (SELECT emoji_id
FROM emoji
WHERE code_point IN (127467, 127479)
GROUP BY emoji_id
HAVING COUNT(emoji_id) = 2
);
I think you sould use having b ut without subquery
SELECT
distinct
emoji_id,
code_point,
`order`
FROM emoji
WHERE code_point IN (127467, 127479)
having cont(*) = 2;

How to aggregate data without group by

I am having a little bit of a situation here.
The environment
I have a database for series here.
One table for the series itself, one for the season connected to the series table, one for the episodes connected to the seasons table.
Since there are air dates for different countries I have another table called 'series_data` which looks like the following:
CREATE TABLE IF NOT EXISTS `episode_data` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`episode_id` int(11) NOT NULL,
`country` char(3) NOT NULL,
`title` varchar(255) NOT NULL,
`date` date NOT NULL,
`tba` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
KEY `episode_id` (`episode_id`),
KEY `date` (`date`),
KEY `country` (`country`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Now I am trying to collect the last aired episodes from each series in the database using the following query:
SELECT
*
FROM
`episode_data` ed
WHERE
`ed`.`date` < CURDATE( ) &&
`ed`.`date` != '1970-01-01' &&
`ed`.`series_id` = 1
GROUP BY
`ed`.`country` DESC
ORDER BY
`ed`.`date` DESC
Since I have everything normalized I changed 'episode_id' with 'series_id' to make the query less complicated.
What I am trying to accomplish
I want to have the last aired episodes for each country which are actually announced (ed.date != '1970-01-01') as the returning result of one query.
What's the problem
I know now (searched google, found not for me working answers here), that the ordering takes place AFTER grouping, so my "date" ordering is completly useless.
The other problem is that the query above is working, but always takes those entries with the lowest id matching my conditions, because those are the first ones in the tables index.
What is the question?
How may accomplish the above. I do not know if the grouping is the right way to do it. If there is no "one liner", I think the only way is a sub query which I want to avoid since this is as far as I know slower than a one liner with the right indexes set.
Hope in here is everything you need :)
Example data
CREATE TABLE IF NOT EXISTS `episode_data` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`episode_id` int(11) NOT NULL,
`country` char(3) NOT NULL,
`title` varchar(255) NOT NULL,
`date` date NOT NULL,
`tba` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
KEY `episode_id` (`episode_id`),
KEY `date` (`date`),
KEY `country` (`country`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `episode_data` (`id`, `episode_id`, `country`, `title`, `date`, `tba`) VALUES
(4942, 2471, 'de', 'Väter und Töchter', '2013-08-06', 0),
(4944, 2472, 'de', 'Neue Perspektiven', '2013-08-13', 0),
(5013, 2507, 'us', 'Into the Deep', '2013-08-06', 0),
(5015, 2508, 'us', 'The Mirror Has Three Faces', '2013-08-13', 0);
Attention!
This is the original table data with "EPISODE_ID" not "SERIES_ID".
The data I want are those with closest dates to today, which are here 4944 and 5015.
If you want the last aired date for each country, then use this aggregation:
SELECT country, max(date) as lastdate
FROM `episode_data` ed
WHERE `ed`.`date` < CURDATE( ) AND
`ed`.`date` != '1970-01-01' AND
`ed`.`series_id` = 1
GROUP BY `ed`.`country`;
If you are trying to get the episode_id and title as well, you can use group_concat() and substring_index():
SELECT country, max(date) as lastdate,
substring_index(group_concat(episode_id order by date desc), ',', 1
) as episode_id,
substring_index(group_concat(title order by date desc separator '|'), '|', 1
) as title
FROM `episode_data` ed
WHERE `ed`.`date` < CURDATE( ) AND
`ed`.`date` != '1970-01-01' AND
`ed`.`series_id` = 1
GROUP BY `ed`.`country`;
Note that this uses a different separator for the title, under the assumption that it might have a comma.

Invalid use of group function - MySQL using COALESCE

I want a database table with links that are used to generate the navigation on a website. For instance, the text 'Home' will link to 'http://example.com/home' and the text 'Twitter' will link to the Twitter URL, etc. I also wanted to be able to change the order in which the links are presented, hence the order column. I also want to be able to edit the links, that's why I'm using auto_incremented id's.
Now I want order to be unique, so my plan was to get the max of order and just add one. This is the query I'm using, but it will return: Invalid use of group function
INSERT INTO
`links`
(`id`, `order`, `text`, `html_text`, `link`, `html_link`)
VALUES
(NULL, COALESCE((MAX(`order`) + 1), 1), 'text', 'text (html)', 'url', 'url (html)');
My table is like this:
CREATE TABLE IF NOT EXISTS `links` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`order` int(11) NOT NULL,
`text` varchar(31) NOT NULL,
`html_text` varchar(63) NOT NULL,
`link` varchar(127) NOT NULL,
`html_link` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `order` (`order`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
How do I get a valid query doing just what I want?
Thanks in advance!
If you want to do it in one shot, you'll have to do an INSERT ... SELECT combination to get the value from the database and insert based on it;
INSERT INTO
`links`
(`id`, `order`, `text`, `html_text`, `link`, `html_link`)
SELECT
NULL, COALESCE((MAX(`order`) + 1), 1), 'text', 'text (html)', 'url', 'url (html)'
FROM `links`;
An SQLfiddle to test with.

MySQL Select Records from 2 not-related Tables Ordering by Timestamp

I'd like to collect data from 2 different mysql tables ordering the result by a timestamp but without merging the columns of the 2 tables in a single row.
T_ONE(one_id,one_someinfo,one_ts)
T_TWO(two_id,two_otherinfo,two_ts)
Notice that the field two_otherinfo is not the same as one_someinfo, the only columns in common are id and timestamp.
The result should be a mix of the two tables ordered by the timestamp but each row, depending on the timestamp, should contain only the respective columns of the table.
For example, if the newest record comes from T_TWO that row should have the T_ONE one_someinfo column empty.
I just need to order the latest news from T_ONE and the latest messages posted on T_TWO so the tables are not related. I'd like to avoid using 2 queries and then merging and ordering the results by timestamp with PHP. Does anyone know a solution to this? Thanks in advance
This is the structure of the table
CREATE TABLE `posts` (
`id` int(10) unsigned NOT NULL auto_increment,
`fromid` int(10) NOT NULL,
`toteam` int(10) NOT NULL,
`banned` tinyint(1) NOT NULL default '0',
`replyid` int(15) default NULL,
`cont` mediumtext NOT NULL,
`timestamp` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
CREATE TABLE `stars` (
`id` int(10) unsigned NOT NULL auto_increment,
`daynum` int(10) NOT NULL,
`userid` int(10) NOT NULL,
`vote` tinyint(2) NOT NULL default '3',
`timestamp` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
INSERT INTO `posts` (`fromid`, `toteam`, `banned`, `replyid`, `cont`, `timestamp`) VALUES(5, 12, 0, 0, 'mess posted#1', 1222222220);
INSERT INTO `posts` (`fromid`, `toteam`, `banned`, `replyid`, `cont`, `timestamp`) VALUES(5, 12, 0, 0, 'mess posted#2', 1222222221);
INSERT INTO `posts` (`fromid`, `toteam`, `banned`, `replyid`, `cont`, `timestamp`) VALUES(5, 12, 0, 0, 'mess posted#3', 1222222223);
INSERT INTO `stars` (`daynum`, `userid`, `vote`, `timestamp`) VALUES(3, 160, 4, 1222222222);
INSERT INTO `stars` (`daynum`, `userid`, `vote`, `timestamp`) VALUES(4, 180, 3, 1222222224);
The result ordering by timestamp DESC should be the second record of table stars with timestamp 1222222224 then the third record of table posts with timestamp 1222222223 and following... Since the tables have got different fields from each other, the first row of the result should contain the columns of the table stars while the columns of table posts should be empty.
The columns of a UNION must be the same name and datatype on every row. In fact, declare column aliases in the first UNION subquery, because it ignores any attempt to rename the column in subsequent subqueries.
If you need the columns from the two subqueries to be different, put in NULL as placeholders. Here's an example, fetching the common columns id and timestamp, and then fetching one custom column from each of the subqueries.
(SELECT p.id, p.timestamp AS ts, p.fromid, NULL AS daynum FROM posts)
UNION
(SELECT s.id, s.timestamp, NULL, s.daynum, FROM stars)
ORDER BY ts DESC
Also put the subqueries in parentheses, so the last ORDER BY applies to the whole result of the UNION, not just to the last subquery.
SELECT one_id AS id, one_someinfo AS someinfo, one_ts AS ts
UNION
SELECT two_id AS id, two_someinfo AS someinfo, two_ts AS ts
ORDER BY ts
SELECT one_id AS id
, one_someinfo AS one_someinfo
, NULL AS two_someinfo
, one_ts AS ts
FROM t_ONE
UNION ALL
SELECT two_id
, NULL
, two_someinfo
, two_ts
FROM t_TWO
ORDER BY ts