mysql select groupby 2 columns - mysql

MySQL
Table Messages
| fromuser | toUser | message | time
| 1 | 2 | Hi? | +1
| 2 | 1 | Hello! | +2
| 1 | 3 | There? | +3
| 3 | 1 | Yes | +4
| 2 | 3 | Hey | +5
| 3 | 2 | Sup? | +6
| 1 | 2 | :) | +7
| 1 | 4 | thanks |
| 4 | 1 | welcome |
I need a query for latest message
Closest I got is
SELECT * FROM (SELECT * FROM Messages WHERE fromUser=1 OR toUser=1 ORDER BY time DESC) AS msg GROUP BY fromUser,toUser
Result:
| fromuser | toUser | message | time
| 2 | 1 | Hello! | +2
| 1 | 3 | There? | +3
| 3 | 1 | Yes | +4
| 1 | 2 | :) | +7
| 1 | 4 | thanks |
| 4 | 1 | welcome |
it returns the latest from and latest to.
What I want is
| fromuser | toUser | message | time
| 3 | 1 | Yes | +4
| 1 | 2 | :) | +7
| 4 | 1 | welcome |
Latest of from and to of user1, like how a list of chats work.

The OP is looking for the latest message from each dialogue within which a given user participates.
There's a probably a more elegant solution, but here's one way...
Note that my dataset has a surrogate PK...
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(fromuser INT NOT NULL
,toUser INT NOT NULL
,message VARCHAR(20) NOT NULL
,message_id INT NOT NULL PRIMARY KEY
);
INSERT INTO my_table VALUES
(1,2,'Hi?',1),
(2,1,'Hello!',2),
(1,3,'There?',3),
(3,1,'Yes',4),
(2,3,'Hey',5),
(3,2,'Sup?',6),
(1,2,':)',7),
(1,4,'thanks',8),
(4,1,'welcome',9);
SELECT a.* FROM my_table a
JOIN
(
SELECT MAX(message_id) message_id FROM
(
SELECT toUser correspondent,message,message_id FROM my_table WHERE fromuser = 1
UNION
SELECT fromuser,message,message_id FROM my_table WHERE touser = 1
) x
GROUP BY correspondent
) b
ON b.message_id = a.message_id;
+----------+--------+---------+------------+
| fromuser | toUser | message | message_id |
+----------+--------+---------+------------+
| 1 | 2 | :) | 7 |
| 3 | 1 | Yes | 4 |
| 4 | 1 | welcome | 9 |
+----------+--------+---------+------------+

Related

How to query MIN value of MAX subquery with two distinct columns?

I have a table like this:
+---------------+--------------+------+-----+----------+
| Field | Type | Null | Key | Default |
+---------------+--------------+------+-----+----------+
| id | smallint(6) | NO | PRI | NULL |
| Book | tinyint(4) | NO | | NULL |
| Chapter | smallint(6) | NO | | NULL |
| Paragraph | smallint(6) | NO | | NULL |
| Text | text | YES | | NULL |
| RevisionNum | mediumint(9) | NO | PRI | NULL |
+---------------+--------------+------+-----+----------+
mysql> select id,Book,Chapter,Paragraph,RevisionNum FROM MyTable ORDER BY id LIMIT 11;
+-----+------+---------+-----------+-------------+
| id | Book | Chapter | Paragraph | RevisionNum |
+-----+------+---------+-----------+-------------+
| 1 | 1 | 1 | 1 | 0 |
| 1 | 1 | 1 | 1 | 1 |
| 1 | 1 | 1 | 1 | 2 |
| 2 | 1 | 2 | 2 | 0 |
| 2 | 1 | 2 | 2 | 1 |
| 2 | 1 | 2 | 2 | 2 |
| 2 | 1 | 2 | 2 | 3 |
| 3 | 1 | 2 | 3 | 0 |
| 4 | 1 | 2 | 4 | 0 |
| 4 | 1 | 2 | 4 | 1 |
| 5 | 1 | 3 | 5 | 0 |
+-----+------+---------+-----------+-------------+
To find a book or chapter which has no unrevised paragraph,
I wish to query either the minimum value of the maximums of
all the distinct id's for that chapter or book, or else in
some fashion determine that no id remains unedited (with a
MAX(RevisionNum) of zero).
Most of my attempts to date have ended in errors like this one:
SELECT DISTINCT Book,RecordNum FROM MyTable
-> WHERE 0 < ALL (SELECT DISTINCT RecordNum,MAX(RevisionNum)
FROM MyTable
WHERE MAX(RevisionNum) > 0);
ERROR 1111 (HY000): Invalid use of group function
...And I wasn't using the "GROUP BY" function at all!
The following query produces results, but simply
gives ALL id's, and does not actually show a unique
set of Book records, as requested. How could this happen?
SELECT DISTINCT Book,id,MAX(RevisionNum) FROM MyTable GROUP BY id LIMIT 5;
+------+----+------------------+
| Book | id | MAX(RevisionNum) |
+------+----+------------------+
| 1 | 1 | 30 |
| 1 | 2 | 16 |
| 1 | 3 | 15 |
| 1 | 4 | 10 |
| 1 | 5 | 9 |
+------+----+------------------+
What would the correct query be to give results more like this:
+------+-----+-----------------------+
| Book | id | MIN(MAX(RevisionNum)) |
+------+-----+-----------------------+
| 1 | 5 | 3 |
| 2 | 17 | 1 |
| 3 | 33 | 2 |
| 4 | 147 | 0 |
| 5 | 225 | 2 |
+------+-----+-----------------------+
Are you looking for two levels of aggregation?
select id, book, min(max_revisionnum)
from (select id, book, chapter, paragraph, max(revisionnum) as max_revisionnum
from mytable
group by id, book, chapter, paragraph
) t
group by id, book;
EDIT:
Based on your comment, you can use:
select *
from (select id, book, chapter, paragraph, max(revisionnum) as max_revisionnum,
row_number() over (partition by book order by max(revisionnum) desc) as seqnum
from mytable
group by id, book, chapter, paragraph
) t
where seqnum = 1;
Here is a db<>fiddle.
In older versions of MariaDB, you can use a correlated subquery:
select t.*
from mytable t
where (id, book, chapter, paragraph, revisionnum) = (select t2.id, t2.book, t2.chapter, t2.paragraph, t2.revisionnum
from mytable t2
where t2.book = t.book
order by t2.revisionnum desc
limit 1
);
For this query, try adding an index on (book, revisionnum desc).

