I'm running into some problems with a very simple query. I figure that it must be because of an incorrect assumption about how SQL queries work.
I'm trying to write a simple LEFT OUTER JOIN query using the following tables:
tmtrip_viewer( tmtrip_id, tmuser_id ) -> FKs: (tmtrip.id, tmuser.id)
Values: ( 6, 2 )
( 6, 3 )
( 7, 4 )
tmtrip( id, ...)
Values: (1, ...)
(2, ...)
(3, ...)
(4, ...)
tmuser(id, username, ...)
Values: (1, user1)
(2, user2)
(3, user3)
(4, user4)
What I want to do is:
Display alls id from tmuser table given the following conditions:
- That the id != '1'
- That the id is NOT in table tmtrip_viewer where tmtrip_viewer.tmtrip_id = 7.
Basically, I want to get all the users that are not viewing the tmtrip with tmtrip_id = 7 (except the logged in user ..id='1').
I have formulated the following query, but it does not behave as desired:
SELECT a.`id`, a.`username` FROM
`tmuser` a LEFT OUTER JOIN `tmtrip_viewer` b
ON a.`id` = b.`tmuser_id` AND b.`tmtrip_id` = '7'
WHERE a.id <> '1'
Why is this not working? What would be the right way to do this?
Add AND b.tmtrip_id IS NULL to your WHERE. Your query is getting all tmusers and their "trip 7" info if they have any; this will reduce the results to only the ones that had no "trip 7" info.
I think this should do what you want.
It would show one record for each user that doesn't have ID = 1 and also doesn't have a record in tm_tripviewer with tmtrip_id = 7.
SELECT id, username
FROM tmuser
WHERE id != 1
AND id NOT IN
(SELECT id FROM tmtrip_viewer WHERE tmtrip_id = 7)
Related
I have three table as:
1.table person
id, others
1, x
2, y
3, z
4, w
2.table followup, FOREIGN KEY (person_id) REFERENCES person (id)
id, person_id, ftime, details
1, 1, '2018-01-12', 'json_1'
2, 1, '2018-04-21', 'json_2'
3, 2, '2017-12-16', 'json_3'
4, 2, '2018-01-17', NULL
5, 3, '2018-06-02', 'json_5'
6, 4, '2018-01-19', NULL
3.table followup_track, FOREIGN KEY (fid) REFERENCES followup (id)
id, fid, ftime, details
1, 1, '2018-01-27', 't_json_1'
2, 2, '2018-05-07', 't_json_2'
3, 5, '2018-06-17', 't_json_3'
Now I want find all the last ftime of every person, and the details IS NOT NULL in followup and/or followup_track.
the result what I want to get is (here the pid is person's id):
pid, ftime, details
1, '2018-05-07', 't_json_2'
2, '2017-12-16', 'json_3'
3, '2018-06-17', 't_json_3'
Because there is no detalis != NULL for person.id = 4, so the result no need for pid=4.
Because the last time of person.id = 1 is '2018-05-07', so need that column.
I create a view like:
CREATE VIEW view_full_flup AS
SELECT
p.id AS pid, fp.ftime, fp.details
FROM
((followup_track fp
LEFT JOIN followup ON (fp.fid = followup.id))
LEFT JOIN person p ON (followup.person_id = p.id))
WHERE
fp.details IS NOT NULL
UNION
SELECT
f.person_id AS pid, f.ftime, f.details
FROM
followup f
WHERE
f.details IS NOT NULL
Then, I use sql:
SELECT *, MAX(`ftime`) FROM view_full_flup GROUP BY pid;
Is my solution right please? The details can not make index, and it is slow. How to do this right please?
You can’t select star group by(well, you might be able to in MySQL depending on how it’s set up but it’s not a great habit to get into for portability of sql skills), you have to specify a list of columns in your select and either group them (put the column name in the group by) or aggregate them (pass the column name into an aggregate function)
It feels like you’ve got your left join logic backwards- person is the only table you know you have records in, so it should be on the left side of the join.. the other tables are potentially recordless
We’re this my query I’d write it something more like:
select
Pid,
Max(case when ft.ftime > f.ftime then ft.ftime else f.ftime end) maxft
From
Person p
Left join (select * from followup where details is not null) f on f.person_id = p.id
Left join (select * from follup_track where details is not null) ft on ft.fid = f.id
Group by pid
We just join the set of tables once, having already filtered for records where either table’s details are present and then get the max date from either table
Note that this query can return null dates, if you have records where the date column is null even though details is filled in. If those are undesirable, filter hem with a HAVING or wrap the whole thing in another select and filter using a WHERE
Ps; as it stands, this query doesn’t seem particularly useful because though you know the most recent date you don’t have any other data. If it’s all you wanted to know (I wasn’t able to tell from your original post because the description and the query disagreed in what they said they wanted) then great, but if you wanted the other info, maybe using mysql 8’s new analytic queries would better suit:
Select * from
(
select
Pid,
Row_number over(partition by pid order by (case when ft.ftime > f.ftime then ft.ftime else f.ftime end) desc) rown
From
Person p
Left join (select * from followup where details is not null) f on f.person_id = p.id
Left join (select * from follup_track where details is not null) ft on ft.fid = f.id
) a where rown = 1
EDIT: I seemed to have asked this question incorrectly.
I'm trying to find a way to query if a set is available in another set. For example:
SELECT * FROM something
WHERE (1, 3) IN (1, 2, 3, 4, 5)
In this case, 1 & 3 are in the set (1, 2, 3, 4, 5). Another example:
SELECT * FROM something
WHERE (1, 3) IN (1, 5, 7, 9);
In this case, 1 & 3 ARE NOT in the set (1, 5, 7, 9) so nothing should be pulled from the table.
NOTE: This answers the original question, which seems to have nothing to do with the question after OP modifications.
You can get the users who completed all three levels by using:
SELECT cl.user_id
FROM completed_levels cl
WHERE cl.id IN (3, 5, 7)
GROUP BY cl.user_id
HAVING COUNT(DISTINCT cl.id) = 3;
(Note: DISTINCT is not necessary if the ids for a given user are unique.)
THEN, you can get what you want using a JOIN or similar construct:
SELECT u.*
FROM users u JOIN
(SELECT cl.user_id
FROM completed_levels cl
WHERE cl.id IN (3, 5, 7)
GROUP BY cl.user_id
HAVING COUNT(DISTINCT cl.id) = 3
) cu
ON cl.user_id = u.id;
NEW REQUEST (according to sqlfiddle.com/#!9/f36d92/2):
# The goal is to write a query that will select all exercises
# that the user has the correct equipment for, where the pre-defined
# set is the id's of the equipment the user has.
# For example, let's assume the user has equipment (1, 4)
# The exercise "Curls" should be pulled from the table, as the user has all
# of the required equipment based on the exercise_requirements table.
# while "Wrecking Ball" is not returned as the user only has a portion of the
# required equipment.
# If the user's equipment was (1, 3, 4) then both "Curls" and "Wrecking ball"
# would be returned from the exercises table, as the user has the required equipment
# for both exercises.
#----
#Below is my take on your query.
SELECT ex.* FROM exercises ex
WHERE ex.id IN (
SELECT exercise_id FROM exercise_requirements
WHERE ex.id IN (1, 4)
GROUP BY exercise_id
HAVING COUNT(distinct exercise_id) = 3
);
SOLUTION:
You are confusing some IDs here. This would be closer:
SELECT ex.* FROM exercises ex
WHERE ex.id IN (
SELECT exercise_id FROM exercise_requirements
WHERE equipment_id IN (1, 4)
GROUP BY exercise_id
HAVING COUNT(distinct equipment_id) = 2
);
But still this query is vice versa. We don't want to know whether all the user's equipment are found in a set of equipment needed for an exercise, but whether the whole set of equipment needed for an exercise is found in the user's equipment.
Probably the easiest way to write this is: aggregate exercise_requirements per exercise_id and check that no equipment_id is needed that the user doesn't have.
select *
from exercises
where id in
(
select exercise_id
from exercise_requirements
group by exercise_id
having sum(equipment_id not in (1, 4)) = 0
);
Your updated fiddle: http://sqlfiddle.com/#!9/f36d92/5
You are using the IN clause with a correlated subquery (i.e. the subquery references u.id). This is not how we use it. The IN clause is great for non-correlated subqueries; if you need a correlated subquery, use EXISTS instead. For your problem a non-correlated subquery suffices, so use IN accordingly:
select *
from users
where u.id in (select user_id from completed_levels where id in (1, 5, 7);
If a user must have all levels:
select *
from users
where u.id in (select user_id from completed_levels where id = 1
and u.id in (select user_id from completed_levels where id = 5
and u.id in (select user_id from completed_levels where id = 7;
Such problems are usually better solved with an aggregation so as not to have to query the same table again and again:
select *
from users
where u.id in
(
select user_id
from completed_levels where id in (1, 5, 7)
group by user_id
having count(distinct id) = 3
);
You could use this
SELECT u.*
FROM users u
INNER JOIN completed_levels cl
ON cl.user_id = u.id
WHERE cl.id IN (1, 5, 7);
Or using EXISTS as link from #DanFromGermany
You can use Case to make a Sum which will increase with 1 for each level within 1, 5 & 7.
SELECT A.* FROM users AS
INNER JOIN
(
SELECT U.id,
SUM(CASE WHEN
(
A.completed_levels = 1
OR A.completed_levels = 5
OR A.completed_levels = 7
) THEN 1 ELSE 0 END
) AS RN
FROM completed_levels A
INNER JOIN users U ON A.user_id = U.id
GROUP BY U.id
) B ON A.id = B.id
WHERE B.RN = 3 -- Those users have completed level 1, 5 & 7 will have RN = 3 only
What I was trying to do is to get data from multiple tables, supposed that I have the following results in my query:
The numbers in the column ticket_item_type represents certain table. For example, 2 is for tbl_company and 3 is for tbl_lease. Then the details represents the id of a certain record in that table.
Suppose that I want to get the title of those records using ticket_item_type and details. Is it possible to embed it to the results? Or should I make separate queries for each.
I know JOIN, but I is it only for single table?
Here's my MYSQL query for the image above:
SELECT *
FROM
(SELECT *
FROM ticket_items
WHERE hs_customer = 1
AND ticket IN
(SELECT id
FROM tickets
WHERE hs_customer='1'
AND ticket_status = 'dispatch_reviewed')
AND ticket IN
(SELECT ticket
FROM ticket_items
WHERE ticket_item_type = 5
AND details = '159')) AS TB1
WHERE ticket_item_type IN (3,
2,
8)
You could try something like this:
SELECT
TB1.*,
CASE
WHEN TB1.ticket_item_type = 2 THEN t2.title
WHEN TB1.ticket_item_type = 3 THEN t3.title
WHEN TB1.ticket_item_type = 8 THEN t8.title
ELSE 'NA'
END as title
FROM
(
SELECT *
FROM ticket_items
WHERE hs_customer = 1
AND ticket IN (SELECT id FROM tickets WHERE hs_customer='1' AND ticket_status = 'dispatch_reviewed')
AND ticket IN (SELECT ticket FROM ticket_items WHERE ticket_item_type = 5 AND details = '159')
) AS TB1
LEFT JOIN tbl_company t2 ON TB1.details = t2.id
LEFT JOIN tbl_lease t3 ON TB1.details = t3.id
LEFT JOIN tbl_next t8 ON TB1.details = t8.id
WHERE ticket_item_type IN (3, 2, 8)
However, this is not a design that I would prefer. Without looking at details of your database it's going to be hard to write a query to cover multiple types of ticket_item_type. I hope this query works for you, though.
In my project, I have two tables like this:
parameters (
id PRIMARY KEY,
name
)
and
parameters_offeritems (
id_offeritem,
id_parameter,
value,
PRIMARY KEY (id_offeritem, id_parameter)
)
I'm not showing structure of offeritems table, because it's not necessary.
Some sample data:
INSERT INTO parameters (id, name) VALUES
(1, 'first parameter'), (2, 'second parameter'), (3, 'third parameter')
INSERT INTO parameters_offeritems (id_offeritem, id_parameter, value) VALUES
(123, 1, 'something'), (123, 2, 'something else'), (321, 2, 'anything')
Now my question is - how to fetch (for given offer ID) list of all existing parameters, and moreover, if for the given offer ID there are some parameters set, I want to fetch their value in one query.
So far, I made query like this:
SELECT p.*, p_o.value FROM parameters p LEFT JOIN parameters_offeritems p_o
ON p.id = p_o.id_parameter WHERE id_offeritem = OFFER_ID OR id_offeritem IS NULL
But it fetches only those parameters, for which there are no existing records in parameters_offeritems table, or parameters, for which value are set only for the current offer.
To get all parameters, plus the value of any parameters set for a specific Offer Item, you need to move the Offer ID logic into the join like this (see below).
SELECT p.*, p_o.value
FROM parameters p
LEFT JOIN parameters_offeritems p_o
ON p.id = p_o.id_parameter
AND id_offeritem = OFFER_ID;
If you have logic in your WHERE clause referring to fields in a table you are doing a LEFT JOIN on, you effectively change your JOIN to an INNER JOIN (unless you are checking for a NULL).
The magic word you're looking for is OUTER JOIN. Jeff Atwood did a nice Venn-diagram explanation here.
Your query was almost perfect, just WHERE in wrong pace:
SELECT p.*, p_o.value FROM parameters p
LEFT JOIN (
SELECT * FROM parameters_offeritems
WHERE id_offeritem = OFFER_ID) as p_o
ON p.id = p_o.id_parameter
I tried to simplify my question to a basic example I wrote down below, the actual problem is much more complex so the below queries might not make much sense but the basic concepts are the same (data from one query as argument to another).
Query 1:
SELECT Ping.ID as PingID, Base.ID as BaseID FROM
(SELECT l.ID, mg.DateTime from list l
JOIN mygroup mg ON mg.ID = l.MyGroup
WHERE l.Type = "ping"
ORDER BY l.ID DESC
) Ping
INNER JOIN
(SELECT l.ID, mg.DateTime from list l
JOIN mygroup mg ON mg.ID = l.MyGroup
WHERE l.Type = "Base"
ORDER BY l.ID DESC
) Base
ON Base.DateTime < Ping.DateTime
GROUP BY Ping.ID
ORDER BY Ping.ID DESC;
+--------+--------+
| PingID | BaseID |
+--------+--------+
| 11 | 10 |
| 9 | 8 |
| 7 | 6 |
| 5 | 3 |
| 4 | 3 |
+--------+--------+
// from below I need to replace 11 by PingID above and 10 by BaseID above then the results to show up on as third column above (0 if no results, 1 if results)
Query 2:
SELECT * FROM
(SELECT sl.Data FROM list l
JOIN sublist sl ON sl.ParentID = l.ID
WHERE l.Type = "ping" AND l.ID = 11) Ping
INNER JOIN
(SELECT sl.Data FROM list l
JOIN sublist sl ON sl.ParentID = l.ID
WHERE l.Type = "base" AND l.ID = 10) Base
ON Base.Data < Ping.Data;
How can I do this? Again I'm not sure what kind of advice I will receive but please understand that the Query 2 is in reality over 200 lines and I basically can't touch it so I don't have so much flexibility as I'd like and ideally I'd like to get this working all in SQL without having to script this.
CREATE DATABASE lookback;
use lookback;
CREATE TABLE mygroup (
ID BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
DateTime DateTime
) ENGINE=InnoDB;
CREATE TABLE list (
ID BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
Type VARCHAR(255),
MyGroup BIGINT NOT NULL,
Data INT NOT NULL
) ENGINE=InnoDB;
CREATE TABLE sublist (
ID BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
ParentID BIGINT NOT NULL,
Data INT NOT NULL
) ENGINE=InnoDB;
INSERT INTO mygroup (DateTime) VALUES ("2012-03-09 22:33:19"), ("2012-03-09 22:34:19"), ("2012-03-09 22:35:19"), ("2012-03-09 22:36:19"), ("2012-03-09 22:37:19"), ("2012-03-09 22:38:19"), ("2012-03-09 22:39:19"), ("2012-03-09 22:40:19"), ("2012-03-09 22:41:19"), ("2012-03-09 22:42:19"), ("2012-03-09 22:43:19");
INSERT INTO list (Type, MyGroup, Data) VALUES ("ping", 1, 4), ("base", 2, 2), ("base", 3, 4), ("ping", 4, 7), ("ping", 5, 8), ("base", 6, 7), ("ping", 7, 8), ("base", 8, 3), ("ping", 9, 10), ("base", 10, 2), ("ping", 11, 3);
INSERT INTO sublist (ParentID, Data) VALUES (1, 2), (2, 3), (3, 6), (4, 8), (5, 4), (6, 5), (7, 1), (8, 9), (9, 11), (10, 4), (11, 6);
The simplest way of dealing with this is temporary tables, described here and here. If you create an empty table to store your results (let's call it tbl_temp1) you can to this:
INSERT INTO tbl_temp1 (PingID, BaseID)
SELECT Ping.ID as PingID, Base.ID as BaseID
FROM ...
Then you can query it however you like:
SELECT PingID, BaseID from tbl_temp1 ...
Edited to add:
From the docs for CREATE TEMPORARY TABLE:
You can use the TEMPORARY keyword when creating a table. A TEMPORARY
table is visible only to the current connection, and is dropped
automatically when the connection is closed. This means that two
different connections can use the same temporary table name without
conflicting with each other or with an existing non-TEMPORARY table of
the same name. (The existing table is hidden until the temporary table
is dropped.)
If this were a more flattened query, then there would a straightforward answer.
It is certainly possible to use a derived table as the input to outer queries. A simple example would be:
select
data1,
(select data3 from howdy1 where howdy1.data1 = greetings.data1) data3_derived
from
(select data1 from hello1 where hello1.data2 < 4) as greetings;
where the derived table greetings is used in the inline query. (SQL Fiddle for this simplistic example: http://sqlfiddle.com/#!3/49425/2 )
Following this logic would lead us to assume that you could cast your first query as a derived table of query1 and then recast query2 into the select statement.
For that I constructed the following:
select query1.pingId, query1.baseId,
(SELECT ping.Data pingData FROM
(SELECT sl.Data FROM list l
JOIN sublist sl ON sl.ParentID = l.ID
WHERE l.Type = "ping" AND l.ID = query1.pingId
) Ping
INNER JOIN
(SELECT sl.Data FROM list l
JOIN sublist sl ON sl.ParentID = l.ID
WHERE l.Type = "base" AND l.ID = query1.baseId
) Base
ON Base.Data < Ping.Data)
from
(SELECT Ping.ID as PingID, Base.ID as BaseID FROM
(SELECT l.ID, mg.DateTime from list l
JOIN mygroup mg ON mg.ID = l.MyGroup
WHERE l.Type = "ping"
ORDER BY l.ID DESC
) Ping
INNER JOIN
(SELECT l.ID, mg.DateTime from list l
JOIN mygroup mg ON mg.ID = l.MyGroup
WHERE l.Type = "Base"
ORDER BY l.ID DESC
) Base
ON Base.DateTime < Ping.DateTime
GROUP BY Ping.ID
) query1
order by pingId desc;
where I have inserted query2 into a select clause from query1 and inserted query1.pingId and query1.baseId in place of 11 and 10, respectively. If 11 and 10 are left in place, this query works (but obviously only generates the same data for each row).
But when this is executed, I'm given an error: Unknown column 'query1.pingId'. Obviously, query1 cannot be seen inside the nested derived tables.
Since, in general, this type of query is possible, when the nesting is only 1 level deep (as per my greeting example at the top), there must be logical restrictions as to why this level of nesting isn't possible. (Time to pull out the database theory book...)
If I were faced with this, I'd rewrite and flatten the queries to get the real data that I wanted. And eliminate a couple things including that really nasty group by that is used in query1 to get the max baseId for a given pingId.
You say that's not possible, due to external constraints. So, this is, ultimately, a non-answer answer. Not very useful, but maybe it'll be worth something.
(SQL Fiddle for all this: http://sqlfiddle.com/#!2/bac74/35 )
If you cannot modify query 2 then there is nothing we can suggest. Here is a combination of your two queries with a reduced level of nesting. I suspect this would be slow with a large dataset -
SELECT tmp1.PingID, tmp1.BaseID, IF(slb.Data, 1, 0) AS third_col
FROM (
SELECT lp.ID AS PingID, MAX(lb.ID) AS BaseID
FROM MyGroup mgp
INNER JOIN MyGroup mgb
ON mgb.DateTime < mgp.DateTime
INNER JOIN list lp
ON mgp.ID = lp.MyGroup
AND lp.Type = 'ping'
INNER JOIN list lb
ON mgb.ID = lb.MyGroup
AND lb.Type = 'base'
GROUP BY lp.ID DESC
) AS tmp1
LEFT JOIN sublist slp
ON tmp1.PingID = slp.ParentID
LEFT JOIN sublist slb
ON tmp1.BaseID = slb.ParentID
AND slb.Data < slp.Data;