Preserve order of SQL WHERE IN() clause with nested SELECT - mysql

I need a list of item names ordered by count of items. Item names and corresponing id's are stored in tabletwo while tableone refers to items by id's:
tableone tabletwo
+--------+-----------+ +----+------+
| itemid | condition | | id | name |
+--------+-----------+ +----+------+
| 2 | satisfied | | 1 | foo |
+--------+-----------+ +----+------+
| 1 | satisfied | | 2 | bar |
+--------+-----------+ +----+------+
| 3 | satisfied | | 3 | hurr |
+--------+-----------+ +----+------+
| 3 | satisfied | | 4 | durr |
+--------+-----------+ +----+------+
| 3 | satisfied |
+--------+-----------+
| 4 | satisfied |
+--------+-----------+
| 4 | satisfied |
+--------+-----------+
| 3 | nope |
+--------+-----------+
| 1 | satisfied |
+--------+-----------+
SQL code:
SELECT `itemname` FROM `tabletwo` WHERE `id` IN (
SELECT `itemid` FROM (
SELECT count(`itemid`), `itemid`
FROM `tableone`
WHERE `some_codition`="satisfied"
GROUP BY `itemid`
ORDER BY count(`itemid`) DESC
) alias
)
The nested SELECT returns a list of item id's in descendant order: 3, 4, 1, 2. This list is then used as an argument of an IN() clause. The expected result of the whole query is: hurr, durr, foo, bar (in this exact order). But the order is not preserved. I know it can be done like this: ORDER BY FIELD(id, 3, 4, 1, 2) but I don't know how to do this trick when the ordered list is fetched dynamically like in my case. Do I need to SELECT it again? Or temporary table maybe? Or is it better to build another query outside SQL?

Try using JOIN instead:
SELECT t2.`itemname`
FROM `tabletwo` AS t2
JOIN (
SELECT count(`itemid`) AS cnt, `itemid`
FROM `tableone`
WHERE `some_codition`="satisfied"
GROUP BY `itemid`
) AS t1 ON t1.`itemid` = t2.`id`
ORDER BY t1.cnt DESC
You can create a derived table using the subquery of the IN operator and perform a JOIN to this table, so that you are able to use the COUNT in the ORDER BY clause of the main query.

Use JOIN instead of IN:
SELECT
t2.name
FROM tabletwo t2
LEFT JOIN tableone t1
ON t1.itemid = t2.id
AND t1.`condition` = 'satisfied'
GROUP BY
t2.id, t2.name
ORDER BY COUNT(*) DESC
If you want to exclude rows from tabletwo that do not have a match on tableone, use INNER JOIN instead of LEFT JOIN.
ONLINE DEMO

Related

Join related Issue

New to SQL
Suppose we have two tables
One has got the ID and Name column :
+----+-------+
| ID | Name |
+----+-------+
| 1 | Sam |
| 1 | Dan |
+----+-------+
and the second one has also got two columns as follow :
+----+------------+
| ID | Relatives |
+----+------------+
| 1 | Uncle |
| 2 | Aunty |
+----+------------+
If we do inner join we would only get the rows where the condition satisfies. But i want the output to be Like
+------+------------+
| ID | Relatives |
+------+------------+
| 1 | Uncle |
| NULL | Aunty |
+------+------------+
once only the value in the ID column should be shown. If the occurrence is twice or thrice it should come as null.
Just tell me if it is possible or not? and How for both the cases.
As your question is not clear, so assuming that you need to retrieve id from table a and name from table b and you also want to avoid duplicate rows, then an option could be to use distinct along with left join:
select distinct a.id, b.name
from b
left outer join a
on b.id = a.id
order by id desc
Result:
+------+-------+
| id | name |
+------+-------+
| 1 | Uncle |
| NULL | Aunty |
+------+-------+
DEMO
Try this:
SELECT
T1.Id,
T2.Relatives
FROM SecondTable T2
LEFT JOIN FirstTable T1
ON T1.ID = T2.ID
GROUP BY T1.Id,
T2.Relatives
This is what I get exactly:
CREATE TABLE #a (
id int,
name varchar(10)
)
CREATE TABLE #b (
id int,
name varchar(10)
)
INSERT INTO #a
VALUES (1, 'sam')
INSERT INTO #a
VALUES (1, 'Dan')
INSERT INTO #b
VALUES (1, 'Uncle')
INSERT INTO #b
VALUES (2, 'Aunty')
SELECT
T1.Id,
T2.name
FROM #b T2
LEFT JOIN #a T1
ON T1.ID = T2.ID
GROUP BY T1.Id,
T2.name
DROP TABLE #a
DROP TABLE #b
Output:
Id name
NULL Aunty
1 Uncle
Hope, this is what you ask in your question.

