use just one where for all UNION ALL selects - mysql

I have this select and I want to optimize it.
I was wondering if I could use for all this UNION ALL just one where instead of repeat this all the time making mysql scan table 4 times instead of just 1
select id from sells
where user_id in (select fv from favorite where user =?)
union all
select id from likes
where user_id in (select fv from favorite where user =?)
union all
select id from favorites
where user_id in (select fv from favorite where user =?)
union all
select id from comments
where user_id in (select fv from favorite where user =?)
is it possible? how can I change it?

select id,user_id from(
select id,user_id from sells
union all
select id,user_id from likes
union all
select id,user_id from favorites
union all
select id,user_id from comments
) as t
where user_id in (select fv from favorite where user =?)

You could do:
select user_id
from (select user_id from sells union all
select user_id from likes union all
select user_id from favorites union all
select user_id from comments
) x
where user_id in (select fv from favirote where user = ?);
However, I would discourage this because of performance. There are two hits. First, the subquery is materialized, which slows down processing. More importantly, the subqueries do not take advantage of indexes, further slowing down the query.
Your version is probably the most reasonable, assuming that you have proper indexes (on all the user_id columns and fv).
Also, if you don't want duplicates, use union instead of union all. I usually advocate union all, but this seems to be a case where duplicate removal is warranted.
Probably the most performant approach is this:
select f.fv
from favorites f
where f.user = ? and
(exists (select 1 from sells s where s.user_id = f.fv) or
exists (select 1 from likes l where l.user_id = f.fv) or
exists (select 1 from favorites f where s.user_id = f.fv) or
exists (select 1 from comments c where s.user_id = c.fv)
);
This can make use of indexes and doesn't require additional overhead.

Related

SQL that return specific ids in group of 5

