MySQL select all subquery rows with minimum value - mysql

I'd like to select all rows of a subquery with the minimum value in a given field. Here's some toy examples of the techniques I've tried so far:
-- 1.
select
id, min(foo)
from
(select 1 AS id, 2 AS foo
union select 2 AS id, 2 AS foo
union select 3 AS id, 3 AS foo) a;
-- 2.
select
min(foo)
from
(
select 1 AS id, 2 AS foo, 0 AS const
union select 2 AS id, 2 AS foo, 0 AS const
union select 3 AS id, 3 AS foo, 0 AS const) a
group by const;
-- 3.
select
id
from
(select 1 AS id, 2 AS foo
union select 2 AS id, 2 AS foo
union select 3 AS id, 3 AS foo) a
where id = (select id from a where min(foo) = foo);
-- 4.
select
id
from
(select 1 AS id, 2 AS foo
union select 2 AS id, 2 AS foo
union select 3 AS id, 3 AS foo) a
where foo = (select min(foo));
-- 5.
select r.*
from
(
select min(foo) t
from
(select 1 AS id, 2 AS foo
union select 2 AS id, 2 AS foo
union select 3 AS id, 3 AS foo) a
) m
INNER JOIN a ON m.t = r.foo;
The actual query I'm working on is similar to the examples, in that it's made up of several smaller queries UNIONed together. The overall goal here is to lookup a row in a central table based on the fields of an association table k to which it is joined, where k is highest priority table. The result is a sort of tree view of rows from similar (but different tables).
I've mentioned this so in case someone can see that I'm going about this in a roundabout way they can shed some light on the bigger picture. But for now my angle is to select by a taking a minimum value on a field in the subquery.

Use order by and limit:
select t.*
from t
order by foo
limit 1;
Note: this only returns one row with the minimum, even if there are duplicates. The t is your subquery or table.
If you want all of them, then you need to include the table definition twice:
select t.*
from t
where t.foo = (select min(t2.foo) from t t2);

Related

How to create multiple rows from a initial row

I use mysql db engine, I wonder is it possible that the data in the table one row transferred to another table, this table would consist of two columns, id and value
each of the transferred value would go into one row and row would look like ID, value, and for as long as it has a value that is transferred to new row maintains the id as long as it has a value that belonged to the id of a row from which it transferred
Initial table looks like
id |country_name |city_1 |city_2 |city_3 |city_4
------------------------------------------------------------------------
1 |Some_country |some_city1 |some_city2 |some_city3 |some_city4
Wanted table looks like
id | city_name
1 | some_city1
1 | some_city2
1 | some_city3
1 | some_city4
Use this for one particular ID
select id, city_name from(
select id, city_1 as city_name from yourTable
union all
select id, city_2 from yourTable
union all
select id, city_3 from yourTable
union all
select id, city_4 from yourTable
) as t where id= yourID
http://sqlfiddle.com/#!9/7ee1f/1
Use this for whole table
select id, city_name from(
select id, city_1 as city_name from yourTable
union all
select id, city_2 from yourTable
union all
select id, city_3 from yourTable
union all
select id, city_4 from yourTable
) as t
order by id
What you are looking for is often referred to as vertical pivoting: you want to pivot something like an array of four city names - hard-wired into the table definition - into four vertical rows.
The solution is a cross join with a temporary table with as many consecutive integers, starting from 1, as you have columns to pivot, in conjunction with a CASE-WHEN expression that makes use of that series of integers.
See here:
WITH foo(id,country_name,city_1,city_2,city_3,city_4) AS (
SELECT 1,'Some_country','some_city1','some_city2','some_city3','some_city4'
)
, four_indexes(idx) AS (
SELECT 1
UNION ALL SELECT 2
UNION ALL SELECT 3
UNION ALL SELECT 4
)
SELECT
id AS country_id
, idx AS city_id
, CASE idx
WHEN 1 THEN city_1
WHEN 2 THEN city_2
WHEN 3 THEN city_3
WHEN 4 THEN city_4
ELSE ''
END AS city_name
FROM foo CROSS JOIN four_indexes
;
country_id|city_id|city_name
1| 1|some_city1
1| 3|some_city3
1| 2|some_city2
1| 4|some_city4
Only the other day, I answered a question that was looking for reversing the operation we are performing here: horizontal pivoting.
See here if you're curious ...
How to go about a column with different values in a same row in sql?
Happy Playing -
Marco the Sane

