How to use result of an subquery multiple times into an query - mysql

A MySQL query needs the results of a subquery in different places, like this:
SELECT COUNT(*),(SELECT hash FROM sets WHERE ID=1)
FROM sets
WHERE hash=(SELECT hash FROM sets WHERE ID=1)
and XD=2;
Is there a way to avoid the double execution of the subquery (SELECT hash FROM sets WHERE ID=1)?
The result of the subquery always returns an valid hash value.
It is important that the result of the main query also includes the HASH.
First I tried a JOIN like this:
SELECT COUNT(*), m.hash FROM sets s INNER JOIN sets AS m
WHERE s.hash=m.hash AND id=1 AND xd=2;
If XD=2 doesn't match a row, the result is:
+----------+------+
| count(*) | HASH |
+----------+------+
| 0 | NULL |
+----------+------+
Instead of something like (what I need):
+----------+------+
| count(*) | HASH |
+----------+------+
| 0 | 8115e|
+----------+------+
Any ideas? Please let me know! Thank you in advance for any help.
//Edit:
finally that query only has to count all the entries in an table which has the same hash value like the entry with ID=1 and where XD=2. If no rows matches that (this case happend if XD is set to an other number), so return 0 and simply hash value.

SELECT SUM(xd = 2), hash
FROM sets
WHERE id = 1
If id is a PRIMARY KEY (which I assume it is since your are using a single-record query against it), then you can just drop the SUM:
SELECT xd = 2 AS cnt, hash
FROM sets
WHERE id = 1
Update:
Sorry, got your task wrong.
Try this:
SELECT si.hash, COUNT(so.hash)
FROM sets si
LEFT JOIN
sets so
ON so.hash = si.hash
AND so.xd = 2
WHERE si.id = 1

I normally nest the statements like the following
SELECT Count(ResultA.Hash2) AS Hash2Count,
ResultA.Hash1
FROM (SELECT S.Hash AS Hash2,
(SELECT s2.hash
FROM sets AS s2
WHERE s2.ID = 1) AS Hash1
FROM sets AS S
WHERE S.XD = 2) AS ResultA
WHERE ResultA.Hash2 = ResultA.Hash1
GROUP BY ResultA.Hash1
(this one is hand typed and not tested but you should get the point)
Hash1 is your subquery, once its nested, you can reference it by its alias in the outer query. It makes the query a little larger but I don't see that as a biggy.

If I understand correctly what you are trying to get, query should look like this:
select count(case xd when 2 then 1 else null end case), hash from sets where id = 1 group by hash

I agree with the other answers, that the GROUP BY may be better, but to answer the question as posed, here's how to eliminate the repetition:
SELECT COUNT(*), h.hash
FROM sets, (SELECT hash FROM sets WHERE ID=1) h
WHERE sets.hash=h.hash
and sets.ID=1 and sets.XD=2;

Related

Get item from breadcrumb/tree path (Adjacency model)

I understand you can get breadcrumbs/ tree path using a with a recursive CTE, but is it possible to select an item knowing the breadcrumb/tree?
id| name | parent_id
--------------------
0 | a | null
1 | b | 0
2 | c | 1
3 | b | 2
For example, if the breadcrumb looked like this: a/b/c/b, how would I be able to return the row with id 3 knowing this information?
Postgres just rocks.
http://sqlfiddle.com/#!17/0a6f4/27
The idea is to build the textbook recursive query which returns the path of each element in the tree, along with a "level" which represents the number of nodes from the root. You can also call it "depth".
Then, we turn the path 'a/b/c/b' into an ARRAY['a','b','c','b']... therefore indexing this array on [level] gives the name of the node we're looking for at each level.
WITH RECURSIVE h(id,name,parent_id,level,path,search_path) AS (
SELECT id,
name,
parent_id,
1,
ARRAY[name],
ARRAY['a','b','c','b']
FROM t WHERE parent_id IS NULL AND name = 'a'
UNION ALL
SELECT t.id,
t.name,
t.parent_id,
level+1,
path || t.name,
h.search_path
FROM t JOIN h ON(t.parent_id=h.id)
WHERE search_path[level+1] = t.name
)
SELECT *, path=search_path as match FROM h;
This returns the nodes from the requested path, in path order. I added a "match" column which becomes true when the requested row was found. If you only want this row, put the condition in the where, unless you want it to stop at the closest match and return it in case the path is not found, in which case you'll need to take the last row.
Funnily enough it should be possible to attempt this in MySQL by using session variables to transfer the parent_id from one row to the next, although MySQL has no arrays, so something like find_in_set() could work instead... would be kind of a hack...