I have articles table with id and created_by
And users table with id and name
I need to write SQL that retrieve 5 or less articles for users 1,2,3,4,5,6,...
If I use limit 5, it will limit the result to 5 records, but I need to limit the result to 5 for each specific user.
I can use something like this:
(SELECT id, created_by FROM content where created_by = 1 limit 5)
union
(SELECT id, created_by FROM content where created_by = 2 limit 5)
But I have about 20 users, so I do not think it is efficient.
You can use variables to simulate ROW_NUMBER window function, not available in MySQL:
SELECT ArticleId, UserId, UserName
FROM (
SELECT a.id AS ArticleId, u.id AS UserId, u.name AS UserName,
#row_number:= IF (#uid = u.id,
IF (#uid:=u.id, #row_number+1, #row_number+1),
IF (#uid:=u.id, 1, 1)) AS rn
FROM articles AS a
CROSS JOIN (SELECT #row_number:=0, #uid:=0) vars
INNER JOIN users AS u ON a.created_by = u.id
WHERE u.id IN (1, 2, 3, 4, 5, 6)
ORDER BY u.id, a.id ) t
WHERE t.rn <= 5
The above query will pick the top 5 articles per user (as per articleId ordering). If a user has less than 5 articles, then all articles of the user are selected.
Note the usage of nested conditionals in order to guarantee that #uid is first read and subsequently set.
Demo here
Something like this should work... (abstract implementation)
SELECT id_user FROM TABLE
WHERE id_user IN
(SELECT id_user
FROM TABLE
GROUP BY id_user
HAVING count(articles)<=5)

Subquery - records are not in ordered form

I have three tables:
user: id, name
keyword: id, name
userkeyword: id, user_id, keyword_id
I want to execute query in following way:
Display those users whose keyword/s are matched with the login user's
keywords. In the order of maximum number of keyword matched user
should display first
e.g : If userA having 4 matched keywords, userB having 8, userC having 1, userD having 6 then the result should be in the order of,
userB
userD
userA
userC
For that I have done with this query (assume login user's id is 1):
select *
from user
where id IN (
select user_id
from userkeywords
where keyword_id IN (
select keyword_id
from userkeywords
where user_id=1)
group by user_id
order by count(keyword_id) desc)
AND id != 1
Here the result is getting perfect but the order is not correct. I have merged two queries in following manner"
select *
from user
where id IN (?)
AND id!=1
+
select user_id
from userkeywords
where keyword_id IN (
select keyword_id
from userkeywords
where user_id=1)
group by user_id
order by count(keyword_id) desc
Second query returns user_id in correct order but when I merged both queries, order was changed (wrong).
Hope I have mentioned my query properly with enough detail.
A subquery returns an unordered set, so the order by in a subquery only matters for its limit clause, if there is any. Any database other than MySQL would give an error message for a purely decorative sort order.
There's no way to sort on a column that only exists in the where clause. You'd have to rewrite the query. One option is to replace your in conditions with joins:
select uk2.name
from userkeywords uk1
join userkeywords uk2
on uk1.keyword_id = uk2.keyword_id
and uk1.user_id <> uk2.user_id
join user u2
on u2.id = uk2.user_id
where uk1.user_id = 1
group by
uk2.name
order by
count(*) desc
This should do it.
select uk.user_id, u.name
from userkeywords uk
left join user u on u.id = uk.user_id
where uk.keyword_id IN (
select keyword_id
from userkeywords
where user_id=1)
group by uk.user_id
order by count(uk.keyword_id) desc) AND uk.user_id != 1
Also, JOIN provides better performance.
I would use an inner join to select the correct rows:
SELECT *
FROM user
INNER JOIN (
SELECT * FROM userkeyword
WHERE keyword_id IN (
SELECT keyword_id
FROM userkeyword
WHERE user_id=1
)
) uk
ON user.id = uk.user_id
GROUP BY u.id
ORDER BY count(*) DESC;

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

SQL: please improve my select-where-subquery-returns-one query

I already have a working solution to the following problem, but I worry that it is silly or inefficient.
There is a Thing table with columns (id, attributes...) and also a ThingVersion table with columns (id, thing_id, version_attributes...) The problem is to select from Thing where exactly one corresponding ThingVersion row exists.
At the moment I have something like
SELECT Thing.id AS id, Foo.whatever
FROM Thing JOIN Foo ON Thing.id=Foo.thing_id
WHERE Thing.id IN (
SELECT thing_id FROM ThingVersion TV
WHERE 1 = (
SELECT COUNT(*)
FROM ThingVersion TV2
WHERE TV2.thing_id = TV.thing_id)
)
ORDER BY Foo.whatever
It seems to give the correct results, and as an emphatic non-guru it seems self-explanatory, but -- three selects?!?! -- I can't help but feel there must be a better way.
Is there?
You can use a HAVING clause in your subquery:
SELECT Thing.id AS id, Foo.whatever
FROM Thing JOIN Foo ON Thing.id=Foo.thing_id
WHERE Thing.id IN
( SELECT thing_id
FROM ThingVersion TV
GROUP BY thing_id
HAVING COUNT(*) = 1
)
ORDER BY Foo.whatever
;
You can also eliminate the JOIN in the main query:
SELECT thing_id AS id, whatever
FROM Foo
WHERE thing_id IN
( SELECT thing_id
FROM ThingVersion TV
GROUP BY thing_id
HAVING COUNT(*) = 1
)
ORDER BY whatever
;
(I'm assuming that every value that appears in both Foo.thing_id and ThingVersion.thing_id must certainly appear Thing.id.)
SELECT Thing.id AS id, Foo.whatever
FROM Thing JOIN Foo ON Thing.id=Foo.thing_id
where Thing.idin (select thing_id from ThingVersion group by thing_id having COUNT(*)>1)
Solution using strictly JOINs:
SELECT t.*
FROM Thing t
JOIN ThingVersion tv1
ON tv1.thing_id = t.id
LEFT JOIN ThingVersion tv2
ON tv2.thing_id = t.id
AND tv2.id <> tv1.id
WHERE tv2.id IS NULL
Solution using GROUP BY and COUNT:
SELECT t.*
FROM Thing t
JOIN ThingVersion tv
ON tv.thing_id = t.id
GROUP BY t.id
HAVING COUNT(t.id) = 1;
Try them both out for performance.

Multiple select statements in Single query

I am generating a report in php (mysql),
ex:
`select count(id) as tot_user from user_table
select count(id) as tot_cat from cat_table
select count(id) as tot_course from course_table`
Like this I have 12 tables.
Can i make it in single query. If i did? Process gets slow?
SELECT (
SELECT COUNT(*)
FROM user_table
) AS tot_user,
(
SELECT COUNT(*)
FROM cat_table
) AS tot_cat,
(
SELECT COUNT(*)
FROM course_table
) AS tot_course
If you use MyISAM tables, the fastest way is querying directly the stats:
select table_name, table_rows
from information_schema.tables
where
table_schema='databasename' and
table_name in ('user_table','cat_table','course_table')
If you have InnoDB you have to query with count() as the reported value in information_schema.tables is wrong.
You can certainly us the a Select Agregation statement as Postulated by Ben James, However This will result in a view with as many columns as you have tables. An alternate method may be as follows:
SELECT COUNT(user_table.id) AS TableCount,'user_table' AS TableSource FROM user_table
UNION SELECT COUNT(cat_table.id) AS TableCount,'cat_table' AS TableSource FROM cat_table
UNION SELECT COUNT(course_table.id) AS TableCount, 'course_table' AS TableSource From course_table;
The Nice thing about an approch like this is that you can explicitly write the Union statements and generate a view or create a temp table to hold values that are added consecutively from a Proc cals using variables in place of your table names. I tend to go more with the latter, but it really depends on personal preference and application. If you are sure the tables will never change, you want the data in a single row format, and you will not be adding tables. stick with Ben James' solution. Otherwise I'd advise flexibility, you can always hack a cross tab struc.
select RTRIM(A.FIELD) from SCHEMA.TABLE A where RTRIM(A.FIELD) = ('10544175A')
UNION
select RTRIM(A.FIELD) from SCHEMA.TABLE A where RTRIM(A.FIELD) = ('10328189B')
UNION
select RTRIM(A.FIELD) from SCHEMA.TABLE A where RTRIM(A.FIELD) = ('103498732H')
SELECT t1.credit,
t2.debit
FROM (SELECT Sum(c.total_amount) AS credit
FROM credit c
WHERE c.status = "a") AS t1,
(SELECT Sum(d.total_amount) AS debit
FROM debit d
WHERE d.status = "a") AS t2
I know this is an old stack but i will post this Multi-SQL select case
SELECT bp.bizid, bp.usrid, bp.website,
ROUND((SELECT SUM(rating) FROM ratings WHERE bizid=bp.bizid)/(SELECT COUNT(*) FROM ratings WHERE bizid=bp.bizid), 1) AS 'ratings',
(SELECT COUNT(*) FROM bzreviews WHERE bizid=bp.bizid) AS 'ttlreviews',
bp.phoneno, als.bizname,
(SELECT COUNT(*) FROM endorsment WHERE bizid=bp.bizid) AS 'endorses'
, als.imgname, bp.`location`, bp.`ownership`,
(SELECT COUNT(*) FROM follows WHERE bizid=bp.bizid) AS 'followers',
bp.categories, bp.openhours, bp.bizdecri FROM bizprofile AS bp
INNER JOIN alluser AS als ON bp.usrid=als.userid
WHERE als.usertype='Business'