mysql - use result of query in multiple sub queries - mysql

To describe my problem, heres a hypothetical scenario:
(
select user_id, stat
from stats1
where user_id in (select id from users where selected = 1)
)
union
(
select user_id, stat
from stats2
where user_id in (select id from users where selected = 1)
)
union
(
select user_id, stat
from stats3
where user_id in (select id from users where selected = 1)
)
# and so on ...
see how the query in 'where in' is used multiple times. I want to save the result of this query adn then use it. in all the other queries. Obviously this is easily possible if by running that query in PHP and then passing in the values, but is this possible purely in MySQL?

You could create a permanent view for your selected users
CREATE VIEW selectedUsers AS SELECT id FROM users WHERE selected = 1;
Then when you need it, your syntax will be easier and lighter
(
SELECT user_id, stat
FROM stats1
WHERE user_id IN (SELECT * FROM selectedUsers)
)
UNION
(
SELECT user_id, stat
from stats2
WHERE user_id IN (SELECT * FROM selectedUsers)
)
UNION
(
SELECT user_id, stat
FROM stats3
WHERE user_id IN (SELECT * FROM selectedUsers)
)
# and so on ...
If your data in the table users doesn't change during the request, the database engine should use the cache each time your view is needed
Here is the MySQL Views documentation

Related

Combine subqueries used to retrieve values for comparison into a single query?

I have an SQL query like this:
SELECT COUNT(*) AS count_value FROM submissions WHERE username = (
SELECT username FROM submissions WHERE id = '1'
) AND number = (
SELECT number FROM submissions WHERE id = '1'
) AND tstmp < (
SELECT tstmp FROM submissions WHERE id = '1'
);
Notice how I am using this query to find all rows with similar column values, but with a timestamp value that is less than row number 1.
This works for me, but I was wondering, is there a way I could combine the three subqueries into one? They all select information from the same table, so I thought it might be possible, but I have no clue how to do it.
I think you could merge the subqueries into one and use it as a derived table in a join. Please try this:
SELECT COUNT(*) AS count_value
FROM submissions s
JOIN (
SELECT username, number, tstmp
FROM submissions WHERE id = 1
) o ON s.number = o.number AND s.username = o.username AND s.tstmp < o.tstmp

How can i check for multiple values in the same variable SQL

SO basically, i want to return "cid" (customer id's) of users who has ordered a certain product "p07 & p01".
My currently query don't work, it only works when i check for one value instead of two. But i need it to check for two. The return value i get is basically an empty space. My guess is, it doesn't know which "pid" to use so it returns nothing.
SELECT cid FROM orders
WHERE pid = 'p07' AND pid = 'p01'
You can use aggregation for this purpose. No single row can have both values, so you need to look at groups of them:
SELECT cid
FROM orders
WHERE pid IN ('p07', 'p01')
GROUP BY cid
HAVING COUNT(DISTINCT pid) = 2;
If you are using Oracle database than you can use the INTERSECT keyword to get the answer. Following is the syntax for the same:
(SELECT cid FROM orders where pid = 'p07')
intersect
(SELECT cid FROM orders where pid = 'p01').
but if you are using MySQL, than this INTERSECT keyword will not work.
Following is the solution for the MySql database:
SELECT t1.cid from (
(SELECT DISTINCT cid FROM orders where pid = 'p07')
UNION ALL
(SELECT DISTINCT cid FROM orders where pid = 'p01')
) AS t1 GROUP BY cid HAVING count(*) >= 2;
If you want to use the above query in the Oracle database than there is a bit change in the syntax. Following is the syntax for the same:
SELECT t1.cid from (
(SELECT DISTINCT cid FROM orders where pid = 'p07')
UNION ALL
(SELECT DISTINCT cid FROM orders where pid = 'p01')
) t1 GROUP BY cid HAVING count(*) > = 2

Obtain a list with the items found the minimum amount of times in a table

I have a MySQL table where I have a certain id as a foreign key coming from another table. This id is not unique to this table so I can have many records holding the same id.
I need to find out which ids are seen the least amount of times in this table and pull up a list containing them.
For example, if I have 5 records with id=1, 3 records with id=2 and 3 records with id=3, I want to pull up only ids 2 & 3. However, the data in the table changes quite often so I don't know what that minimum value is going to be at any given moment. The task is quite trivial if I use two queries but I'm trying to do it with just one. Here's what I have:
SELECT id
FROM table
GROUP BY id
HAVING COUNT(*) = MIN(SELECT COUNT(*) FROM table GROUP BY id)
If I substitute COUNT(*) = 3, then the results come up but using the query above gives me an error that MIN is not used properly. Any tips?
I would try with:
SELECT id
FROM table
GROUP BY id
HAVING COUNT(*) = (SELECT COUNT(*) FROM table GROUP BY id ORDER BY COUNT(*) LIMIT 1);
This gets the minimum selecting the first row from the set of counts in ascendent order.
You need a double select in the having clause:
SELECT id
FROM table
GROUP BY id
HAVING COUNT(*) = (SELECT MIN(cnt) FROM (SELECT COUNT(*) as cnt FROM table GROUP BY id) t);
The MIN() aggregate function is suposed to take a column, not a query. So, I see two ways to solve this:
To properly write the subquery, or
To use temp variables
First alternative:
select id
from yourTable
group by id
having count(id) = (
select min(c) from (
select count(*) as c from yourTable group by id
) as a
)
Second alternative:
set #minCount = (
select min(c) from (
select count(*) as c from yourTable group by id
) as a
);
select id
from yourTable
group by id
having count(*) = #minCount;
You need to GROUP BY to produce a set of grouped values and additional select to get the MIN value from that group, only then you can match it against having
SELECT * FROM table GROUP BY id
HAVING COUNT(*) =
(SELECT MIN(X.CNT) AS M FROM(SELECT COUNT(*) CNT FROM table GROUP BY id) AS X)

