Mysql Select within IF condition - mysql

looking to perform a query that on a particular conditions gets the data from another table.
it like
select field1, field2.... IF(fieldX=3,select value from sub_table where cat_id = 3 order by id desc limit 0,1, NULL) from abc ;
The query within the if is valid.
I am used to with implementing if conditions without any issue but those were all for some static values or a field. But, this is the first time I am trying to get a select's result in if and unable to do it.
The case is because for some particular value of 'fieldX' I need to get a record from another table.
Regards
Junaid

wrap you inner select in ( )
IF(fieldX=3, (select value from sub_table where cat_id = 3 order by id desc limit 0,1), NULL)

why not use a left join and use fieldX=3 as a join condition? if fieldX is different from 3, sql fills the field with NULL
select a.field1, a.field2, sub.value
from abc a
left join
(
select value from sub_table
where cat_id = 3
limit 0,1
) sub
on a.fieldX = 3
or, if you do want to get all rows for the corresponding values (i see you have cat_id = 3 and fieldX = 3, so basically cat_id = fieldX), just use a simple join. no need to use complicated if constructs. sql was built to do fast and efficient joins:
select a.field1, a.field2, sub.value
from abc a
left join sub_table sub
on a.fieldX = sub.cat_id
note however, that the second query will return multiple rows, when there are more matches between fieldX and cat_id (non-unique cat_id)

you could do something like:
select fields... from sub_table st
where st.idSubTable in(
Select IF(fieldX=3
,(
select st.idSubTable from sub_table where cat_id = 3 order by id desc limit 0,1
),
NULL)
from abc);
it will solve your problem.

Related

Combine two queries into one and reorder

