Select based on some default value for group by having - mysql

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 |
+---------+---------+

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 * ....).

MYSQL delete - Table 'USER_TABLE' is specified twice, both as a target for 'DELETE' and as a separate source for data

I am new to MySql large queries, and trying to find some solution for my problem,
I looking for delete duplicate values based on "ID_object" column in my USER_TABLE.
Here is my USER_TABLE description,
`USER_TABLE` (
`ID` varchar(256) NOT NULL,
`ID_OBJECT` varchar(256) DEFAULT NULL,
`INSERTION_TIME` date DEFAULT NULL,
KEY `USER_TABLE_inx01` (`ID`(255)),
KEY `user_inx02` (`ID_OBJECT`(255))
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
I tried the following query to remove the duplicate ID_OBJECTs,
delete from USER_TABLE where id in (
select ID from USER_TABLE,
(select ID_OBJECT, min(INSERTION_TIME) as fecha from USER_TABLE group by ID_OBJECT having count(ID_OBJECT)>1) tbpr
where USER_TABLE.ID_OBJECT = tbpr.ID_OBJECT and USER_TABLE.INSERTION_TIME=tbpr.fecha);
But it says,
SQL Error (1093): Table 'USER_TABLE' is specified twice, both as a target for 'DELETE' and as a separate source for data
Can anyone assist me in this?
This will do it. I haven't attempted to check whether your actual business logic for removing duplicates is correct, since your stated requirement isn't 100% clear anyway, but this is one way you can overcome the error message:
CREATE TEMPORARY TABLE IF NOT EXISTS duplicates AS (
SELECT UT.id
FROM `USER_TABLE` AS UT
INNER JOIN
(SELECT
ID_OBJECT,
MIN(INSERTION_TIME) AS fecha
FROM `USER_TABLE`
GROUP BY ID_OBJECT
HAVING COUNT(ID_OBJECT)>1) AS tbpr
ON
UT.ID_OBJECT = tbpr.ID_OBJECT AND UT.INSERTION_TIME = tbpr.fecha
);
DELETE FROM `USER_TABLE`
WHERE id IN (SELECT id FROM duplicates);
DROP TABLE IF EXISTS duplicates;
You can see a working demo here: https://www.db-fiddle.com/f/amnAPUftLD1SmW67fjVSEv/0
You could change your query slightly
delete from USER_TABLE
where concat(id_object,insertion_time) in
(
select concat(ID_object,fecha) from
(
select ID_OBJECT, min(INSERTION_TIME) as fecha
from USER_TABLE
group by ID_OBJECT
having count(ID_OBJECT)>1
) tbpr
)
But this would not cope with triplicates, quadruplets etc. so maybe you need to reverse the logic and keep only the max where there are multiples
delete from USER_TABLE
where concat(id_object,insertion_time) not in
(
select concat(ID_object,fecha) from
(
select ID_OBJECT, max(INSERTION_TIME) as fecha
from USER_TABLE
group by ID_OBJECT
having count(ID_OBJECT)>1
) tbpr
)
and
id_object not in
(
select ID_object from
(
select ID_OBJECT, count(*) as fecha
from USER_TABLE
group by ID_OBJECT
having count(ID_OBJECT) = 1
) tbpr2
)
;
create table `USER_TABLE` (
`ID` varchar(256) NOT NULL,
`ID_OBJECT` varchar(256) DEFAULT NULL,
`INSERTION_TIME` date DEFAULT NULL,
KEY `USER_TABLE_inx01` (`ID`(255)),
KEY `user_inx02` (`ID_OBJECT`(255))
) ;
truncate table user_table;
insert into user_table values
(1,1,'2017-01-01'),(2,1,'2017-01-02'),(3,1,'2017-01-03'),
(4,2,'2017-01-01');
Result of first query
MariaDB [sandbox]> select * from user_table;
+----+-----------+----------------+
| ID | ID_OBJECT | INSERTION_TIME |
+----+-----------+----------------+
| 2 | 1 | 2017-01-02 |
| 3 | 1 | 2017-01-03 |
| 4 | 2 | 2017-01-01 |
+----+-----------+----------------+
3 rows in set (0.00 sec)
Result of second query
MariaDB [sandbox]> select * from user_table;
+----+-----------+----------------+
| ID | ID_OBJECT | INSERTION_TIME |
+----+-----------+----------------+
| 3 | 1 | 2017-01-03 |
| 4 | 2 | 2017-01-01 |
+----+-----------+----------------+
2 rows in set (0.00 sec)

MySQL: Merge tables, run SELECT and update duplicates

I've the following three tables:
Table A:
id VARCHAR(32) | value VARCHAR(32) | groupId INT
abcdef | myValue1 | 1
ghijkl | myValue2 | 2
mnopqr | myValue3 | 1
Table B:
id VARCHAR(32) | value VARCHAR(32) | userId INT
abcdef | myValue4 | 1
uvwxyz | anotherValue | 1
Table C:
id VARCHAR(32) | someOtherColumns...
abcdef
ghijkl
mnopqr
...
uvwxyz
Table A and B are used for a m:n-association, thus the "id"-column in both tables references the same field ("id"-column in table c).
What I want to do is (for instance)... select all entries in table A where groupId = 1
SELECT * FROM TableA WHERE groupId = 1
and also select all entries in table B where userId = 1
SELECT * FROM TableB WHERE userId = 1
That's all no problem... but the following makes the select-statement(s) difficult: How can I merge both select-results and replace the value of the first result? For example:
selecting all entries in Table A where groupId = 1 I'll get abcdef and also mnopqr.
when I select all entries in Table B where userId = 1 I'll also get abdef (and additionally uvwxyz).
Now, the value of abcdef in Table B should replace the value in the selection result of table A. And the uvwxyz-entry should be added to the result.
Finally I'm looking for a query which produces the following table:
id VARCHAR(32) | value VARCHAR(32)
abcdef | myValue4 -- myValue1 from the select-statement in tableA should be overwritten
mnopqr | myValue2 -- from table A
uvwxyz | anotherValue -- from table B
I hope anyone know how to do this... thanks in advance for any suggestion! By the way... it would be great if there is any chance to realize this using one single (long) select statement.
Try this:
SELECT * FROM TableB WHERE userId = 1
UNION
SELECT * FROM TableA WHERE groupId = 1
and id not in (select id from TableB where userid = 1)
#rs points out to use the UNION, which is required since MySQL doesn't have FULL joins.
Favoring the data from table B is a chose for CASE:
select id, case when max(value_b) is not null then max(value_b) else max(value_a) end as final_value
from (
select id, value as 'value_a', null as 'value_b' from tableA
union
select id, null, value from tableB
) ugh
group by 1;

Mysql unique values query

I have a table with name-value pairs and additional attribute. The same name can have more than one value. If that happens I want to return the row which has a higher attribute value.
Table:
ID | name | value | attribute
1 | set1 | 1 | 0
2 | set2 | 2 | 0
3 | set3 | 3 | 0
4 | set1 | 4 | 1
Desired results of query:
name | value
set2 | 2
set3 | 3
set1 | 4
What is the best performing sql query to get the desired results?
the best performing query would be as follows:
select
s.set_id,
s.name as set_name,
a.attrib_id,
a.name as attrib_name,
sav.value
from
sets s
inner join set_attribute_values sav on
sav.set_id = s.set_id and sav.attrib_id = s.max_attrib_id
inner join attributes a on sav.attrib_id = a.attrib_id
order by
s.set_id;
+--------+----------+-----------+-------------+-------+
| set_id | set_name | attrib_id | attrib_name | value |
+--------+----------+-----------+-------------+-------+
| 1 | set1 | 3 | attrib3 | 20 |
| 2 | set2 | 0 | attrib0 | 10 |
| 3 | set3 | 0 | attrib0 | 10 |
| 4 | set4 | 4 | attrib4 | 10 |
| 5 | set5 | 2 | attrib2 | 10 |
+--------+----------+-----------+-------------+-------+
obviously for this to work you're gonna also have to normalise your design and implement a simple trigger:
drop table if exists attributes;
create table attributes
(
attrib_id smallint unsigned not null primary key,
name varchar(255) unique not null
)
engine=innodb;
drop table if exists sets;
create table sets
(
set_id smallint unsigned not null auto_increment primary key,
name varchar(255) unique not null,
max_attrib_id smallint unsigned not null default 0,
key (max_attrib_id)
)
engine=innodb;
drop table if exists set_attribute_values;
create table set_attribute_values
(
set_id smallint unsigned not null,
attrib_id smallint unsigned not null,
value int unsigned not null default 0,
primary key (set_id, attrib_id)
)
engine=innodb;
delimiter #
create trigger set_attribute_values_before_ins_trig
before insert on set_attribute_values
for each row
begin
update sets set max_attrib_id = new.attrib_id
where set_id = new.set_id and max_attrib_id < new.attrib_id;
end#
delimiter ;
insert into attributes values (0,'attrib0'),(1,'attrib1'),(2,'attrib2'),(3,'attrib3'),(4,'attrib4');
insert into sets (name) values ('set1'),('set2'),('set3'),('set4'),('set5');
insert into set_attribute_values values
(1,0,10),(1,3,20),(1,1,30),
(2,0,10),
(3,0,10),
(4,4,10),(4,2,20),
(5,2,10);
This solution will probably perform the best:
Select ...
From Table As T
Left Join Table As T2
On T2.name = T.name
And T2.attribute > T1.attribute
Where T2.ID Is Null
Another solution which may not perform as well (you would need to evaluate against your data):
Select ...
From Table As T
Where Not Exists (
Select 1
From Table As T2
Where T2.name = T.name
And T2.attribute > T.attribute
)
select name,max(value)
from table
group by name
SELECT name, value
FROM (SELECT name, value, attribute
FROM table_name
ORDER BY attribute DESC) AS t
GROUP BY name;
There is no easy way to do this.
A similar question was asked here.
Edit: Here's a suggestion:
SELECT `name`,`value` FROM `mytable` ORDER BY `name`,`attribute` DESC
This isn't quite what you asked for, but it'll at least give you the higher attribute values first, and you can ignore the rest.
Edit again: Another suggestion:
If you know that value is a positive integer, you can do this. It's yucky, but it'll work.
SELECT `name`,CAST (GROUP_CONCAT(`value` ORDER by `attribute` DESC) as UNSIGNED) FROM `mytable` GROUP BY `name`
To include negative integers you could change UNSIGNED to SIGNED.
Might want to benchmark all these options, here's another one.
SELECT t1.name, t1.value
FROM temp t1
WHERE t1.attribute IN (
SELECT MAX(t2.attribute)
FROM temp t2
WHERE t2.name = t1.name);
How about:
SELECT ID, name, value, attribute
FROM table A
WHERE A.attribute = (SELECT MAX(B.attribute) FROM table B WHERE B.NAME = A.NAME);
Edit: Seems like someones said the same already.
Did not benchmark them, but here is how it is doable:
TableName = temm
1) Row with maximum value of attribute :
select t.name, t.value
from (
select name, max(attribute) as maxattr
from temm group by name
) as x inner join temm as t on t.name = x.name and t.attribute = x.maxattr;
2) Top N rows with maximum attribute value :
select name, value
from temm
where (
select count(*) from temm as n
where n.name = temm.name and n.attribute > temm.attribute
) < 1 ; /* 1 can be changed to 2,3,4 ..N to get N rows */

MySQL UNION query from one table + ORDER BY

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