mysql pivot using column and row numbers

I am stuck in this situation where I need to use Row Number and Column Number values from table's columns to derive the output mentioned below. I have tried everything - if/else, case when/then but not helping.
Any help/suggestions are really appreciated!
Here is a mocked up sample data present in db table -
+--------+--------+--------+----------+-------------+
| Record | ColNbr | RowNbr | ColTitle | CellContent |
+--------+--------+--------+----------+-------------+
| 1 | 1 | 1 | Unit | sqf |
| 1 | 1 | 2 | Unit | cm |
| 1 | 2 | 1 | Desc | roof |
| 1 | 2 | 2 | Desc | rod |
| 1 | 3 | 1 | Material | concrete |
| 1 | 3 | 2 | Material | steel |
| 1 | 4 | 1 | Quantity | 100 |
| 1 | 4 | 2 | Quantity | 12 |
| 1 | 1 | 1 | Unit | liter |
| 1 | 1 | 2 | Unit | ml |
| 1 | 2 | 1 | Desc | bowl |
| 1 | 2 | 2 | Desc | plate |
| 1 | 3 | 1 | Material | plastic |
| 1 | 3 | 2 | Material | glass |
| 1 | 4 | 1 | Quantity | 2 |
| 1 | 4 | 2 | Quantity | 250 |
+--------+--------+--------+----------+-------------+
Expected Output -
+--------+--------+--------+----------+-------------+
| Record | Unit | Desc | Material | Quantity |
+--------+--------+--------+----------+-------------+
| 1 | sqf | roof | concrete | 100 |
| 1 | cm | rod | steel | 12 |
| 2 | liter | bowl | plastic | 2 |
| 2 | ml | plate | glass | 250 |
+--------+--------+--------+----------+-------------+
If your actual data is like that, I suggest that you consider to separate the data to; for example, 4 different tables (unit,description,material & a table to store all that ids+quantity). The former 3 tables will store the prerequisite info that get minor updates throughout time and the last table will store all the quantity records. Let's say your tables will look something like this:
CREATE TABLE `Unit` (
unit_id INT,
unit_name VARCHAR(50));
+---------+-----------+
| unit_id | unit_name |
+---------+-----------+
| 1 | sqf |
| 2 | cm |
| 3 | liter |
| 4 | ml |
+---------+-----------+
CREATE TABLE `Description` (
desc_id INT,
desc_name VARCHAR(50));
+---------+-----------+
| desc_id | desc_name |
+---------+-----------+
| 1 | roof |
| 2 | rod |
| 3 | bowl |
| 4 | plate |
+---------+-----------+
CREATE TABLE `Material` (
mat_id INT,
mat_name VARCHAR(50));
+--------+----------+
| mat_id | mat_name |
+--------+----------+
| 1 | concrete |
| 2 | steel |
| 3 | plastic |
| 4 | glass |
+--------+----------+
CREATE TABLE `Records` (
unit_id INT,
desc_id INT,
mat_id INT,
quantity DECIMAL(14,4));
+---------+---------+--------+----------+
| unit_id | desc_id | mat_id | Quantity |
+---------+---------+--------+----------+
| 1 | 1 | 1 | 100 |
| 2 | 2 | 2 | 12 |
| 3 | 3 | 3 | 2 |
| 4 | 4 | 4 | 250 |
+---------+---------+--------+----------+
Then you insert the data accordingly.
Anyhow, for your existing data example, it could be done but there are some concern over whether the unit+desc+material+quantity matching are correct. The only way I can maybe at least think that it's correctly matched is by giving all of the query a similar ORDER BY clause. Hence, the following:
SELECT A.record,A.unit,B.Desc,C.Material,D.Quantity FROM
(SELECT #rn:=#rn+1 AS record,CASE WHEN coltitle='unit' THEN cellcontent END AS Unit
FROM yourtable, (SELECT #rn :=0 ) v
HAVING unit IS NOT NULL
ORDER BY colnbr) A LEFT JOIN
(SELECT #rn1:=#rn1+1 AS record,CASE WHEN coltitle='Desc' THEN cellcontent END AS `Desc`
FROM yourtable, (SELECT #rn1 :=0 ) v
HAVING `Desc` IS NOT NULL
ORDER BY colnbr) B ON a.record=b.record LEFT JOIN
(SELECT #rn2:=#rn2+1 AS record,CASE WHEN coltitle='material' THEN cellcontent END AS Material
FROM yourtable, (SELECT #rn2:=0 ) v
HAVING Material IS NOT NULL
ORDER BY colnbr) C ON a.record=c.record LEFT JOIN
(SELECT #rn3:=#rn3+1 AS record,CASE WHEN coltitle='Quantity' THEN cellcontent END AS Quantity
FROM yourtable, (SELECT #rn3:=0 ) v
HAVING Quantity IS NOT NULL
ORDER BY colnbr) D ON a.record=d.record;
The idea here is to make a sub-query based on COLTITLE then assign a numbering/ranking (#rn,#rn1,#rn2,#rn3) variable to each of the sub-query and join them up using LEFT JOIN. Now, this experiment works to exactly return the output that you need but its not a definite answer because there are some part that is questionable especially on matching the combination correctly. Hopefully, this will give you some idea.

Remove duplicates leaving at least one with highest parameter from group

I have following schema:
+--+------+-----+----+
|id|device|token|cash|
+--+------+-----+----+
column device is unique and token is not unique and null by default.
What i want to achieve is to set all duplicate token values to default (null) leaving only one with highest cash. If duplicates have same cash leave first one.
I have heard about cursor, but it seems that it can be done with usual query.
I have tried following SELECT only to see if im right about my thought how to achieve this, but it seems im wrong.
SELECT
*
FROM
db.table
WHERE
db.table.token NOT IN (SELECT
*
FROM
(
SELECT DISTINCT
MAX(db.table.balance)
FROM
db.table
GROUP BY db.table.balance) temp
)
For example:
This table after query
+-----+---------+--------+-------+
| id | device | token | cash|
+-----+---------+--------+-------+
| 1 | dev_1 | tkn_1 | 3 |
| 2 | dev_2 | tkn_1 | 10 |
| 3 | dev_3 | tkn_2 | 10 |
| 4 | dev_4 | tkn_2 | 14 |
| 5 | dev_5 | tkn_3 | 10 |
| 6 | dev_6 | null | 10 |
| 7 | dev_7 | null | 10 |
| 8 | dev_8 | tkn_4 | 11 |
| 8 | dev_8 | tkn_4 | 11 |
| 8 | dev_8 | tkn_5 | 11 |
+-----+---------+--------+-------+
should be:
+-----+---------+--------+-------+
| id | device | token | cash|
+-----+---------+--------+-------+
| 1 | dev_1 | null | 3 |
| 2 | dev_2 | tkn_1 | 10 |
| 3 | dev_3 | null | 10 |
| 4 | dev_4 | tkn_2 | 14 |
| 5 | dev_5 | tkn_3 | 10 |
| 6 | dev_6 | null | 10 |
| 7 | dev_7 | null | 10 |
| 8 | dev_8 | tkn_4 | 11 |
| 8 | dev_8 | null | 11 |
| 8 | dev_8 | tkn_5 | 15 |
+-----+---------+--------+-------+
Thanks in advance :)
Try using an EXISTS subquery:
UPDATE yourTable t1
SET token = NULL
WHERE EXISTS (SELECT 1 FROM (SELECT * FROM yourTable) t2
WHERE t2.token = t1.token AND
t2.cash > t1.cash);
Demo
Note that this answer assumes that there would never be a tie for two token records having the same highest cash amount.
To set exactly one row in the even of duplicates on the maximum cash, use the id:
update t join
(select tt.*,
(select t3.id
from t t3
where t3.token = tt.token
order by t3.cash desc, id desc
) as max_cash_id
from t tt
) tt
on t.id = tt.id and t.id < tt.max_cash_id
set token = null;

SQL Advanced SELECT Statement

translations
+---------+----------------+----------+---------+
| id_user | id_translation | referrer | id_word |
+---------+----------------+----------+---------+
| 1 | 3 | NULL | 4 |
| 1 | 17 | NULL | 3 |
| 2 | 17 | NULL | 5 |
| 2 | 17 | NULL | 1 |
| 2 | 17 | NULL | 7 |
words
+----+------+
| id | word |
+----+------+
| 4 | out |
+----+------+
users_translations
+---------+----------------+----------+---------+
| id_user | id_translation | referrer | id_word |
+---------+----------------+----------+---------+
| 1 | 17 | 1 | 4 |
| 2 | 17 | 2 | 4 |
| 3 | 18 | NULL | 4 |
I need to select all translations for current word and id_translation, but if in the row referrer = 1 (current user), then I don't need another results (translations from another users for current word), if there is no referrer = 1, show all.
SELECT DISTINCT `t`.*, `ut`.`id_user` AS tuser
FROM translations AS t
LEFT JOIN users_translations AS ut ON `t`.`id` = `ut`.`id_translation`
INNER JOIN words ON `words`.`id` = `ut`.`id_word` OR `words`.`id` = `t`.`id_word`
WHERE (`word` = 'help')
ORDER BY `t`.`translation` ASC
+----+-------------+---------+---------+-------+
| id | translation | id_word | id_user | tuser |
+----+-------------+---------+---------+-------+
| 17 | допомагати | 4 | 1 | 2 |
| 17 | допомагати | 4 | 1 | 1 |
First row doesn't need, because we have tuser = 1. When there is no tuser = 1, all results should be returned.
I don't understand how to build select statement and I will be very appreciative that somebody shows me how to make it work.
First thing that comes to mind
--add this to your where clause
id_user <=
CASE WHEN EXISTS(SELECT * FROM translations WHERE id_user = 1 AND id_word = words.id_word)
THEN 1
ELSE (SELECT MAX(Id) FROM translations)
END

How do I properly format this MySQL JOIN Statement?

I've got a table that looks like:
Table 1 ->
+----+--------+--------+
| id | name | author |
+----+--------+--------+
| 1 | First | Me |
| 2 | Second | You |
+----+--------+--------+
Table 2 ->
+-----+------------+-----------+------------+
| mid | table1_id | key | value |
+-----+------------+-----------+------------+
| 1 | 1 | desc | hello |
| 2 | 1 | begin_day | monday |
| 3 | 1 | end_day | tuesday |
| 4 | 2 | desc | goodbye |
| 5 | 2 | begin_day | wednesday |
| 6 | 2 | end_day | friday |
+-----+------------+-----------+------------+
The relationship here is that the id in table 1 corresponds to the table1_id in table 2.
The output that I'm trying to get is...
+----+---------+---------+-------------+-----------+-----------+
| id | name | author | desc | begin_day | end_day |
+----+---------+---------+-------------+-----------+-----------+
| 1 | First | Me | hello | monday | tuesday |
| 1 | Second | You | goodbye | wednesday | friday |
+----+---------+---------+-------------+-----------+-----------+
I've tried several different join statements -- all a variation of the below. I'm not that well versed in MySQL queries, however.
SELECT * FROM table_1 LEFT JOIN table_2 on table_1.id = table_2.table1_id
Which produces...
+----+----------+----------+----------+------------+-----------+
| id | mid | name | author | key | value |
+----+----------+----------+----------+------------+-----------+
| 1 | 1 | First | Me | desc | hello |
| 1 | 2 | First | Me | begin_day | monday |
| 1 | 3 | First | Me | end_day | tuesday |
| 2 | 4 | Second | You | desc | goodbye |
| 2 | 5 | Second | You | begin_day | wednesday|
| 2 | 6 | Second | You | end_day | friday |
Obviously, iterating over this join statement produces 6 results, 1 for each row in table 2 that matches the id in table 1. How can I avoid this with a proper query statement?
Thank you in advance.
You can use a case statement if you know all of the columns you will be getting, as follows:
Select distinct table_1.*,
case when table_2.key='desc' then value end as desc,
case when table_2.key='begin_day' then value end as begin_day,
case when table_2.key='end_day' then value end as end_day
FROM table_1 LEFT JOIN table_2 on table_1.id = table_2.table1_id
Hope this helps!
SELECT
table_1.*,
MAX(IF(key='desc', value, NULL)) AS 'desc',
MAX(IF(key='begin_day', value, NULL)) AS begin_day,
MAX(IF(key='end_day', value, NULL)) AS end_day
FROM table_1
LEFT JOIN table_2 ON (id = table1_id)
GROUP BY id;