How to SELECT top different value using order by matrics

i have a table like this
i want to get the row of each table that have min responsetime
i have tried this query :
select tablename,
index1,
index2,
min(responsetime)
from tableconf
group by tablename
order by responsetime asc
but it doesn't give what i want
the output that i want is
+------------------+------------------+--------+--------------+
| tablename | index1 | index2 | responsetime |
+------------------+------------------+--------+--------------+
| salesorderheader | TotalDue | NULL | 6.1555 |
| salesterritory | Name | NULL | 11.66667 |
| store | BusinessEntityId | Name | 3.6222 |
| previous | previous | NULL | 5.03333 |
| NONE | NONE | NULL | 5.6 |
+------------------+------------------+--------+--------------+
what query i should use for get the output that i want
Select the minimum date per table name. Use an IN clause on these to get the rows:
select *
from tableconf
where (tablename, responsetime) in
(
select tablename, min(responsetime)
from tableconf
group by tablename
);
(Edited from previous answer)
I don't know if all SQL syntax accept a comma separated where parameter. Another option building off of the highest voted answer right now utilizes a join:
select *
from tableconf t
inner join (
select tablename, min(responsetime) min_rt
from tableconf t2
group by tablename
) t3 on t.tablename = t2.tablename and t.responsetime = t2.min_rt

Odd behavior of max and having in MySQL when max==0

I have the following table:
mysql> select * from foo;
| id | value | bar |
+----+-------+------+
| 1 | 2 | 3 |
| 2 | 0 | 3 |
| 1 | 1 | 5 |
I want to select the tuple with the maximum value for each id. However, when max(value) is 0, I don't get a result.
mysql> select id,max(value),bar from foo group by id having max(value);
| id | max(value) | bar |
+----+------------+------+
| 1 | 2 | 3 |
Is this supposed to behave like that and if so, why?
HAVING cannot be used in any way to pick a record out of a group of records as defined by the fields used in the GROUP BY clause. It is rather applied to the group as a whole.
So, in your case, you have to do a self-join to get the rest of the table fields:
select t1.id, t1.value, t1...
from foo as t1
join (
select id, max(value) as max_value
from foo
group by id
) as t2 on t1.id = t2.id and t1.value = t2.max_value
IMHO you can get MAX couple by multiplying (id x value).
create table foo(id int, value int);
insert into foo values
(2,0),
(1,0),
(2,1),
(3,0),
(2,2);
select id, value
from foo
order by (id * value) desc
limit 1;
id | value
2 | 2
drop table foo;

Mysql select with IN, limit rows to 1 for each match

lets say I have this table:
| id | record_id | date_updated |
|----|-----------|--------------|
| 1 | 1 | 19-03-2015 |
| 2 | 1 | 18-03-2015 |
| 3 | 1 | 17-03-2014 |
| 4 | 2 | 01-01-2015 |
| 5 | 2 | 05-02-2015 |
so the results I am looking for are :
| id | record_id | date_updated |
|----|-----------|--------------|
| 1 | 1 | 19-03-2015 |
| 4 | 2 | 01-01-2015 |
I have array with record ids.
$records = [1,2];
So I can do something like:
select * from `mytable`
WHERE `record_id` IN ($records)
AND mytable.date_update > 01-01-2014
AND mytable.date_updated < 12-12-2015
so mysql will select records wich match date_updated criteria ( and record id ofc ), which are more then 1 for each record ID, basically I want to make him limit the rows for each $record_id to 1
If it is even possible.
//it is super hard to explain the problem, the real case is that this is a sub query of another query, but the real example is 10 rows query and 100 columns table, so it will be even more hard to explain the situation and for someone to read it / udnerstands it. Hopefully someone will understand my problem, if not I will try to explain more.
Thanks
You can try using the group by clause
SELECT *
FROM `mytable`
WHERE id IN (
SELECT min(id)
FROM `mytable`
WHERE `record_id` IN ($records)
AND mytable.date_update > 01-01-2014
AND mytable.date_updated < 12-12-2015
group by record_id
);
There are many ways to get the record per group, and since you need only once you can easily do as below
select t1.* from table_name t1
where (
select count(*) from table_name t2
where t1.record_id = t2.record_id
) > =0
and
t1.date_updated > '2014-01-01' and date_updated < '2015-12-12'
group by t1.record_id ;
There are other way too using left join
select t1.* from table_name t1
left join table_name t2 on t1.record_id = t2.record_id
and t1.id >t2.id where t2.id is null
This will give you data with asc order with id
If you need data with max(id) for a record_id you can use
t1.id < t2.id
instead of
t1.id >t2.id
The same comparison you can do with first query.