Mysql subquery not working properly with comma-separated value in column

I have a MySQL table Mapdetail that has resultTypeIds column storing a comma separated list of ids:
MapDetails
=============================
mapheaderid | resultTypeIds
=============================
54 | 1,4,-9999
Now, when I try it as a subquery, it does not return me the correct result while if I hard-code (1,4,-9999) it works fine
WRONG - RETURNING 1 ROW ONLY
select * from resulttypes rt where rt.resulttypeid in
(select md1.resulttypeids from mapdetail md1
where md1.mapheaderid = 54)
CORRECT - RETURNING 2 ROWS
select * from resulttypes rt where rt.resulttypeid in (1,4,-9999)
If you can't re-structure your DB (as mentioned in Comments). Try using FIND_IN_SET instead of IN
Try something like:
select * from resulttypes rt where FIND_IN_SET (rt.resulttypeid,
(select md1.resulttypeids from mapdetail md1
where md1.mapheaderid = 54))

Why is this only returning 1 row?

I am trying to query 2 tables with a join. I expect to get 2 rows but only get 1:
SELECT tmp.pk, tmp.domain, count(crawl.pk)
FROM (
SELECT * FROM domains
WHERE domain IN('www.google.com', 'www.yahoo.com')
AND pk < 10000
) tmp
JOIN crawl ON crawl.domain=tmp.pk
AND crawl.date_crawled <= 3
HAVING COUNT(crawl.pk) < 1000
Result:
+-------+--------------------+-----------------+
| pk | domain | count(crawl.pk) |
+-------+--------------------+-----------------+
| 14929 | www.yahoo.com | 88 |
+-------+--------------------+-----------------+
1 row in set (0.03 sec)
If I remove 'www.yahoo.com' from the IN statement then I get 'www.google.com' in the result (therefore, I know that both www.google.com and www.yahoo.com pass my criteria).
Why is it returning only 1 row, when it should be returning 2?
Don't know why you're using a sub-query. Try this one...
SELECT d.pk, d.domain, count(c.pk)
FROM domains d
INNER JOIN crawl c ON d.pk = c.domain
WHERE d.pk < 10000
AND d.domain in ('www.google.com', 'www.yahoo.com')
AND c.date_crawled <= 3
GROUP BY d.pk, d.domain
HAVING COUNT(c.pk) < 1000
If you're still having issues, I'd try removing the HAVING clause as well as the d.pk < 10000
This doesn't make sense because tmp.pk is suppose to be less than 10000 yet your example recordset shows tmp.pk being greater than 10000.
And, your join looks incorrect. Seems you are trying to join a string-based field with a numerical one. I am referring specifically to: crawl.domain=tmp.pk
I would suggest you try JOIN by tmp.domain.
Alternatively, I would recommend removing the subquery and restructuring your query to represent more what #Phil suggested in his answer.

MySQL variable in WHERE clause