MySQL UNION SELECT and IN clause

I have two very simple table: t1 and t2 with the following rows:
table t1:
id, name
1 PBN
table t2:
id, name
100 FIBERHOME
Query 1:
SELECT name FROM t1 UNION SELECT name FROM t2 WHERE id IN (1)
Result is: PBN
Query 2:
SELECT name FROM t1 UNION SELECT name FROM t2 WHERE id IN (100)
Result is: PBN, FIBERHOME
But the expected result is: FIBERHOME..! What is the reason?
To expand on #Knep's answer, if you only want one WHERE id IN ():
SELECT name FROM (
SELECT id, name FROM t1
UNION
SELECT id, name FROM t2
) unioned
WHERE id IN (1,100)
Probably not great speed wise, so best to test.
Note also the id needs to be in the sub query to be used in the outer WHERE.
I thought that the WHERE clause is global – #szpal
To answer the question as to why the WHERE isn't used for all queries in the UNION, think about two queries that don't share a column.
On their own:
SELECT id, name FROM x WHERE colA = 123
And:
SELECT id, name FROM y WHERE colB = 456
Then together with (the incorrect) single WHERE clause:
SELECT id, name FROM x
UNION
SELECT id, name FROM y
WHERE colB = 456 -- But table x doesn't have a colB!
Whereas if (correctly) the WHERE clause sits with each query:
SELECT id, name FROM x
WHERE colA = 123 -- I have a colA, still don't have a colB
UNION
SELECT id, name FROM y
WHERE colB = 456 -- I have a colB, still don't have a colA
Everyone's a winner!
UNION sum up the two results.
In the first query, there is no condition so it returns PBN, then it adds the result of the second result FIBERHOME.
Using UNION you could try:
SELECT name FROM t1 WHERE id IN (100) UNION SELECT name FROM t2 WHERE id IN (100)
The where condition in second query will be executed before union.
SELECT name FROM t1
will return
id name
1 PBN
SELECT name FROM t2 WHERE id IN (100)
will return
id name
null null
The union will combine above two results as
SELECT name FROM t1 UNION SELECT name FROM t2 WHERE id IN (100)
id name
1 PBN
You can solve this by
SELECT
name
FROM
(SELECT
*
FROM
interns_test_db.t1 UNION SELECT
*
FROM
interns_test_db.t2) A
WHERE
ID IN (100)
But this may reduce the performance.

GROUP_CONCAT on two tables

I have two tables, with independent ids (can't be connected via joins), I want to query and get a GROUP_CONCAT of both columns.
Example: table "a" has ids: 1, 2, 3. table "b" has the ids: 10, 11.
End result should be: 1, 2, 3, 10, 11
I have tried a few queries:
SELECT CONCAT_WS(',', GROUP_CONCAT(a.id), GROUP_CONCAT(b.id)) AS combined FROM a, b
SELECT GROUP_CONCAT(a.id, b.id) AS combined FROM a, b
These queries are returning me duplicate results though 8as in, all results from a twice and all results from b twice as well)
Try union all:
select group_concat(ab.id) as ids
from ((select id from a
) union all
(select id from b
)
) ab;
Your queries are doing cross join's between the tables, so data after the cross join is:
a.id b.id
1 10
1 11
2 10
2 11
3 10
3 11
After the union all, the data is:
ab.id
1
2
3
10
11
GROUP_CONCAT(DISTINCT [])
will help
https://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html#function_group-concat
The following query will generate that you want.
You can play with the table_position dynamic column for deciding which table goes first.
Select group_concat(id order by table_position) from
(
select id, 1 as table_position from a
union all
select id, 2 as table_position from b
)
If you want duplicates, use union all. If you don't want duplicates, use union.
In either case, the query you need is as follows:
select group_concat(id) from
(select id from a
union
select id from b) as ids;

Count same values in mysql query

