SQL query from 3 different tables - mysql

There are 3 tables:
Users table
------------
|uid|username|
------------
Values table
------------------
|vid|values|checked|
------------------
Relations
-----------
|cid|uid|vid|
-----------
Relations table contains user ids related to value ids. How to select value id from values table that is not related to given user id in relations table?
EDIT:
What I tried so far:
SELECT vid FROM relations where uid=user_id //this gives me array of value ids
SELECT vid FROM values where vid!=vid1 AND vid!=vid2 .....
EDIT2:
Basic solution can be found here. But is there more efficient way? If table is very large for both values table and relations table basic solution is not efficient.

Which dbms are you using? Does it support the minus clause? If yes you can do something like this
select vid from values
minus
select vid from relations where uid = #user_id
this should give the vid's which are not mapped to a given user id
Another way to do this is through a not-exists clause (handy if your dbms doesn't support the minus clause)
select v.vid from values v where not exists (select 1 from relations r where
r.vid = v.vid and r.user_id = #user_id)
I would caution against using the not in clause though. Its performance is questionable and fails if the inner query returns a null value, which though is not possible in your case, but you should make it a habit to never use the 'not in' clause with a sub-query. Only use it when you have a list of literal values e.g. '... vid not in (1, 2, 3, 4)'. Whenever you have to 'Minus' something from one table based on values in another table use the 'not exists' and never 'not in'

I think you can execute a simple query like this (assuming that the data type of user identifier is int):
DECLARE #givenUserID int --local variable where you store the given user identifier
SELECT vid
FROM Values
WHERE vid NOT IN (SELECT vid FROM Relations where uid = #givenUserID)

Is it ok for you ?
select vid from values where vid not in (select vid from relations where uid = user_id)

I think something simple like this query will suffice.
If there is no uid for a particular entry in the value table, then there shouldn't be an entry in the relations table either.
SELECT vid
FROM values
LEFT JOIN relations on values.vid = relations.vid
WHERE relations.uid IS NULL

select distinct v.vid
from values v
left join relations r on (r.vid=v.vid)
where r.uid != user_id
It's unfortunate that MySQL doesn't support with; this is not going to perform very well, unfortunately.

If a Value is exactly 0 or 1 time in your Relations table, you can use a JOIN for that:
SELECT `Values`.`vid` FROM `Values`
LEFT JOIN `Relations` ON (`Values`.`vid` = `Relations`.`vid`)
WHERE `Relations`.`uid` != 1;
This will not work if a Value is more than 1 time in the Relations table because the WHERE would match another row with a different uid in this case. It is the same with a NOT IN, this could also match a different row with the same vid but another uid.
If every Value is at least once in the Relations table, the most efficient way is to query only the Relations table:
SELECT DISTINCT `Relations`.`vid` FROM `Relations`
WHERE `Relations`.`uid` != 1;
If a Value can be 0, 1, or more times in the Relations table, the best way is to use an EXISTS (see also taimur's answer):
SELECT `Values`.`vid` FROM `Values`
WHERE NOT EXISTS (
SELECT * FROM `Relations`
WHERE `Relations`.`vid` = `Values`.`vid` AND `Relations`.`uid` = 1
);
However, EXISTS is a bit slower than the IN or JOIN, so you should compare how the execution times are in your case.

Related

MySQL How to select records with indrect IDs?

My table has a columns labeled primary_key and summary_id. The value in the second field summary_id in each record maps to the primary_key field of another record. There is a third field template_id. I need to select those records for which:
template_id is a certain value. Let's say 4.
primary_key matches at least one of the records' summary_id field.
Please don't tell me to redesign the tables. My next project will have a better design, but I don't have time for that now. I need to do this with one or more queries; the fewer the better. Ideally, there's some way to do this with one query, but I'm okay if it requires more.
This is how far I've gotten with my own query. (I know it's seriously lacking, which is why I need help.)
SELECT DISTINCT esjp_content.template_id
FROM esjp_content
INNER JOIN esjp_hw_config ON esjp_content.template_id = esjp_hw_config.proc_id
INNER JOIN esjp_assets ON esjp_hw_config.primary_key = esjp_assets.hw_config_id
WHERE
esjp_content.summary_id > 0
AND
(esjp_assets.asset_label='C001498500' OR esjp_assets.asset_label='H0065' OR esjp_assets.asset_label='L0009');
SELECT
esjp_content.primary_key, esjp_content.template_id, esjp_content.content, esjp_content.summary_id
FROM
esjp_content
WHERE
esjp_content.template_id = 4;
I need the records that summary_id points to. For example, if summary_id is 90, then I need the record where primary_key is 90.
You're looking for the existence of at least one row where summary_id = your primary key. like this.
SELECT *
FROM esjp_content c
WHERE template_id = 4
AND EXISTS (SELECT 1 FROM esjp_content c2 WHERE c2.summary_id = c.primary_key)
You can JOIN same table by using both IDs:
SELECT
t1.*
FROM
esjp_content t1
INNER JOIN esjp_content t2 ON t1.summary_id = t2.primary_key
WHERE
t1.template_id = 4

A bit of trouble with mySQL querying

say I have a table called "users", which houses all the users of a system. Each user has a "role" as defined by the "roles" column in addition to a "name" and "id" column. Each user with the role "worker" is advised by a "supervisor" as defined by the "advised_by" column.
I'd like to query my database so that it returns the both the name of the worker and their supervisor and one line, is that possible without joins?
Thanks for any advise, I've been banging my head against this for a while now
Edit: Thanks to everybody who tried to help. Sorry, I guess that was a pretty vague/awful description in retrospect. I'll try to model the table better here.
Columns: Name, ID, Role, Advised_by
Row 1: Bob, 123, Worker, 321
Row 2: Tom, 321, Supervisor, N/A
I would like to return a result with all the people tagged as "Workers", along with their name and the name of their supervisor, so something like: Bob, Worker, Tom on one line.
I was trying to avoid adding a join because my professor asked us not to use them due to the performance hit, but it doesn't seem like there's a practical way around it, in which case I'll be alright with a join.
Edit 2: Guess I'm just an idiot, I realized after I typed it all out I could just do it pretty easily with aliasing. Sorry for the trouble guys
Some more details would be necessary, but you need to join the table to get both the name of the supervisor and worker in one MySQL sentence.
Assuming the advised_by column contains the ID of the user who advises this worker:
SELECT u.id, u.name, u2.id AS supervisorID, u2.name AS supervisorNAME
FROM users u, users u2
WHERE u.advised_by = u2.id AND u.role = 'worker'
At the risk of repeating the answer provided by EpochGalaxy...
Q: Is that possible without joins?
A: Yes. It is possible. One approach is to use a correlated subquery.
Consider the query you need to run to get the supervisor name...
SELECT s.name
FROM users s
WHERE s.id = 321
What if you ran a query like that for each row you retrieved from users.
Say you ran query like this:
SELECT u.id
, u.name
, u.role
, u.advised_by
FROM users u
ORDER BY u.id
And for each row from that query, right before you returned it, you ran a query to get the name of the supervisor.
We can achieve that by using a subquery in the SELECT list.
As a trivial example of running a subquery in the SELECT list consider this query. (There's no logical reason we'd run a query like this, but as a simple demonstration that we can run a subquery...
SELECT u.id
, u.name
, u.role
, u.advised_by
, ( SELECT 'bar' ) AS foo
FROM users u
ORDER BY u.id
The rows returned by this query will include an additional column, named foo. The value assigned to the column will be provided from the result of the subquery... "SELECT 'bar'".
Now that we know we can run a subquery in the SELECT list, and return a column with that, we can try something else. Like this:
SELECT u.id
, u.name
, u.role
, u.advised_by
, ( SELECT s.name
FROM users s
WHERE s.id = 321
) AS s_name
FROM users u
ORDER BY u.id
With that, for each row returned by the outer query, MySQL will run the subquery in the SELECT list, and take the value returned by the subquery, and put the value returned into a column of the row being returned by the outer query.
That subquery is going to get only the name from the row in users with id = 321.
So that doesn't really get us what we need.
Here's the trick... instead of a literal value 321, we can put there an expression that includes a reference to the table in the outer query.
SELECT u.id
, u.name
, u.role
, u.advised_by
, ( SELECT s.name
FROM users s
WHERE s.id = u.advised_by
) AS s_name
FROM users u
ORDER BY u.id
Aha!
Now, for each row returned by the outer query, MySQL will run the subquery in the SELECT this.
This time, the subquery includes a reference to the advised_by column from the row of the outer query. MySQL will take the value that's in the advised_by column from the row being returned (by the outer query), and then slide that into the subquery before the subquery executes. The return from the subquery goes into the column of the resulset.
With this, the results we get from the sbuquery are determined by values in the outer query. The subquery is related to the outer query.
Some important restrictions to notes: The subquery in a SELECT list of another query must return exactly one column. (It can be an expression, a literal. But the return from the subquery is not allowed to return two or more columns.
The subquery must return no more than one row. If it returns zero rows, a NULL value is assigned to the column in the outer query.
The subquery in the example above satisfies the conditions. There's exactly one expression in the SELECT list, and the id column is unique in the users table, so it can't return more than one row.
The SQL terminology for using a subquery like this is correlated subquery.
So, to answer your question, yes... it is possible to achieve the specified result without a join operation.
Some other notes:
In terms of performance, it's important that the subquery be efficient. It is going to be executed for every row returned by the outer query. If we're returning 10,000 rows, that's 10,000 executions of the subquery.
So, this approach is best suited to cases where the outer query is returning a small number of rows, and the correlated subquery is very efficient.
Use the following query:
SELECT (u.name)workername, (select name from users where id=u.advised_by)supervisor FROM `users` u where u.roles="worker"
Sample table structure as described by you:
--
-- Database: `test`
--
-- --------------------------------------------------------
--
-- Table structure for table `users`
--
CREATE TABLE `users` (
`id` int(11) NOT NULL,
`name` varchar(255) NOT NULL,
`roles` varchar(255) NOT NULL,
`advised_by` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
--
-- Dumping data for table `users`
--
INSERT INTO `users` (`id`, `name`, `roles`, `advised_by`) VALUES
(1, 'test1', 'supervisor', 0),
(2, 'test2', 'worker', 1),
(3, 'test3', 'worker', 1);

Normalise data into one table

I'm trying to insert rows into a table (usersteps) from the table steps for all users only if the step id does not exist.
INSERT INTO userssteps
(status,user_id,step_id)
SELECT
'0' ,
(SELECT DISTINCT id from users),
(SELECT DISTINCT id from steps)
I get the following error on the above MYSQL
#1242 - Subquery returns more than 1 row
Reason:
A new user signs up they should get all steps, if I create a new step i'd want to create it in usersteps for current users to see.
If there is a more clever way to do this i'd love to know but i'm stumped. I am also using cakePHP so if there is a special cakePHP way to help me in this i'd prefer that.
Table Structure
steps:
id
name
users:
id
username
password
userssteps:
id
user_id
step_id
status
It looks like you are trying to produce a cartesian product. http://en.wikipedia.org/wiki/Cartesian_product.
If there is no relations between the users and steps table then they cannot be joined, only multiplied.
INSERT INTO userssteps
(status,user_id,step_id)
select 0,
users.id,
steps.id
from users
inner join steps
The subquerys (SELECT DISTINCT id from users) and (SELECT DISTINCT id from steps) will return ALL the id's. In a insert clause you will need only one value (you can't have more than 1 value).
you can try to inner join the two tables by the ID
Try this way:
INSERT INTO userssteps
(status,user_id,step_id)
select 0 as status,
users.id,steps.id
from users
inner join steps
on (users.id=steps.user_id);
That way should works ;)
PS: Now the join is right.
Saludos.

Need to find all client id's the most efficient/fastest way using mySql

I have a bridging table that looks like this
clients_user_groups
id = int
client_id = int
group_id = int
I need to find all client_id's of of clients that belong to the same group as client_id 46l
I can achieve it doing a query as below which produces the correct results
SELECT client_id FROM clients_user_groups WHERE group_id = (SELECT group_id FROM clients_user_groups WHERE client_id = 46);
Basically what I need to find out is if there's a way achieving the same results without using 2 queries or a faster way, or is the method above the best solution
You're using a WHERE-clause subquery which, in MySQL, ends up being reevaluated for every single row in your table. Use a JOIN instead:
SELECT a.client_id
FROM clients_user_groups a
JOIN clients_user_groups b ON b.client_id = 46
AND a.group_id = b.group_id
Since you plan on facilitating clients having more than one group in the future, you might want to add DISTINCT to the SELECT so that multiple of the same client_ids aren't returned when you do switch (as a result of the client being in more than one of client_id 46's groups).
If you haven't done so already, create the following composite index on:
(client_id, group_id)
With client_id at the first position in the index since it most likely offers the best initial selectivity. Also, if you've got a substantial amount of rows in your table, ensure that the index is being utilized with EXPLAIN.
you can try with a self join also
SELECT a.client_id
FROM clients_user_groups a
LEFT JOIN clients_user_groups b on b.client_id=46
Where b.group_id=a.group_id
set #groupID = (SELECT group_id FROM clients_user_groups WHERE client_id = 46);
SELECT client_id FROM clients_user_groups WHERE group_id = #groupID;
You will have a query which gets the group ID and you store it into a variable. After this you select the client_id values where the group_id matches the value stored in your variable. You can speed up this query even more if you define an index for clients_user_groups.group_id.
Note1: I didn't test my code, hopefully there are no typos, but you've got the idea I think.
Note2: This should be done in a single request, because DB requests are very expensive if we look at the needed time.
Based on your comment that each client can only belong to one group, I would suggest a schema change to place the group_id relation into the client table as a field. Typically, one would use the sort of JOIN table you have described to express many-to-many relationships within a relational database (i.e. clients could belong to many groups and groups could have many clients).
In such a scenario, the query would be made without the need for a sub-select like this:
SELECT c.client_id
FROM clients as c
INNER JOIN clients as c2 ON c.group_id = c2.group_id
WHERE c2.client_id = ?

MySQL JOIN tables with WHERE clause

I need to gather posts from two mysql tables that have different columns and provide a WHERE clause to each set of tables. I appreciate the help, thanks in advance.
This is what I have tried...
SELECT
blabbing.id,
blabbing.mem_id,
blabbing.the_blab,
blabbing.blab_date,
blabbing.blab_type,
blabbing.device,
blabbing.fromid,
team_blabbing.team_id
FROM
blabbing
LEFT OUTER JOIN
team_blabbing
ON team_blabbing.id = blabbing.id
WHERE
team_id IN ($team_array) ||
mem_id='$id' ||
fromid='$logOptions_id'
ORDER BY
blab_date DESC
LIMIT 20
I know that this is messy, but i'll admit, I am no mysql veteran. I'm a beginner at best... Any suggestions?
You could put the where-clauses in subqueries:
select
*
from
(select * from ... where ...) as alias1 -- this is a subquery
left outer join
(select * from ... where ...) as alias2 -- this is also a subquery
on
....
order by
....
Note that you can't use subqueries like this in a view definition.
You could also combine the where-clauses, as in your example. Use table aliases to distinguish between columns of different tables (it's a good idea to use aliases even when you don't have to, just because it makes things easier to read). Example:
select
*
from
<table> as alias1
left outer join
<othertable> as alias2
on
....
where
alias1.id = ... and alias2.id = ... -- aliases distinguish between ids!!
order by
....
Two suggestions for you since a relative newbie in SQL. Use "aliases" for your tables to help reduce SuperLongTableNameReferencesForColumns, and always qualify the column names in a query. It can help your life go easier, and anyone AFTER you to better know which columns come from what table, especially if same column name in different tables. Prevents ambiguity in the query. Your left join, I think, from the sample, may be ambigous, but confirm the join of B.ID to TB.ID? Typically a "Team_ID" would appear once in a teams table, and each blabbing entry could have the "Team_ID" that such posting was from, in addition to its OWN "ID" for the blabbing table's unique key indicator.
SELECT
B.id,
B.mem_id,
B.the_blab,
B.blab_date,
B.blab_type,
B.device,
B.fromid,
TB.team_id
FROM
blabbing B
LEFT JOIN team_blabbing TB
ON B.ID = TB.ID
WHERE
TB.Team_ID IN ( you can't do a direct $team_array here )
OR B.mem_id = SomeParameter
OR b.FromID = AnotherParameter
ORDER BY
B.blab_date DESC
LIMIT 20
Where you were trying the $team_array, you would have to build out the full list as expected, such as
TB.Team_ID IN ( 1, 4, 18, 23, 58 )
Also, not logical "||" or, but SQL "OR"
EDIT -- per your comment
This could be done in a variety of ways, such as dynamic SQL building and executing, calling multiple times, once for each ID and merging the results, or additionally, by doing a join to yet another temp table that gets cleaned out say... daily.
If you have another table such as "TeamJoins", and it has say... 3 columns: a date, a sessionid and team_id, you could daily purge anything from a day old of queries, and/or keep clearing each time a new query by the same session ID (as it appears coming from PHP). Have two indexes, one on the date (to simplify any daily purging), and second on (sessionID, team_id) for the join.
Then, loop through to do inserts into the "TempJoins" table with the simple elements identified.
THEN, instead of a hard-coded list IN, you could change that part to
...
FROM
blabbing B
LEFT JOIN team_blabbing TB
ON B.ID = TB.ID
LEFT JOIN TeamJoins TJ
on TB.Team_ID = TJ.Team_ID
WHERE
TB.Team_ID IN NOT NULL
OR B.mem_id ... rest of query
What I ended up doing is;
I added an extra column to my blabbing table called team_id and set it to null as well as another field in my team_blabbing table called mem_id
Then I changed the insert script to also insert a value to the mem_id in team_blabbing.
After doing this I did a simple UNION ALL in the query:
SELECT
*
FROM
blabbing
WHERE
mem_id='$id' OR
fromid='$logOptions_id'
UNION ALL
SELECT
*
FROM
team_blabbing
WHERE
team_id
IN
($team_array)
ORDER BY
blab_date DESC
LIMIT 20
I am open to any thought on what I did. Try not to be too harsh though:) Thanks again for all the info.