I have two queries that work fine by themselves, however, because the second one is ran within the loop of the first one, I can not reorder results returned by the first query.
The first query always returns results, the second query - sometimes, but when it does I need those results to be on top. By default the results are sorted by distance, starting with the closest.
For example, here's what I get now:
Name1 (phone1) - 0.1 mi
Name2 (phone2) - 0.4 mi
Name3 (phone3) - 1.3 mi <- Now open (query 2 match)
Name4 (phone4) - 2.4 mi
What I would like to see:
Name3 (phone3) - 1.3 mi <- Now open (query 2 match)
Name1 (phone1) - 0.1 mi
Name2 (phone2) - 0.4 mi
Name4 (phone4) - 2.4 mi
Here are my current queries (simplified):
Query 1:
SELECT
t1.busName,
t1.busPhone
FROM t1
WHERE t1.lat BETWEEN $min_lat AND $max_lat
AND t1.lon BETWEEN $min_lon AND $max_lon
ORDER BY (POW((t1.lon-$lon),2) + POW((t1.lat-$lat),2))
LIMIT 5
Query 2:
SELECT COUNT(t3.rule_id) AS rcount
FROM t3
LEFT JOIN t2 ON (t3.rule_busID = t2.busID)
WHERE t3.APIid = '".$APIid."'
Another problem that I have is that there's no way to establish a direct connection between t1 and t3. The only way is to have t2
t1.APIid = t2.APIid
t2.busID = t3.rule_busID
Table structure as follows:
t1
--------------------------------------
busName | busPhone | lon | lat | APIid
t2
--------------------------------------
busID | APIid
t3
--------------------------------------
rule_id | rule_busID
Currently, with two queries, if I return 10 results I have to run 11 queries. Ideally I'd like to do it just once.
Sorry, this might be quite obvious, but I'm stuck.
Assumption: t3 does not contain the column APIid ("t3.APIid" in query 2 in question should read "t2.APIid").
Because you are ordering by columns not included in the select clause you need to perform the count calculation either as a derived table, or as a correlated subquery.
Derived Table
Here you perform the COUNT() & GROUP BY within a subquery and that result joined to the main query.
SELECT
t1.busName
, t1.busPhone
, COALESCE(r.rcount,0) rcount
FROM t1
LEFT JOIN (
SELECT
t2.APIid
, COUNT(t3.rule_id) AS rcount
FROM t3
INNER JOIN t2 ON t3.rule_busID = t2.busID
GROUP BY
t2.APIid
) r ON t1.APIid = r.APIid
WHERE t1.lat BETWEEN $min_lat AND $max_lat
AND t1.lon BETWEEN $min_lon AND $max_lon
ORDER BY (POW((t1.lon - $lon), 2) + POW((t1.lat - $lat), 2))
#LIMIT 5
;
Correlated Subquery
An alternative approach is to perform the count calculation inside the select clause of the main query. This style of subquery can cause performance issues, but if the number of rows being returned from the main query isn't large then this approach may perform adequately well.
SELECT
t1.busName
, t1.busPhone
, COALESCE(SELECT COUNT(t3.rule_id)
FROM t3 INNER JOIN t2 ON t3.rule_busID = t2.busID
WHERE t2.APIid = t1.APIid
),0) as rCount
FROM t1
WHERE t1.lat BETWEEN $min_lat AND $max_lat
AND t1.lon BETWEEN $min_lon AND $max_lon
ORDER BY (POW((t1.lon - $lon), 2) + POW((t1.lat - $lat), 2))
#LIMIT 5
;
Note: In either approach there is no value in using a LEFT JOIN from t3 on t2. If t3 has rules that don't link to t2, it also becomes impossible to link those rules to t1. So, just use an INNER JOIN between t3 and t2.
You may require the use of COALESCE() or IFNULL() to return zero if there is no matching count. You can use either function but I prefer the ANSI standard COALESCE()
Adjust the LIMIT to suit your need.
Not sure if that really works, but you could try the following approach:
Use your Query1 as subquery in the FROM-clause (to add the inner sorting and limit), select the rule-count in the SELECT-clause. ORDER the result by count DESC.
Haven't tested it, but that should look something like:
SELECT
sub1.busName,
sub1.busPhone,
(SELECT COUNT(t3.rule_id) AS rcount FROM t3 LEFT JOIN t2 ON (t3.rule_busID = t2.busID) WHERE t2.APIid = t1.APIid) as rCount
FROM
(
SELECT
t1.busName,
t1.busPhone
FROM t1
WHERE t1.lat BETWEEN $min_lat AND $max_lat AND t1.lon BETWEEN $min_lon AND $max_lon
ORDER BY (POW((t1.lon-$lon),2) + POW((t1.lat-$lat),2)) ASC
LIMIT 5
) as sub1
ORDER BY rCount DESC
But actually I wouldn't to that, I'd probably stick with your current approach of individual less complex queries and to the re-ordering afterwards in the application.
you could use this approach:
select name, phone from (
select 0 order, name, phone
from ...
union all
select 1, name, phone
from ...
)q
order by q.order
You can directly provide the join if you have Relational Database(i.e connection between your each tables including direct or indirect).
so your query simplified into one like below:
SELECT
t1.busName,
t1.busPhone,
COUNT(t3.rule_id) AS rcount
FROM t1
INNER JOIN t2 on t2.APIid=t1.APIid
LEFT JOIN t3 on t2.busID=t3.rule_busID
WHERE t1.lat BETWEEN $min_lat AND $max_lat
AND t1.lon BETWEEN $min_lon AND $max_lon
AND t3.APIid = '".$APIid."'
ORDER BY (POW((t1.lon-$lon),2) + POW((t1.lat-$lat),2))
LIMIT 5
use LIMIT keyword only when you restrict your data to be display.
select name, phone from (
select ROW_NUMBER() over (order by name, phone) as order, name, phone
from ...
union all
select 1, name, phone
from ...
)q
order by q.order
use ROW_NUMBER() over (order by name, phone) you can partition and order asc and descending for generating and identity simulation and then you can union join or do what you want. (you can do the second select in the field of the first query select a, (select b from c where d.a = c.a) from d
I don't understand the output what you expect.
Sorry for my English :)

Select something from mysql database and order it by count (where)

