MySQL UNION query from one table + ORDER BY - mysql

I have one table with two queries and I need to sort it with descending type using ORDER BY. Here is my MySQL query that does not work properly:
(SELECT `text`
FROM `comments`
WHERE user_fr='".$user."' && archive='1'
ORDER BY `is_new_fr` DESC)
UNION
(SELECT `text`
FROM `message`
WHERE user_to='".$user."' && archive='1'
ORDER BY `is_new_to` DESC)
Description!
is_new_fr and is_new_to counts total new messages.
Here is my table contant:
user_fr | user_to | archive | is_new_fr | is_new_to| text
name1 | name2 | 1 | 2 | 0 | testing...
name2 | name1 | 1 | 0 | 5 | testing ...
I want to make an order that 1st will display note that has more messages to few, or by another words using DESCending type.
This is the display on the page I want to do:
Open dialog with name2. Messages (5)
Open dialog with name1. Messages (2)
Thank you!

The only way I know is a subquery:
SELECT `text`
FROM (
SELECT `text`, `is_new_fr` AS `is_new`
FROM `comments`
WHERE user_fr = '".$user."'
AND archive = '1'
UNION
SELECT `text`, `is_new_to` AS `is_new`
FROM `message`
WHERE user_to = '".$user."'
AND archive = '1'
) ORDER BY `is_new` DESC

Related

SQL where not exists with multiple rows and status

