Get last mysql record only from a column - mysql

This is my existing table
id name version
| 1 | a | 1.1 |
| 2 | b | 2.1 |
| 3 | c | 3.1 |
| 4 | d | 1.2 |
| 5 | e | 4.1 |
how can I write a query to generate results where i will return all records but only the last record in the column version is selected like below?
id name version
| 4 | d | 1.2 |
| 2 | b | 2.1 |
| 3 | c | 3.1 |
| 5 | e | 4.1 |

If you prefer a slightly less laborious solution...
SELECT x.*
FROM t x
JOIN
( SELECT MAX(grade) grade
FROM t
GROUP
BY FLOOR(grade)
) y
ON y.grade = x.grade
http://sqlfiddle.com/#!9/f17db1/16

This is a bit laborious but it can be done
SELECT
SUBSTRING_INDEX(GROUP_CONCAT(id ORDER BY REPLACE(grade,'.','')*1 DESC),',',1) as id,
SUBSTRING_INDEX(GROUP_CONCAT(letter ORDER BY REPLACE(grade,'.','')*1 DESC),',',1) as letter,
MAX(grade) as grade
FROM
t
GROUP BY SUBSTRING_INDEX(grade,'.',1)
ORDER BY REPLACE(grade,'.','')*1
Assuming the last column is float you can use ORDER BY lastcol directly
FIDDLE
CREATE TABLE t
(`id` int, `letter` varchar(7), `grade` varchar(55))
;
INSERT INTO t
VALUES
(1, 'a', '1.1'),
(2, 'b', '2.1'),
(3, 'c', '3.1'),
(4, 'd', '1.2'),
(5, 'e', '4.1')

Related

How to sort a list of lists using a sql query?