I have a problem with selecting something from my database. Here is the sql sentence:
SELECT name
FROM table1
JOIN table2
ON table1.id=table2.advid
GROUP BY advid
ORDER BY COUNT(table2.likes) ASC
This will output name with the least table2.likes to the highest value of table2.likes
The problem is that table2.likes contain both likes and dislikes. Likes are marked with 1, and dislikes are marked with 2 in the table.
Currently, if there is...
...written in the table, the syntax will count both likes and dislikes so the result would be 6. I would need this result to be zero, which means when counting, dislikes have to be deduced from the number of likes. Which also means this part of the sentence: ORDER BY COUNT(table2.likes) ASC would have to be changed, but I don't know how.
Use conditional aggregation with SUM():
SELECT name
FROM table1 t1 JOIN
table2 t2
ON t2.id = t2.advid
GROUP BY name
ORDER BY SUM(CASE WHEN t2.likes = 1 THEN 1 ELSE -1 END) ASC;
Note: I changed the GROUP BY to be by name. The GROUP BY columns should match the columns you are selecting.
Use a case expression to count 1 for likes and -1 for dislikes. It is considered good style and less error-prone not to join and then aggregate, but to join the already aggregated data instead.
select t1.name, t2.sumlikes
from table1 t1
join
(
select advid, sum(case when likes = 1 then 1 else -1 end) as sumlikes
from table2
group by advid
) t2 on t2.advid = t1.id
order by sumlikes;
If you want to list names without like entries, too, then turn the join into a left outer join and select coalesce(t2.sumlikes, 0) instead.

Particular MySql Query

Having the following tables
Post(*id, name, description, cat, publish_date)
Category(*id, name)
It is possible in ONE query to get (max) the first N element of each different category?
Assuming that N=3, i'd need the following result:
Result set:
["1", "Name1","Descr","cat1"]
["2", "Name1","Descr","cat1"]
["3", "Name1","Descr","cat1"]
["10","Name1","Descr","cat2"]
["20","Name1","Descr","cat2"]
["22","Name1","Descr","cat2"]
["25","Name1","Descr","cat3"]
["30","Name1","Descr","cat3"]
["19","Name1","Descr","cat3"]
And so on.
I need this, to get the first N article of EACH category, with one query (so without ask for a specific category but for all category in table)
It is possible? If yes what's the right query?
This query will do what you need. If any category has less than 3 post it will still work.
SELECT P.id,P.name,P.description,C.name
FROM Post P
LEFT JOIN Category C
ON P.type = C.id
WHERE FIND_IN_SET(P.id,
(
SELECT GROUP_CONCAT(ids) FROM
(SELECT SUBSTRING_INDEX(GROUP_CONCAT(id),',',3) as ids
FROM Post
GROUP BY type
) AS foo
GROUP BY ''
)
)
Here is a working SQL Fiddle
UPDATE
In response to your comment and updated question:
SELECT P.id,P.name,P.description,P.publish_date,C.name
FROM Post P
LEFT JOIN Category C
ON P.type = C.id
WHERE FIND_IN_SET(P.id,
(
SELECT GROUP_CONCAT(ids) FROM
(SELECT SUBSTRING_INDEX(GROUP_CONCAT(id ORDER BY publish_date DESC),',',3) as ids
FROM Post
GROUP BY type
) AS foo
GROUP BY ''
)
)
You can use UNION to join multiple queries into one. This assumes that you know what type you are selecting for each set.
SELECT * FROM
(
SELECT * FROM T1 WHERE type='Type1' ORDER BY id DESC LIMIT 3
) DUMMY1
UNION ALL
SELECT * FROM
(
SELECT * FROM T1 WHERE type='Type2' ORDER BY id DESC LIMIT 3
) DUMMY2
UNION ALL
SELECT * FROM
(
SELECT * FROM T1 WHERE type='Type3' ORDER BY id DESC LIMIT 3
) DUMMY3
The DUMMY table aliases are needed to allow ordering within each subquery.

Keep all records in "WHERE IN()" clause, even if they are not found