Combine results of two unrelated queries into single view

Is it possible to combine the results of two separate (unrelated) sql queries into a single view. I am trying to total some figures for users and count the views for videos this month to display on a dashboard.
i.e.,
select count(*) from video where monthname(views) = 'May';
and
select sum(sessions) from user where user_id = 6;
I would like to create a view that combines that contains these two results.
Is this possible?
If you want the results next to each other in separate columns you can simply SELECT a list of queries:
SELECT ( select count(*) from video where monthname(views) = 'May') AS May_CT
,( select sum(sessions) from user where user_id = 6) AS User_Sum
If you want the results stacked in one column:
select count(*) from video where monthname(views) = 'May'
UNION ALL
select sum(sessions) from user where user_id = 6
The latter may require datatype conversion
SELECT t2.total_session,
t1.watch_count
FROM
(SELECT 1 AS common_key,
count(*) AS watch_count
FROM video
WHERE monthname(views) = 'May') AS t1
JOIN
(SELECT 1 AS common_key,
sum(sessions) AS total_session
FROM USER
WHERE user_id = 6) AS t2 ON t1.common_key = t2.common_key;
Ofcourse, this will be very efficient only when the output in both t1 and t2 is one row.

How do I write this kind of query (returning the latest avaiable data for each row)

I have a table defined like this:
CREATE TABLE mytable (id INT NOT NULL AUTO_INCREMENT, PRIMARY KEY(id),
user_id INT REFERENCES user(id) ON UPDATE CASCASE ON DELETE RESTRICT,
amount REAL NOT NULL CHECK (amount > 0),
record_date DATE NOT NULL
);
CREATE UNIQUE INDEX idxu_mybl_key ON mytable (user_id, amount, record_date);
I want to write a query that will have two columns:
user_id
amount
There should be only ONE entry in the returned result set for a given user. Furthermore, the amount figure returned should be the last recoreded amount for the user (i.e. MAX(record_date).
The complication arises because weights are recorded on different dates for different users, so there is no single LAST record_date for all users.
How may I write (preferably an ANSI SQL) query to return the columns mentioned previously, but ensuring that its only the amount for the last recorded amount for the user that is returned?
As an aside, it is probably a good idea to return the 'record_date' column as well in the query, so that it is eas(ier) to verify that the query is working as required.
I am using MySQL as my backend db, but ideally the query should be db agnostic (i.e. ANSI SQL) if possible.
First you need the last record_date for each user:
select user_id, max(record_date) as last_record_date
from mytable
group by user_id
Now, you can join previous query with mytable itself to get amount for this record_date:
select
t1.user_id, last_record_date, amount
from
mytable t1
inner join
( select user_id, max(record_date) as last_record_date
from mytable
group by user_id
) t2
on t1.user_id = t2.user_id
and t1.record_date = t2.last_record_date
A problem appears becuase a user can have several rows for same last_record_date (with different amounts). Then you should get one of them, sample (getting the max of the different amounts):
select
t1.user_id, t1.record_date as last_record_date, max(t1.amount)
from
mytable t1
inner join
( select user_id, max(record_date) as last_record_date
from mytable
group by user_id
) t2
on t1.user_id = t2.user_id
and t1.record_date = t2.last_record_date
group by t1.user_id, t1.record_date
I do not now about MySQL but in general SQL you need a sub-query for that. You must join the query that calculates the greatest record_date with the original one that calculates the corresponding amount. Roughly like this:
SELECT B.*
FROM
(select user_id, max(record_date) max_date from mytable group by user_id) A
join
mytable B
on A.user_id = B.user_id and A.max_date = B.record_date
SELECT datatable.* FROM
mytable AS datatable
INNER JOIN (
SELECT user_id,max(record_date) AS max_record_date FROM mytable GROUP BS user_id
) AS selectortable ON
selectortable.user_id=datatable.user_id
AND
selectortable.max_record_date=datatable.record_date
in some SQLs you might need
SELECT MAX(user_id), ...
in the selectortable view instead of simply SELECT user_id,...
The definition of maximum: there is no larger(or: "more recent") value than this one. This naturally leads to a NOT EXISTS query, which should be available in any DBMS.
SELECT user_id, amount
FROM mytable mt
WHERE mt.user_id = $user
AND NOT EXISTS ( SELECT *
FROM mytable nx
WHERE nx.user_id = mt.user_id
AND nx.record_date > mt.record_date
)
;
BTW: your table definition allows more than one record to exist for a given {id,date}, but with different amounts. This query will return them all.