Abstract question
I have a sql-table that contains records in the following form:
(list_id, value) where the list_id is an Integer identifiing a specific list and the value is something that has an order.
I now struggle to write a sql query that returns all records of that table at first ordered by the rank the list has compared to the other lists and then ordered by the value.
The abstract problem is, that I want to sort a list of lists using sql.
Algorithm to compare two lists
The algorithm to compare two lists is the following:
data CompareRes = FirstSmaller | FirstGreater | Equal deriving Show
compareLists :: Ord a => [a] -> [a] -> CompareRes
compareLists [] [] = Equal
-- Longer lists are considered to be smaller
compareLists _ [] = FirstSmaller
compareLists [] _ = FirstGreater
compareLists (x:xs) (y:ys)
| x < y = FirstSmaller
| x > y = FirstGreater
| otherwise = compareLists xs ys
Details
In my specific case the values are all Dates.
So my table looks like this:
CREATE TABLE `list_date` (
`list_id` INT NOT NULL,
`date` DATE NOT NULL,
PRIMARY KEY (`list_id`, `date`)
);
I'm using a mysql:8.0 database so a solution using WINDOW-functions is acceptable.
Example
Data
INSERT INTO `list_date` VALUES
(1, '2019-11-02'), (1, '2019-11-03'), (1, '2019-11-04'), (1, '2019-11-05'), (1, '2019-11-07'), (1, '2019-11-08'), (1, '2019-11-09'),
(2, '2019-11-01'), (2, '2019-11-03'), (2, '2019-11-04'),
(3, '2019-11-01'), (3, '2019-11-02'), (3, '2019-11-03'),
(4, '2019-11-02'), (4, '2019-11-04'), (4, '2019-11-13'), (4, '2019-11-14'),
(5, '2019-11-03'), (5, '2019-11-04'), (5, '2019-11-05'), (5, '2019-11-10'),
(6, '2019-11-01'), (6, '2019-11-02'), (6, '2019-11-03'), (6, '2019-11-05');
Query
Where I really struggle is to create an expression that calculates the list_rank:
SELECT
`list_id`,
`date`,
<PLEASE HELP> as `list_rank`
FROM
`list_date`
ORDER BY
`list_rank`, `date`;
Expected result
| list_id | date | list_rank |
|---------|------------|-----------|
| 6 | 2019-11-01 | 1 |
| 6 | 2019-11-02 | 1 |
| 6 | 2019-11-03 | 1 |
| 6 | 2019-11-05 | 1 |
| 3 | 2019-11-01 | 2 |
| 3 | 2019-11-02 | 2 |
| 3 | 2019-11-03 | 2 |
| 2 | 2019-11-01 | 3 |
| 2 | 2019-11-03 | 3 |
| 2 | 2019-11-04 | 3 |
| 1 | 2019-11-02 | 4 |
| 1 | 2019-11-03 | 4 |
| 1 | 2019-11-04 | 4 |
| 1 | 2019-11-05 | 4 |
| 1 | 2019-11-07 | 4 |
| 1 | 2019-11-08 | 4 |
| 1 | 2019-11-09 | 4 |
| 4 | 2019-11-02 | 5 |
| 4 | 2019-11-04 | 5 |
| 4 | 2019-11-13 | 5 |
| 4 | 2019-11-14 | 5 |
| 5 | 2019-11-03 | 6 |
| 5 | 2019-11-04 | 6 |
| 5 | 2019-11-05 | 6 |
| 5 | 2019-11-10 | 6 |
or
That image is the current live result my application produces. Currently the sorting is implemented using Java.
Edit
After not receiving a better answer, I implemented a solution as suggested by #gordon-linoff:
SELECT
`list_id`,
`date`
FROM
`list_date`
INNER JOIN (
SELECT `sub`.`list_id`,
GROUP_CONCAT(`sub`.`date` ORDER BY `sub`.`date` SEPARATOR '') as `concat_dates`
FROM `list_date` as `sub`
GROUP BY `sub`.`list_id`
) `all_dates` ON (`all_dates`.`list_id` = `list_date`.`list_id`)
ORDER BY
`all_dates`.`concat_dates`, `date`;
I've also created an SQL Fiddle - So you can play around with your solution.
But this solution does not sort the lists as expected because longer lists are considered bigger than smaller lists.
So I am still hoping to receive a solution that solves 100% of my requirements :)
If I understand correctly, you can sort the lists by the dates concatenated together:
select ld.*
from list_date ld join
(select list_id, group_concat(date) as dates
from ld
group by list_id
) ldc
on ld.list_id = ldc.list_id
order by ldc.dates, ld.date;
Since it's for MySql 8 the window functions can be used for this (yay).
Here's a query that first calculates some metrics, to use in the calculation of the ranking:
SELECT
list_id,
`date`,
DENSE_RANK() OVER (ORDER BY ListMinDate ASC, ListCount DESC, ListMaxDate, list_id) AS list_rank
FROM
(
SELECT
list_id,
`date`,
COUNT(*) OVER (PARTITION BY list_id) AS ListCount,
MIN(`date`) OVER (PARTITION BY list_id) AS ListMinDate,
MAX(`date`) OVER (PARTITION BY list_id) AS ListMaxDate
FROM list_date
) q
ORDER BY list_rank, `date`
A test on db<>fiddle here

mysql random selection in inner join