I need a query that find the recommended TV shows for an user, based on the TV Shows he is following.
Do to this I have the following tables:
the table Progress that contains wich show the user is following and the percentage of seen episodes (to solve this problem we can assume I have only one user in the database)
the table Suggested that contains _id1,_id2 and value (value is the strength of the connections between the show with id=_id1 and the show with id=_id2: the more value is great, the more the shows have something in common).
Note that in this table applies the commutative property, so the strength of the connection between id1 and _id2 is the same of _id1 and _id2. Moreover there aren't two rows such as ROW1._id1=ROW2._id2 AND ROW1._id2 = ROW2._id1
the table ShowCache that contains the details about a TV Show, such as name etc..
The following query is what I'm trying to do, but the result is an empty set:
SET #a = 0; //In other tests this line seem to be necessary
SELECT `ShowCache`.*,
(SUM(value) * (Progress.progress)) as priority
FROM `Suggested`,`ShowCache`, Progress
WHERE
((_id2 = Progress.id AND _id1 NOT IN (SELECT id FROM Progress) AND #a:=_id1)//There is a best way to set a variable here?
OR
(_id1 = Progress.id AND _id2 NOT IN (SELECT id FROM Progress) AND #a:=_id2))
AND `ShowCache`._id = #a //I think that the query fails here
GROUP BY `ShowCache`._id
ORDER BY priority DESC
LIMIT 0,20
I know the problem is related to the usage of variables, but I can't solve it. Any help is really appreciated.
PS: the main problem is that (because of the commutative propriety), without variables I need two queries, wich takes about 3 secs to begin executed (the query is more complex than the above). I'm really trying to make a single query to do this task
PPS: I tied also with an XOR operation, that results in an infinite loop?!?!? Here's the WHERE clause I tried:
((_id2=Progress.id AND #a:=_id1) XOR (_id1=Progress.id AND #a:=_id2)) AND `ShowCache`._id = #a
EDIT:
I come up with this WHERE conditions without using any variable:
(_id2 = Progress.id OR _id1 = Progress.id)
AND `ShowCache`._id = IF(_id2 = Progress.id, _id1,_id2)
AND `ShowCache`._id NOT IN (SELECT id FROM Progress)
It works, but it is very slow.
Your attempt to use xor is clever. If you want to get the nonmatching value you want to use bitwise XOR which is ^
Progress.id ^_id1 ^ _id2
3 ^ 2 ^ 3 = 2
2 ^ 2 ^ 3 = 3
You can use this trick to setup a join and really simplify your query (eliminate the OR's and NOT IN's and do it in one query without variables.)
select users.name as username, showcache.name as show_name,
sum(progress * value) as priority from users
inner join progress on users.id = progress.user_id
inner join suggested on progress.show_id in (suggested.id_1, suggested.id_2)
inner join showcache on showcache.id =
(suggested.id_1 ^ suggested.id_2 ^ progress.show_id)
where showcache.id not in
(select show_id from progress where user_id = users.id)
group by showcache.id
order by priority desc;
I also setup a fiddle to demonstrate it:
http://sqlfiddle.com/#!2/2dcd8/24
To break it down. I created a users table with a single user (but the solution will work with multiple users.)
The select and join to progress is straightforward. The join to suggested uses IN as an alternative to writing it with OR
The join to showcache is where the bitwise XOR happens. One of the id's links up to the progress.show_id and we want to use the other one.
It does include a not in to exclude shows already watched from the results. I could have changed it to not exists? but it seems clearer this way.
You're setting #a's value twice within the where clause, meaning that the query is actually boiling down to:
...
WHERE ... AND `ShowCache`._id = _id2
MySQL evalutes variable assignments in a first-encountered order, so you should leave #a constant until the END of the clause, then assign a new value, e.g
mysql> set #a=5;
mysql> select #a, #a+1, #a*5, #a := #a + 1, #a;
+------+------+------+--------------+------+
| #a | #a+1 | #a*5 | #a := #a + 1 | #a |
+------+------+------+--------------+------+
| 0 | 1 | 0 | 1 | 1 |
| 1 | 2 | 5 | 2 | 2 |
| 2 | 3 | 10 | 3 | 3 |
+------+------+------+--------------+------+
Note that #a's value in the first 3 columns remains constant, UNTIL mysql reaches the #a := #a +1, after which #a has a new value
So perhaps your query should be
set #a = 0;
select #temp := #a, ..., #a := _id2
where
((_id2 = Progress.id AND _id1 NOT IN (SELECT id FROM Progress) AND #temp =_id1)
...
etc...

Mysql multiple tables select

I've got a table, called for example, "node", from which I need to return values for as shown:
SELECT nid FROM node WHERE type = "book"
After I get a list of values let's say:
|**nid**|
|123|
|12451|
|562|
|536|
Then I need to take these values, and check another table, for rows where column 'path' has values as "node/123", "node/12451" (numbers the previous request returned) in one joined request. It all would be easier if collumn 'path' had simple numbers, without the 'node/'.
And then also count the number of identical i.e. 'node/123' returned.
End result would look like:
nid | path | count(path) | count(distinct path)
123 |node/123| 412 | 123
562 |node/562| 123 | 56
Works fine if done in multiple separated queries, but that won't do.
select a.nid from node a join othertable b
on b.path = concat("node/", a.nid) where type='book'
You can probably do something like the following (nid may require additional conversion to some string type):
SELECT *
FROM OtherTable
JOIN node ON path = CONCAT('node/', nid)
WHERE type = 'book'
Thank you all for your help. Basically, the problem was that I didn't know how to get nid and node/ together, but concat helped.
End result looks something like:
SELECT node.nid, accesslog.path, count(accesslog.hostname), count(distinct accesslog.hostname)
FROM `node`, `accesslog`
WHERE node.uid=1
AND node.type='raamat'
AND accesslog.path = CONCAT('node/', node.nid)
GROUP BY node.nid