Slow count query with where clause

I am trying to perform a count to get the total number of results in a pagination but the query is too slow 2.12s
+-------+
| size |
+-------+
| 50000 |
+-------+
1 row in set (2.12 sec)
my count query
select count(appeloffre0_.ID_APPEL_OFFRE) as size
from ao.appel_offre appeloffre0_
inner join ao.acheteur acheteur1_
on appeloffre0_.ID_ACHETEUR=acheteur1_.ID_ACHETEUR
where
(exists (select 1 from ao.lot lot2_ where lot2_.ID_APPEL_OFFRE=appeloffre0_.ID_APPEL_OFFRE and lot2_.ESTIMATION_COUT>=1))
and (exists (select 1 from ao.lieu_execution lieuexecut3_ where lieuexecut3_.appel_offre=appeloffre0_.ID_APPEL_OFFRE and lieuexecut3_.region=1))
and (exists (select 1 from ao.ao_activite aoactivite4_ where aoactivite4_.ID_APPEL_OFFRE=appeloffre0_.ID_APPEL_OFFRE and (aoactivite4_.ID_ACTIVITE=1)))
and appeloffre0_.DATE_OUVERTURE_PLIS>'2015-01-01'
and (appeloffre0_.CATEGORIE='fournitures' or appeloffre0_.CATEGORIE='travaux' or appeloffre0_.CATEGORIE='services')
and acheteur1_.ID_ENTITE_MERE=2
explain cmd :
+----+--------------------+--------------+------+---------------------------------------------+--------------------+---------+--------------------------------+-------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+--------------------+--------------+------+---------------------------------------------+--------------------+---------+--------------------------------+-------+--------------------------+
| 1 | PRIMARY | acheteur1_ | ref | PRIMARY,acheteur_ibfk_1 | acheteur_ibfk_1 | 5 | const | 3 | Using where; Using index |
| 1 | PRIMARY | appeloffre0_ | ref | appel_offre_ibfk_2 | appel_offre_ibfk_2 | 4 | ao.acheteur1_.ID_ACHETEUR | 31061 | Using where |
| 4 | DEPENDENT SUBQUERY | aoactivite4_ | ref | ao_activites_activite_fk,ao_activites_ao_fk | ao_activites_ao_fk | 4 | ao.appeloffre0_.ID_APPEL_OFFRE | 3 | Using where |
| 3 | DEPENDENT SUBQUERY | lieuexecut3_ | ref | fk_ao_lieuex,fk_region_lieuex | fk_ao_lieuex | 4 | ao.appeloffre0_.ID_APPEL_OFFRE | 1 | Using where |
| 2 | DEPENDENT SUBQUERY | lot2_ | ref | FK_LOT_AO | FK_LOT_AO | 4 | ao.appeloffre0_.ID_APPEL_OFFRE | 5 | Using where |
+----+--------------------+--------------+------+---------------------------------------------+--------------------+---------+--------------------------------+-------+--------------------------+
the index acheteur_ibfk_1 is a FK references table ENTITE_MERE because i have and acheteur1_.ID_ENTITE_MERE=2 in where clause.
You can have multiple conditions on your joins by using ON condition1 AND condition2 etc.
SELECT COUNT(appeloffre0_.ID_APPEL_OFFRE) as size
FROM ao.appel_offre appeloffre0_
JOIN ao.acheteur acheteur1_ ON appeloffre0_.ID_ACHETEUR=acheteur1_.ID_ACHETEUR
JOIN ao.lot lot2_ ON appeloffre0_.ID_APPEL_OFFRE=lot2_.ID_APPEL_OFFRE AND lot2_.ESTIMATION_COUT>=1
JOIN ao.lieu_execution lieuexecut3_ ON appeloffre0_.ID_APPEL_OFFRE=lieuexecut3_.ID_APPEL_OFFRE AND lieuexecut3_.ID_ACTIVITE=1
JOIN ao.ao_activite aoactivite4_ ON appeloffre0_.ID_APPEL_OFFRE=aoactivite4_.ID_APPEL_OFFRE AND aoactivite4_.ID_ACTIVITE=1
WHERE appeloffre0_.DATE_OUVERTURE_PLIS>'2015-01-01'
AND (appeloffre0_.CATEGORIE='fournitures' OR appeloffre0_.CATEGORIE='travaux' OR appeloffre0_.CATEGORIE='services')
AND acheteur1_.ID_ENTITE_MERE=2;
You can try:
select count(aa.ID_APPEL_OFFRE) as size
from (
select ID_APPEL_OFFRE, ID_ACHETEUR from ao.appel_offre appeloffre0_
inner join ao.acheteur acheteur1_
on appeloffre0_.ID_ACHETEUR=acheteur1_.ID_ACHETEUR
where appeloffre0_.DATE_OUVERTURE_PLIS>'2015-01-01'
and (appeloffre0_.CATEGORIE in ('fournitures','travaux','services'))
and (acheteur1_.ID_ENTITE_MERE=2)) aa
inner join ao.lot lot2_ on lot2_.ID_APPEL_OFFRE=aa.ID_APPEL_OFFRE
inner join ao.lieu_execution lieuexecut3_ on lieuexecut3_.appel_offre=aa.ID_APPEL_OFFRE
inner join ao.ao_activite aoactivite4_ on aoactivite4_.ID_APPEL_OFFRE=aa.ID_APPEL_OFFRE
where
aoactivite4_.ID_ACTIVITE=1
and lot2_.ESTIMATION_COUT>=1
and lieuexecut3_.region=1;
But I haven't seen your tables so I am not 100% sure that you won't get duplicates because of joins.
A couple of low-hanging fruits might also be found by ensuring that your appeloffre0_.CATEGORIE and appeloffre0_.DATE_OUVERTURE_PLIS have indexes on them.
Other fields which should have indexes on them are ao.lot.ID_APPEL_OFFRE, ao.lieu_execution.ID_APPEL_OFFRE and ao.ao_activite.ID_APPEL_OFFRE, and ao.appel_offre.ID_ACHETEUR (all the joined fields).
I would have the following indexes on your tables if not already... These are covering indexes for your query meaning the index has the applicable column to get your results without having to go to the actual raw data pages.
table index
appel_offre ( DATE_OUVERTURE_PLIS, CATEGORIE, ID_APPEL_OFFRE, ID_ACHETEUR )
lot ( ID_APPEL_OFFRE, ESTIMATION_COUT )
lieu_execution ( appel_offre, region )
ao_activite ( ID_APPEL_OFFRE, ID_ACTIVITE )
Having indexes on just individual columns won't really help optimize what you are looking for. Also, I am doing count of DISTINCT ID_APPEL_OFFRE's in case any of the JOINed tables have more than 1 record, it does not create a Cartesian result count for you
select
count(distinct AOF.ID_APPEL_OFFRE) as size
from
ao.appel_offre AOF
JOIN ao.acheteur ACH
on AOF.ID_ACHETEUR = ACH.ID_ACHETEUR
and ACH.ID_ENTITE_MERE = 2
JOIN ao.lot
ON AOF.ID_APPEL_OFFRE = lot.ID_APPEL_OFFRE
and lot.ESTIMATION_COUT >= 1
JOIN ao.lieu_execution EX
ON AOF.ID_APPEL_OFFRE = EX.appel_offre
and EX.region = 1
JOIN ao.ao_activite ACT
ON AOF.ID_APPEL_OFFRE = ACT.ID_APPEL_OFFRE
and ACT.ID_ACTIVITE = 1
where
AOF.DATE_OUVERTURE_PLIS > '2015-01-01'
and ( AOF.CATEGORIE = 'fournitures'
or AOF.CATEGORIE = 'travaux'
or AOF.CATEGORIE = 'services')
Like #FuzzyTree said in his comment exists is faster than an inner join if it's not a 1:1 relationship because it terminates as soon as it finds 1 whereas the join will get every matching row.
But the solution is that We add in and not exists :
where ( appeloffre0_.ID_APPEL_OFFRE IN (select lot2_.ID_APPEL_OFFRE from ao.lot lot2_
where lot2_.ESTIMATION_COUT>=1)
)
So the query run very fast than exists or joins .
select count(appeloffre0_.ID_APPEL_OFFRE) as size
from ao.appel_offre appeloffre0_
inner join ao.acheteur acheteur1_
on appeloffre0_.ID_ACHETEUR=acheteur1_.ID_ACHETEUR
where
( appeloffre0_.ID_APPEL_OFFRE IN (select lot2_.ID_APPEL_OFFRE from ao.lot lot2_ where lot2_.ESTIMATION_COUT>=1))
and (appeloffre0_.ID_APPEL_OFFRE IN (select lieuexecut3_.appel_offre from ao.lieu_execution lieuexecut3_ where lieuexecut3_.region=1))
and (appeloffre0_.ID_APPEL_OFFRE IN (select aoactivite4_.ID_APPEL_OFFRE from ao.ao_activite aoactivite4_ where aoactivite4_.ID_ACTIVITE=1 ))
and appeloffre0_.DATE_OUVERTURE_PLIS>'2015-01-01'
and (appeloffre0_.CATEGORIE='fournitures' or appeloffre0_.CATEGORIE='travaux' or appeloffre0_.CATEGORIE='services')
and acheteur1_.ID_ENTITE_MERE=2