Question Mysql Random Row Query on Inner Join is much the same as mine but it was never answered.
I have a master table m and slave s. S contains 1 to many rows for each m. I would like a query that selects every master row joined to exactly one randomly chosen slave.
If the table schemas were:
M
---
id
S
---
id
mid
then, in pseudo code the query would be:
select * from m inner join s on m.id = s.mid where s.id is one randomly chosen from the values that exist
Can this be translated into real SQL?
I think the following query does the required job but using a subquery (not inner join):
SELECT *, (SELECT id FROM S WHERE S.mid = M.id ORDER BY RAND() LIMIT 1) AS S_id
FROM M
Here is a link to test it.
Hope it helps.
This can be solved using Row_Number() concept. We need to randomly assign row number values within a partition of mid in the table s. And, do a Join from the m table to s using mid and row_number = 1. This will pick a single Random row everytime.
In MySQL version below 8, we can use User-defined Variables to emulate Row_Number(). To understand how this works, you may check this answer for the explanation: https://stackoverflow.com/a/53465139/2469308
Note that this technique will be efficient on Large tables than using a Subquery (in the SELECT clause), as it will be doing overall table Sorting only once
View on DB Fiddle
create table m (id int, m_nm varchar(10));
create table s (id int,
mid int references m(mid),
s_nm varchar(10));
insert into m values(1, "a");
insert into m values(2, "b");
insert into m values(3, "c");
insert into s values(1, 1, "aa");
insert into s values(2, 1, "aa");
insert into s values(3, 2, "bb");
insert into s values(4, 2, "bbb");
insert into s values(5, 2, "bbbb");
insert into s values(6, 3, "cc");
insert into s values(7, 3, "ccc");
Query
SELECT
m.*, s_dt.id, s_dt.mid, s_dt.s_nm
FROM
m
JOIN
(
SELECT
#rn := IF(#m = dt.mid, #rn+1, 1) AS row_num,
#m := dt.mid AS mid,
dt.id,
dt.s_nm
FROM
(
SELECT
id, mid, s_nm, RAND() as rand_num
FROM s
ORDER BY mid, rand_num ) AS dt
CROSS JOIN (SELECT #rn:=0, #m:=0) AS user_vars
) AS s_dt
ON s_dt.mid = m.id AND
s_dt.row_num = 1;
Result (Run #1)
| id | m_nm | id | mid | s_nm |
| --- | ---- | --- | --- | ---- |
| 1 | a | 2 | 1 | aa |
| 2 | b | 5 | 2 | bbbb |
| 3 | c | 7 | 3 | ccc |
Result (Run #2)
| id | m_nm | id | mid | s_nm |
| --- | ---- | --- | --- | ---- |
| 1 | a | 1 | 1 | aa |
| 2 | b | 4 | 2 | bbb |
| 3 | c | 6 | 3 | cc |
Result (Run #3)
| id | m_nm | id | mid | s_nm |
| --- | ---- | --- | --- | ---- |
| 1 | a | 1 | 1 | aa |
| 2 | b | 3 | 2 | bb |
| 3 | c | 7 | 3 | ccc |
MySQL 8.0.2+ / MariaDB 10.3+ solution would be simply the following:
SELECT
m.*, s_dt.id, s_dt.mid, s_dt.s_nm
FROM
m
JOIN
(
SELECT
s.*,
ROW_NUMBER() OVER w AS row_num
FROM s
WINDOW w AS (PARTITION BY mid
ORDER BY RAND())
) AS s_dt
ON s_dt.mid = m.id AND
s_dt.row_num = 1
View on DB Fiddle

How to use the mysql sum() function to calculate the sum of each row?

I want to use mysql to traverse all the goods_id in the goods table, and then query the sum of each row in the order_goods table based on all goods_id.Can you solve this problem?
1.Create goods table and insert table
CREATE TABLE `goods` (
`goods_id` int(11) NOT NULL,
`goods_name` varchar(255) NOT NULL,
PRIMARY KEY (`goods_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `goods` (`goods_id`, `goods_name`) VALUES
(1, 'Apple'),
(2, 'Xiaomi'),
(3, 'Huawei');
2.Create order_goods and insert table
CREATE TABLE `order_goods` (
`id` int(11) NOT NULL,
`order_id` int(11) NOT NULL,
`goods_id` int(11) NOT NULL,
`goods_name` varchar(255) NOT NULL,
`goods_num` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `order_goods` (`id`, `order_id`, `goods_id`, `goods_name`,
`goods_num`) VALUES
(1, 1, 1, 'iPhone X', 2),
(2, 2, 1, 'iPhone 8 plus', 1),
(3, 3, 2, 'Y69A', 1),
(4, 4, 2, 'X21', 3),
(5, 5, 3, 'nova 3', 1),
(6, 6, 3, 'P20 Pro', 3),
(7, 7, 3, 'Mate 10 Pro', 5);
3.Query the goods table
mysql> select * from goods;
+----------+------------+
| goods_id | goods_name |
+----------+------------+
| 1 | Apple |
| 2 | Xiaomi |
| 3 | Huawei |
+----------+------------+
3 rows in set (0.00 sec)
4.Query the order_goods table
mysql> select * from order_goods;
+----+----------+----------+---------------+-----------+
| id | order_id | goods_id | goods_name | goods_num |
+----+----------+----------+---------------+-----------+
| 1 | 1 | 1 | iPhone X | 2 |
| 2 | 2 | 1 | iPhone 8 plus | 1 |
| 3 | 3 | 2 | Y69A | 1 |
| 4 | 4 | 2 | X21 | 3 |
| 5 | 5 | 3 | nova 3 | 1 |
| 6 | 6 | 3 | P20 Pro | 3 |
| 7 | 7 | 3 | Mate 10 Pro | 5 |
+----+----------+----------+---------------+-----------+
7 rows in set (0.00 sec)
5.I want to try to get this effect!
+----+----------+----------+---------------+
| id | goods_id | goods_name | num |
+----+----------+----------+---------------+
| 1 | 1 | Apple | 3 |
| 2 | 2 | Xiaomi | 4 |
| 3 | 3 | Huawei | 9 |
+----+----------+----------+---------------+
For example, write sum(g.goods_num) as num
How to write the sql statement above?
X is the query which u do group by to have SUM()
Then u join it with goods to have the name and id and whatever u need from this table
This might be better with performance to do the group by first
WITH X AS
(
SELECT goods_id,SUM(goods_num) AS num
FROM order_goods
GROUP BY goods_id
)
SELECT G.goods_name , X.num
FROM X
INNER JOIN goods G ON G.goods_id = X.goods_id
No WITH clause so bring your join to your query
like this:
SELECT goods_id,SUM(goods_num) AS num
FROM order_goods OG
INNER JOIN goods G ON G.goods_id = OG.goods_id
GROUP BY OG.goods_id -- or G.goods_name
Firstly, do a INNER JOIN between the two tables, using goods_id.
Then, do a grouping of goods_id using GROUP BY clause.
Eventually, using aggregation function SUM function, get the desired sum value and Alias it as num.
Try the following query:
SELECT g.goods_id,
g.goods_name,
SUM(og.goods_num) AS num
FROM goods g
JOIN order_goods og ON og.goods_id = g.goods_id
GROUP BY g.goods_id, g.goods_name
Use inner join and aggregation with group by:
http://sqlfiddle.com/#!9/ae4fd9/3
select go.goods_id,g.goods_name,sum(goods_num) as num
from goods g inner join order_goods go
on g.goods_id=go.goods_id
group by g.goods_name,go.goods_id
Output:
goods_id goods_name num
1 Apple 3
3 Huawei 9
2 Xiaomi 4

SubQuery returns one row when getting data from main query comma separated ids?

SELECT
e.*,
(
SELECT GROUP_CONCAT(topic_name)
FROM topic
WHERE id IN (e.topic_ids)) AS topics
FROM exam e
result :
topics = xyz topic
this query returns a single name of topic as result but when i use this :
SELECT
e.*,
(
SELECT GROUP_CONCAT(topic_name)
FROM topic
WHERE id IN (1,4)) AS topics
FROM exam e
result :
topics = xyz topic,abc topic
That works fine,and exam table had the same value in DB (comma separated topic ids = 1,4) as varchar type field.
is there any issue with datatype of field?
First, let me lecture you about how bad CSV in field is.
| id | topic_ids |
|----|-----------|
| 1 | a,b,c |
| 2 | a,b |
This, is how Satan look like in relational DB. Probably the worst, just after the
"lets put columns as line and use a recursive join to get everything back."
How it should be ?
exam
| id |
|----|
| 1 |
| 2 |
exam_topic
| exam_id | topic_id |
|---------|----------|
| 1 | a |
| 1 | b |
| 1 | c |
| 2 | a |
| 2 | b |
topic
| id |
|----|
| a |
| b |
| c |
Now, as awful as it may be, this is the "dynamic" alternative, using FIND_IN_SET() :
SELECT
e.*,
(
SELECT GROUP_CONCAT(topic_name)
FROM topic
WHERE FIND_IN_SET(id, e.topic_ids) > 0
) AS topics
FROM exam e
SQL Fiddle
MySQL 5.6 Schema Setup:
CREATE TABLE exam
(`id` int, `topic_ids` varchar(5))
;
INSERT INTO exam
(`id`, `topic_ids`)
VALUES
(1, 'a,b,c'),
(2, 'a,b'),
(3, 'b,c,d'),
(4, 'd')
;
CREATE TABLE topic
(`id` varchar(1), `topic_name` varchar(4))
;
INSERT INTO topic
(`id`, `topic_name`)
VALUES
('a', 'topA'),
('b', 'topB'),
('c', 'topC'),
('d', 'topD')
;
Query 1:
SELECT
e.*,
(
SELECT GROUP_CONCAT(topic_name)
FROM topic
WHERE FIND_IN_SET(id, e.topic_ids) > 0
) AS topics
FROM exam e
Results:
| id | topic_ids | topics |
|----|-----------|----------------|
| 1 | a,b,c | topA,topB,topC |
| 2 | a,b | topA,topB |
| 3 | b,c,d | topB,topC,topD |
| 4 | d | topD |

Complex SQL query with group by and having in condition

Suppose I have the table test below:
------------------------------
id | active| record
------------------------------
3 | O | 2015-10-16
3 | O | 2015-10-15
3 | N | 2015-10-14
4 | N | 2015-10-15
4 | O | 2015-10-14
I want to do an update on the table on the lines with:
- An id having the column active = 'O' more than once.
- Among theses lines having active = 'O' more than once, the update shall change the value of active to 'N', except for the one with max(record), which will stay with active = 'O'.
In my example, the id having the column active = 'O' more than once is id = 3.
id |active | record
------------------------------
3 | O | 2015-10-16
3 | O | 2015-10-15
3 | N | 2015-10-14
I want to have this result:
id |active | record
------------------------------
3 | O | 2015-10-16
3 | N | 2015-10-15
3 | N | 2015-10-14
I tried this query, but there is an error:
update test as t1,
(select id
from test
where active = 'O'
group by id
having count(*) > 1) as t2
set t1.actif = 'N'
where t1.record != max(t2.record);
Thanks in advance!
Given this sample data:
CREATE TABLE t
(`id` int, `active` varchar(1), `record` date)
;
INSERT INTO t
(`id`, `active`, `record`)
VALUES
(3, 'O', '2015-10-16'),
(3, 'O', '2015-10-15'),
(3, 'N', '2015-10-14'),
(4, 'N', '2015-10-15'),
(4, 'O', '2015-10-14')
;
This query
UPDATE
t
JOIN (
SELECT
id, MAX(record) AS max_record
FROM
t
WHERE active = 'O'
GROUP BY id
HAVING COUNT(*) > 1
) sq ON t.id = sq.id
SET t.active = IF(t.record = sq.max_record, 'O', 'N');
produces this result:
+------+--------+------------+
| id | active | record |
+------+--------+------------+
| 3 | O | 2015-10-16 |
| 3 | N | 2015-10-15 |
| 3 | N | 2015-10-14 |
| 4 | N | 2015-10-15 |
| 4 | O | 2015-10-14 |
+------+--------+------------+
Can you try with something like this
select ID,
count(*) Counted,
max(record) record
into #TempTable from Table
where Active = 'O'
group by ID
Update tab
set tab.Active = 'N'
from Table tab
join #tempTable temp on tab.ID = temp.ID
where temp.Counted > 1 and
tab.record != temp.record
drop table #tempTable
Basically, you just counting Os while grabbing ID and max record into temp table and after that you doing the update, also this code might need some changes as i just took a glance to point you toward direction i would do it