I have the following tables (minified for the sake of simplicity):
CREATE TABLE IF NOT EXISTS `product_bundles` (
bundle_id int AUTO_INCREMENT PRIMARY KEY,
-- More columns here for bundle attributes
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS `product_bundle_parts` (
`part_id` int AUTO_INCREMENT PRIMARY KEY,
`bundle_id` int NOT NULL,
`sku` varchar(255) NOT NULL,
-- More columns here for product attributes
KEY `bundle_id` (`bundle_id`),
KEY `sku` (`sku`)
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS `products` (
`product_id` mediumint(8) AUTO_INCREMENT PRIMARY KEY,
`sku` varchar(64) NOT NULL DEFAULT '',
`status` char(1) NOT NULL default 'A',
-- More columns here for product attributes
KEY (`sku`),
) ENGINE=InnoDB;
And I want to show only the 'product bundles' that are currently completely in stock and defined in the database (since these get retrieved from a third party vendor, there is no guarantee the SKU is defined). So I figured I'd need an anti-join to retrieve it accordingly:
SELECT SQL_CALC_FOUND_ROWS *
FROM product_bundles AS bundles
WHERE 1
AND NOT EXISTS (
SELECT *
FROM product_bundle_parts AS parts
LEFT JOIN products AS products ON parts.sku = products.sku
WHERE parts.bundle_id = bundles.bundle_id
AND products.status = 'A'
AND products.product_id IS NULL
)
-- placeholder for other dynamic conditions for e.g. sorting
LIMIT 0, 24
Now, I sincerely thought this would filter out the products by status, however, that seems not to be the case. I then changed one thing up a bit, and the query never finished (although I believe it to be correct):
SELECT SQL_CALC_FOUND_ROWS *
FROM product_bundles AS bundles
WHERE 1
AND NOT EXISTS (
SELECT *
FROM product_bundle_parts AS parts
LEFT JOIN products AS products ON parts.sku = products.sku
AND products.status = 'A'
WHERE parts.bundle_id = bundles.bundle_id
AND products.product_id IS NULL
)
-- placeholder for other dynamic conditions for e.g. sorting
LIMIT 0, 24
Example data:
product_bundles
bundle_id | etc.
1 |
2 |
3 |
product_bundle_parts
part_id | bundle_id | sku
1 | 1 | 'sku11'
2 | 1 | 'sku22'
3 | 1 | 'sku33'
4 | 1 | 'sku44'
5 | 2 | 'sku55'
6 | 2 | 'sku66'
7 | 3 | 'sku77'
8 | 3 | 'sku88'
products
product_id | sku | status
101 | 'sku11' | 'A'
102 | 'sku22' | 'A'
103 | 'sku33' | 'A'
104 | 'sku44' | 'A'
105 | 'sku55' | 'D'
106 | 'sku66' | 'A'
107 | 'sku77' | 'A'
108 | 'sku99' | 'A'
Example result: Since the product status of product #105 is 'D' and 'sku88' from part #8 was not found:
bundle_id | etc.
1 |
I am running Server version: 10.3.25-MariaDB-0ubuntu0.20.04.1 Ubuntu 20.04
So there are a few questions I have.
Why does the first query not filter out products that do not have the status A.
Why does the second query not finish?
Are there alternative ways of achieving the same thing in a more efficient matter, as this looks rather cumbersome.
First of all, I've read that SQL_CALC_FOUND_ROWS * is much slower than running two separate query (COUNT(*) and then SELECT * or, if you make your query inside another programming language, like PHP, executing the SELECT * and then count the number of rows of the result set)
Second: your first query returns all the boundles that doesn't have ANY active products, while you need the boundles with ALL products active.
I'd change it in the following:
SELECT SQL_CALC_FOUND_ROWS *
FROM product_bundles AS bundles
WHERE NOT EXISTS (
SELECT 'x'
FROM product_bundle_parts AS parts
LEFT JOIN products ON (parts.sku = products.sku)
WHERE parts.bundle_id = bundles.bundle_id
AND COALESCE(products.status, 'X') != 'A'
)
-- placeholder for other dynamic conditions for e.g. sorting
LIMIT 0, 24
I changed the products.status = 'A' in products.status != 'A': in this way the query will return all the boundles that DOESN'T have inactive products (I also removed the condition AND products.product_id IS NULL because it should have been in OR, but with a loss in performance).
You can see my solution in SQLFiddle.
Finally, to know why your second query doesn't end, you should check the structure of your tables and how they are indexed. Executing an Explain on the query could help you to find eventual issues on the structure. Just put the keyword EXPLAIN before the SELECT and you'll have your "report" (EXPLAIN SELECT * ....).

Get the newest record from each group

I want to sort the last messages with every user that a specific user has chated from ejabberd archive table.
The fields that I'm using are these
id (message id)
username (username copy)
bare_peer (user that is chatting with)
txt (text chat)
created_at (time created)
What I'm trying to achieve is something like that, but I need to group message by bare_peer with username as 1_usernode, but only the last messages.
I already tested a lot of queries, but none of them worked.
This is the first query I tried.
SELECT id, username, bare_peer, txt FROM archive where
username = '1_usernode' GROUP BY bare_peer ORDER BY created_at DESC;
And this is the output.
+------+------------+-------------------------------------------------------+---------------------+
| id | username | bare_peer | txt | created_at |
+------+------------+------------------------+------------------------------+---------------------+
| 1095 | 1_usernode | 10_usernode#localhost | Hello !!! | 2016-07-17 21:15:17 |
| 1034 | 1_usernode | 15_usernode#localhost | hey sup ? | 2016-07-13 22:40:29 |
| 1107 | 1_usernode | 13_usernode#localhost | oi | 2016-07-18 00:09:28 |
| 1078 | 1_usernode | 2_usernode#localhost | Hello this is just a Test!!! | 2016-07-15 16:30:50 |
| 1101 | 1_usernode | 7_usernode#localhost | hey | 2016-07-18 00:05:55 |
| 1084 | 1_usernode | 3_usernode#localhost | Hey how are you? | 2016-07-15 19:36:44 |
| 1085 | 1_usernode | 4_usernode#localhost | Hey how are you doing ? | 2016-07-17 19:20:00 |
Use This Query It's Helful.
SELECT MAX(id), username, bare_peer, txt FROM archive where
username = '1_usernode' ORDER BY created_at DESC
declare created_at as datetime
try this
DROP TABLE IF EXISTS `archive`;
CREATE TABLE `archive` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`username` VARCHAR(50) DEFAULT NULL,
`bare_peer` VARCHAR(50) DEFAULT NULL,
`txt` TEXT,
`created_at` DATETIME DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=latin1;
/*Data for the table archive */
INSERT INTO `archive`(`id`,`username`,`bare_peer`,`txt`,`created_at`)
VALUES (1034,'1_usernode','15_usernode#localhost','hey sup ?','2016-07-13 22:40:29'),
(1078,'1_usernode','2_usernode#localhost','Hello this IS just a Test!!!','2016-07-15 16:30:50'),
(1084,'1_usernode','3_usernode#localhost','Hey how are you?','2016-07-15 19:36:44'),
(1085,'1_usernode','4_usernode#localhost','Hey how are you doing ?','2016-07-17 19:20:00'),
(1095,'1_usernode','10_usernode#localhost','Hello !!!','2016-07-17 21:15:17'),
(1101,'1_usernode','7_usernode#localhost','hey','2016-07-18 00:05:55'),
(1107,'1_usernode','13_usernode#localhost','oi','2016-07-18 00:09:28');
Then run your query
SELECT id, username, bare_peer, txt FROM archive where
username = '1_usernode' GROUP BY bare_peer ORDER BY created_at DESC;
Try this query :-
SELECT archive.id, archive.max_id, archive.username, archive.bare_peer, archive.txt
FROM archive join
(SELECT MAX(id) max_id, username, bare_peer, txt
FROM archivewhere username = '1_usernode' GROUP BY bare_peer)
tab on archive.id=tab.max_id
Try this following code:-
select m.*
from
messages m
inner join (
select max(id) as maxid
from messages
group By (if(username > bare_peer, username, bare_peer)),
(if(username > bare_peer, bare_peer, username))
) t1 on m.id=t1.maxid ;
m is alias of message table
You want the entry with max(created_at) for every username and bare_peer.
One way to do that in MySQL is with 'having' but I don't like that.
I would first get the max(created_at) for every entry:
select username, bare_peer, max(created_at) as m_
from archive
group by username, bare_peer;
Then join the table on that result:
select b.*
from (
select username, bare_peer, max(created_at) as m_
from archive
group by username, bare_peer
) a
inner join archive as b on (
a.username = b.username
and a.bare_peer = b.bare_peer
and a.m_ = b.created_at
)
I want to know why it shows the created_at column when you don't select created_at? And I don't know why you use group by? There is nothing need to be divided into groups.
My statement looks like this.select id, username, bare_peer, txt, created_at from archive where username = '1_usercode' order by created_at desc
I created a temporary solution with the Rahauto's answer.
I put his query that returns me the correct id from latest message inside a
subquery so that i can extract message content from it's id.
SELECT username, bare_peer, txt, created_at FROM archive WHERE id IN (
SELECT tab.max_id FROM
archive JOIN (SELECT MAX(id) max_id, username, bare_peer, txt FROM
archive WHERE username = '1_usernode' GROUP BY bare_peer)
tab ON archive.id=tab.max_id
);

MySQL JOIN with SUBQUERY very slow

I have a forum and I would like to see the latest topics with the author's name and the last user who answered
Table Topic (forum)
| idTopic | IdParent | User | Title | Text |
--------------------------------------------------------
| 1 | 0 | Max | Help! | i need somebody |
--------------------------------------------------------
| 2 | 1 | Leo | | What?! |
Query:
SELECT
Question.*,
Response.User AS LastResponseUser
FROM Topic AS Question
LEFT JOIN (
SELECT User, IdParent
FROM Topic
ORDER BY idTopic DESC
) AS Response
ON ( Response.IdParent = Question.idTopic )
WHERE Question.IdParent = 0
GROUP BY Question.idTopic
ORDER BY Question.idTopic DESC
Output:
| idTopic | IdParent | User | Title | Text | LastResponseUser |
---------------------------------------------------------------------------
| 1 | 0 | Max | Help! | i need somebody | Leo |
---------------------------------------------------------------------------
Example:
http://sqlfiddle.com/#!2/22f72/4
The query works, but is very slow (more or less 0.90 seconds over 25'000 record).
How can I make it faster?
UPDATE
comparison between the proposed solutions
http://sqlfiddle.com/#!2/94068/22
If using your current schema, I'd recommend adding indexes (particularly a clustered index (primary key)) and simplifying your SQL to let mySQL do the work of optimising the statement, rather than forcing it to run a subquery, sort the results, then run the main query.
CREATE TABLE Topic (
idTopic INT
,IdParent INT
,User VARCHAR(100)
,Title VARCHAR(255)
,Text VARCHAR(255)
,CONSTRAINT Topic_PK PRIMARY KEY (idTopic)
,CONSTRAINT Topic_idTopic_UK UNIQUE (idTopic)
,INDEX Topic_idParentIdTopic_IX (idParent, idTopic)
);
INSERT INTO Topic (idTopic, IdParent, User, Title, Text) VALUES
(1, 0, 'Max', 'Help!', 'i need somebody'),
(2, 1, 'Leo', '', 'What!?');
SELECT Question.*
, Response.User AS LastResponseUser
FROM Topic AS Question
LEFT JOIN Topic AS Response
ON Response.IdParent = Question.idTopic
WHERE Question.IdParent = 0
order by Question.idTopic
;
http://sqlfiddle.com/#!2/7f1bc/1
Update
In the comments you mentioned you only want the most recent response. For that, try this:
SELECT Question.*
, Response.User AS LastResponseUser
FROM Topic AS Question
LEFT JOIN (
select a.user, a.idParent
from Topic as a
left join Topic as b
on b.idParent = a.idParent
and b.idTopic > a.idTopic
where b.idTopic is null
) AS Response
ON Response.IdParent = Question.idTopic
WHERE Question.IdParent = 0
order by Question.idTopic
;
http://sqlfiddle.com/#!2/7f1bc/3
Assuming the highest IDTopic is the last responses user...
and assuming you want to return topics without responses...
Select A.IDTopic, A.IDParent, A.User, A.Title, A.Text,
case when b.User is null then 'No Response' else B.User end as LastReponseUser
FROM topic A
LEFT JOIN Topic B
on A.IdTopic = B.IDParent
and B.IDTopic = (Select max(IDTopic) from Topic
where IDParent=B.IDParent group by IDParent)
WHERE A.IDParent =0

Select based on some default value for group by having

I have an SQL table that contains the names of people and respective country codes.
----------------
name | code
----------------
saket | IN
rohan | US
samules | AR
Geeth | CH
Vikash | IN
Rahul | IN
Ganesh | US
Zorro | US
What I wanted was that, I should able to get rows group by country code having names starting with sa first, if not then Vi even if not then last row of the group.
When I tried this
SELECT * FROM MyTable GROUP BY code HAVING name like 'sa%' or name like 'vi%';
But its give me rows who matched with the above condition in having clause.
I want that if condition fails then give me the last row of that group, Is it possible?.
If possible, then how?
Maybe not very efficient, but try:
SELECT FIRST(`name`) AS `name`, `code` FROM (
SELECT `name`, `code` FROM `MyTable`
WHERE `name` LIKE 'sa%'
UNION ALL
SELECT `name`, `code` FROM `MyTable`
WHERE `name` LIKE 'vi%'
UNION ALL
SELECT LAST(`name`) AS `name`, `code` FROM `MyTable` GROUP BY `code`
HAVING `name` NOT LIKE 'sa%' AND `name` NOT LIKE `vi%'
) AS `a` GROUP BY `code`
You can try this query. It returns what you need, but be aware - this query has two pitfalls:
Subquery is a pain on 10^6 rows
Field name in outer query is nonaggregated. MySQL documentation says that is is impossible to say what value will be selected for nonaggregated.
http://dev.mysql.com/doc/refman/5.7/en/group-by-extensions.html
select name, country
from
(
select *, if(name like 'sa%', 0, if(name like 'vi%', 2, 3) ) as name_order
from tmp_names
order by country, name_order, name desc
) as tmp_names
group by country
order by name;
It returns
+---------+---------+
| name | country |
+---------+---------+
| Geeth | CH |
| saket | IN |
| samules | AR |
| Zorro | US |
+---------+---------+

Query returns additional rows

I have this kind of table for simple chat:
messages table structure
+----+---------+-----------+---------+------+------+
| id | to_user | from_user | message | read | sent |
+----+---------+-----------+---------+------+------+
And i need to get list of each conversation which looks like that
Username ---- Message ---- Date
I am using this query to do it:
SELECT *
FROM `messages`
WHERE `sent`
IN (
SELECT MAX( `sent` )
FROM `messages`
WHERE `from_user` = '1' --id of user who is requesting the list
OR `to_user` = '1' --id of user who is requesting the list
GROUP BY `to_user` , `from_user`
)
LIMIT 0 , 30
And this works almost fine, my problem is that it returns me not the last message of that conversation but last message from each user so let's say user 1 and 2 is talking and i'm getting this list, this is what i get:
+----+---------+-----------+-----------------------+------+---------------------+
| id | to_user | from_user | message | read | sent |
+----+---------+-----------+-----------------------+------+---------------------+
| 3 | 2 | 1 | Message 1 from user 1 | 0 | 2012-01-11 13:20:54 |
| 4 | 1 | 2 | Message 1 from user 2 | 0 | 2012-01-11 13:24:59 |
+----+---------+-----------+-----------------------+------+---------------------+
And i would like to get only last message which is 4, cause sent field is the highest in 4th record so how can i solve it?
EDIT After deleting group by i'm getting only one message even if user was talking with more than one user
Here's how you do it:
SELECT *
FROM (SELECT *
FROM messages
WHERE from_user = ?
OR to_user = ?
ORDER by from_user, to_user, sent DESC
) x
GROUP BY from_user, to_user
ORDER BY sent DESC
LIMIT 1;
In mysql, a group by without aggregating the other columns returns the first row for each group. By selecting form an ordered row set (the inner query) we get the most recent row for each conversation.
SELECT *
FROM `messages`
WHERE `sent`
IN (
SELECT MAX( `sent` )
FROM `messages`
WHERE (`from_user` = '1' OR `to_user` = '1')
)
LIMIT 0 , 30
the groupby is going to conbine them i believe.
You are getting the last message from each user because you have done GROUP BY for both: to_user and from_user.
There is no need to use GROUP BY clause in your query.
Remove the group by clause in your in statement--it's useless in this case. It's returning a sent timestamp for each distinct pairing of to_user and from_user. You really just want the max sent where to_user or from_user equal some value. Lose the group by, and you'll return exactly one record showing the latest message either to or from a user.
It looks like this:
SELECT *
FROM `messages`
WHERE `sent`
IN (
SELECT MAX( `sent` )
FROM `messages`
WHERE `from_user` = '1' --id of user who is requesting the list
OR `to_user` = '1' --id of user who is requesting the list
)
LIMIT 0 , 30