So i have a query like
SELECT * FROM `catalog` WHERE `id` IN ('2','2','3','3','3');
And this return only 2 rows with id 2 and 3. It is possible make it return 5 rows (2 with id "2" and 3 with id "3") or add count as new column?
Not sure why you would want to do something like this, but instead of using an 'in' clause you could use an inner query:
select *
from `catalog` c,
(
select 2 ids
union all
select 2
union all
select 3
union all
select 3
union all
select 3
) k
where c.id = k.ids
Try something like this:
SELECT t.p,count(*) FROM
catalog,
(SELECT 2 as id
Union all select 2 as id
Union all select 3 as id
Union all select 3 as id
Union all select 3 as id)as t
where catalog.id = t.id
It can be done using temporary tables:
create temporary table arrayt (id int);
insert into arrayt values ('2'),('2'),('3'),('3'),('3');
select catalog.* from arrayt a LEFT JOIN catalog on (a.id=catalog.id);
if you need count
select count(catalog.id) as count,catalog.id as id from arrayt a LEFT JOIN catalog on (a.id=catalog.id) group by catalog.id;

Changing a Query with a numbered result set (with gaps,) to return result with no gaps, containing every number.

I have a select statement: select a, b, [...]; which returns the results:
a|b
---------
1|8688798
2|355744
4|457437
7|27834
I want it to return:
a|b
---------
1|8688798
2|355744
3|0
4|457437
5|0
6|0
7|27834
An example query that does not do what I would like, since it does not have the gap numbers:
select
sub.num_of_ratings,
count(sub.rater)
from
(
select
r.rater_id as rater,
count(r.id) as num_of_ratings
from ratings r
group by rater
) as sub
group by num_of_ratings;
Explanation of the query:
If a user rates another user, the rating is listed in the table ratings and the id of the rating user is kept in the field rater_id. Effectively I check for all users who are referred to in ratings and count how many ratings records I find for that user, which is rater / num_of_ratings, and then I use this result to find how many users have rated a given number of times.
At the end I know how many users rated once, how many users rated twice, etc. My problem is that the numbers for count(sub.rater) start fine from 1,2,3,4,5... However, for bigger numbers there are gaps. This is because there might be one user who rated 1028 times - but no user who rated 1027 times.
I don't want to apply stored procedures looping over the result or something like that. Is it possible to fill those gaps in the result without using stored procedures, looping, or creating temporary tables?
If you have a sequence of numbers, then you can do a JOIN with that table and fill in the gaps properly.
You can check out this questions on how to get the sequence:
generate an integer sequence in MySQL
Here is one of the answers posted that might be easily used with the limitation that generates numbers from 1 to 10,000:
SELECT #row := #row + 1 as row FROM
(select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t,
(select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t2,
(select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t3,
(select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) t4,
(SELECT #row:=0) t5
Using a sequence of numbers, you can join your result set. For instance, assuming your number list is in a table called numbersList, with column number:
Select number, Count
from
numbersList left outer join
(select
sub.num_of_ratings,
count(sub.rater) as Count
from
(
select
r.rater_id as rater,
count(r.id) as num_of_ratings
from ratings r
group by rater
) as sub
group by num_of_ratings) as num
on num.num_of_ratings=numbersList.number
where numbersList.number<max(num.num_of_ratings)
Your numbers list must be larger than your largest value, obviously, and the restriction will allow it to not have all numbers up to the maximum. (If MySQL does not allow that type of where clause, you can either leave the where clause out to list all numbers up to the maximum, or modify the query in various ways to achieve the same result.)
#mazzucci: the query is too magical and you are not actually explaining the query.
#David: I cannot create a table for that purpose (as stated in the question)
Basically what I need is a select that returns a gap-less list of numbers. Then I can left join on that result set and treat NULL as 0.
What I need is an arbitrary table that keeps more records than the length of the final list. I use the table user for that in the following example:
select #row := #row + 1 as index
from (select #row := -1) r, users u
limit 101;
This query returns a set of the numbers von 0 to 100. Using it as a subquery in a left join finally fills the gap.
users is just a dummy to keep the relational engine going and hence producing the numbers incrementally.
select t1.index as a, ifnull(t2.b, 0) as b
from (
select #row := #row + 1 as index
from (select #row := 0) r, users u
limit 7
) as t1
left join (
select a, b [...]
) as t2
on t1.index = t2.a;
I didn't try this very query live, so have merci with me if there is a little flaw. but technically it works. you get my point.
EDIT:
just used this concept to gain a gapless list of dates to left join measures onto it:
select #date := date_add(#date, interval 1 day) as date
from (select #date := '2010-10-14') d, users u
limit 700
starts from 2010/10/15 and iterates 699 more days.