I have the following mysql query:
SELECT id, sum(views) as total_views
FROM table
WHERE id IN (1,2,3)
GROUP BY id
ORDER BY total_views ASC
If only id 1,3 are found in the database, i still want id 2 to appear, with total_views being set to 0.
Is there any way to do that? This cannot use any other table.
This query hard-codes the list of possible IDs using a sub-query consisting of unions... it then left joins this set of ids to the table containing the information to be counted.
This will preserve an ID in your results even if there are no occurrences:
SELECT ids.id, sum(views) as total_views
FROM (
SELECT 1 AS ID
UNION ALL SELECT 2 AS ID
UNION ALL SELECT 3 AS ID
) ids
LEFT JOIN table
ON table.ID = ids.ID
GROUP BY ids.id
ORDER BY total_views ASC
Alternately, if you had a numbers table, you could do the following query:
SELECT numbers.number, sum(views) as total_views
FROM
numbers
LEFT JOIN table
ON table.ID = ids.ID
WHERE numbers.number IN (1, 2, 3)
GROUP BY numbers.number
ORDER BY total_views ASC
Here's an alternative to Micheal's solution (not a bad solution, mind you -- even with "a lot" of ID's), so long as you're not querying against a cluster.
create temporary table __ids (
id int unsigned primary key
) engine=MEMORY;
insert into __ids (id) values
(1),
(2),
(3)
;
SELECT table.id, sum(views) as total_views
FROM __ids left join table using (id)
GROUP BY table.id
ORDER BY total_views ASC
And if your query becomes complex, I could even conceive of it running more efficiently this way. But, if I were you, I'd benchmark this option with Michael's ad-hoc UNION'ed table option using real data.
in #Michael's answer, if you do have a table with the ids you care about, you can use it as "ids" in place of Michael's in-line data.
Check this fiddle... http://www.sqlfiddle.com/#!2/a9392/3
Select B.ID, sum(A.views) sum from tableB B
left outer join tableA A
on B.ID = A.ID
group by A.ID
also check
http://www.sqlfiddle.com/#!2/a1bb7/1
try this
SELECT id
(CASE 1
IF EXISTS THEN views = mytable.views END
IF NOT EXIST THEN views = 0 END
CASE 2
IF EXISTS THEN views = mytable.views END
IF NOT EXIST THEN views = 0 END
CASE 3
IF EXISTS THEN views = mytable.views END
IF NOT EXIST THEN views = 0 END), sum(views) as total_views
FROM mytable
WHERE id IN (1,2,3)
GROUP BY id
ORDER BY total_views ASC
Does it have to be rows or could you pivot the data to give you one row and a column for every id?
SELECT
SUM(IF (id=1, views, 0)) views_1,
SUM(IF (id=2, views, 0)) views_2,
SUM(IF (id=3, views, 0)) views_3
FROM table

if "Subquery returns more than 1 row" consider it NULL

I'm trying to sync store ids on newtable with the ids from the maintable here:
UPDATE newtable t SET t.store_id = (SELECT store_id FROM maintable s
WHERE t.state = s.state AND s.city = t.city AND t.name = s.name)
Whenever a subquery returns more than one row it errors out with "Subquery returns more than 1 row", but when it returns zero rows the subquery is considered to have returned nothing so the store_id on newtable remains NULL. Nothing new here, it's just how it works.
I'd like to know if it's possible to let the subquery output the same as what it does when it has no matches when it has more than one matching row.
This way I'd get the store_id synced only for ONE matching row on the main table and skipped when more than one matching row comes out in the subquery.
I think you might be looking for a HAVING clause to force the query to match exactly once:
UPDATE newtable t
SET t.store_id = (
SELECT store_id
FROM maintable s
WHERE t.state = s.state
AND s.city = t.city
AND t.name = s.name
HAVING COUNT(*) = 1
)
That should make multiple matches behave the same as no matches. The HAVING clause is applied almost at the very end of the query process; if there are no matches from the WHERE or more than one match, then COUNT(*) = 1 will fail and the inner query will return nothing but if there is exactly one row then COUNT(*) = 1 will succeed and the inner query will return that single match.
You might consider putting a LIMIT 1 in your sub-query to better achieve what you are trying to accomplish, depending on your specific needs.
Otherwise, you should be able to get creative with IF or CASE:
UPDATE newtable t SET t.store_id = (
SELECT IF(num>1, NULL, storeid) FROM (
SELECT COUNT(*) AS num, storeid FROM maintable s WHERE t.state=s.state AND s.city=t.city AND t.name=s.name
)
)
Untested, but should get you in the ballpark.
UPDATE newtable t SET t.store_id = IFNULL((SELECT store_id FROM maintable s
WHERE t.state = s.state AND s.city = t.city AND t.name = s.name HAVING COUNT(*) = 1), t.store_id)
IFNULL(use_this_value_if_not_null,value_if_first_isnull)