Say that I have 2 tables, one with entries of films that an user likes and an other with events that an user has gone. Each table has a column for knowing the user. Something like:
Table Films:
id | iduser | film | number of watches | note ....
Table events:
id | iduser | event | date | ....
both iduser are connected with a relation to a table with other information of the user.
If I want to select some columns from table films and others from table events with the same iduser, is there a better way than 2 SELECT? I say this because each select has diferent number of rows so UNION gives me an error and join gives me like:
EDIT
FILM | NOTE | EVENT | DATE
-----------------------------------------
tlor | 9 | going to park | 20/7/12
tlor | 9 | eat a sandwich | 5/9/10
B film | 7 | going to park | 20/7/12
B film | 7 | eat a sandwich | 5/9/10
EDIT 2
I say only a select because I think is the faster way but if there's a faster way, please let me know it.
Doing two SELECTs is the correct solution here. You're loading two different sets of data.
IF the two tables had similar schemas (e.g, same column names and types), you could combine the two using:
SELECT * FROM table1 WHERE userid = ? UNION SELECT * FROM table2 WHERE userid = ?
However, this will not work sensibly with two tables with different schemas.
If you for some reason need to fetch your data in using exactly one SELECT you can unify your resultsets for UNION ALL like this
SELECT 'film' type, iduser, film name, watches, note, NULL date
FROM films
WHERE iduser = ?
UNION ALL
SELECT 'event' type, iduser, event name, NULL, NULL, date
FROM events
WHERE iduser = ?
Another approach to grab all data in one go is to pack column values specific to particular table with GROUP_CONCAT into a details column and then explode it in client code
SELECT 'film' type, iduser, film name, GROUP_CONCAT(CONCAT_WS('|', watches, note)) details
FROM films
WHERE iduser = 1
GROUP BY iduser, film
UNION ALL
SELECT 'event' type, iduser, event name, GROUP_CONCAT(CONCAT_WS('|', date))
FROM events
WHERE iduser = 1
GROUP BY iduser, event
Here is SQLFiddle demo
In addition to UNION as others have mentioned, you can do this with multiple LEFT JOINS.
Something like
SELECT U.UID, F.FIELD1, E.FIELD1
FROM USERS U
LEFT JOIN EVENTS E ON U.USERID=E.USERID
LEFT JOIN FILMS F ON U.USERID=F.USERID
WHERE F.USERID IS NOT NULL OR F.USERID IS NOT NULL
Something like that could work based on your requirement. This way if the row was an EVENT row it would have fields for your event and if it was a film record the film fields would be populated.
Related
So, I have an intermediate table in MySQL called "Inventory", with two PKs: idMovie and idSubsidiary. That table looks like this:
----------------------------------
idMovie (int) | idSubsidiary (int)
----------------------------------
0 | 0
2 | 0
1 | 1
3 | 2
----------------------------------
I want to select the IDs of the pair of subsidiaries that have exactly the same movies.
For that, I was thinking about something like this:
select distinct inv1.idSubsidiary, inv2.idSubsidiary
from inventory inv1
join inventory inv2
on inv1.idSubsidiary <> inv2.idSubsidiary
where not exists (
SELECT i1.idSubsidiary, i1.idMovie , i2.idSubsidiary, i2.idMovie
FROM inventory i1
INNER JOIN inventory i2 ON i1.idMovie = i2.idMovie
WHERE (i1.idSubsidiary= inv1.idSubsidiary and i2.idSubsidiary= inv2.idSubsidiary
AND i2.idSubsidiary IS NULL
)
The result I'm looking for would be something like this:
idSubsidiary | idSubsidiary
---------------------------
0 | 1
3 | 4
So, subsidiary 0 and 1 have the same identical movies on the inventory, same with 3 and 4.
However, the previously shown query is not working. Basically, the query looks up for couples of SubsidiaryID's on Inventory and then runs a nested query to find if the first Subsidiary have any movie that the second Subsidiary does not have. If they don't, it selects both.
However, the nested query is not working. As I said, I want to do a left join of the same table without the inner part.
Any help is much appreciated :)
The simplest method in MySQL is to do double aggregation:
select movies, group_concat(idSubsidiary) as subsidiaries
from (select i.idSubsidiary, group_concat(idMovie order by idMovie) as movies
from inventory i
group by i.idSubsidiary
) s
group by movies
having count(*) > 1;
Each row in the result set is a set of movies with the set of ids that have exactly those movies. Those are the duplicates.
I have two tables.
I am a total newbie to SQL. Using mysql at the moment.
I have the following setup for a school-related db:
Table A contains students records.
Student's id, password,name, lastname and so on.
Table B contains class attendancy records.
Each record goes like this: date, student id, grade
I need to gather all the student info of students that attended classes in a certain date range.
Now, the stupid way would be
first I SELECT all classes from Table B with DATE IN BETWEEN the range
then for each class, I get the student id and SELECT * FROM students WHERE id = student id
What I can't wrap my mind around is the smart way.
How to do this in one query only.
I am failing at understanding the concepts of JOIN, UNION and so on...
my best guess so far is
SELECT students.id, students.name, students.lastname
FROM students, classes
WHERE classes.date BETWEEN 20140101 AND 20150101
AND
classes.studentid = students.id
but is this the appropriate way for this case?
Dont add the join statement in the where clause. Do it like this:
SELECT s.id, s.name, s.lastname,c.date,c.grade
FROM classes c
inner join students s
on c.studentid=s.id
WHERE c.date BETWEEN '01/01/2014' AND '01/01/2015'
This sounds like an assignment so I will attempt to describe the problem and give a hint to the solution.
An example of a union would be;
SELECT students.name, students.lastname
FROM students
WHERE students.lastname IS NOT NULL
UNION
SELECT students.name, 'N/A'
FROM students
WHERE students.lastname IS NULL;
+--------------+--------------+
| name | lastname |
+--------------+--------------+
| John | Doe | <- First two rows came from first query
| Jill | Smith |
| Bill | N/A | <- This came from the second query
+--------------+--------------+
The usual use case for a union is to display the same columns, but munge the data in a different way - otherwise you can usually achieve similar results through a WHERE clause.
An example of a join would be;
SELECT authors.id, authors.name, books.title
FROM authors LEFT JOIN books ON authors.id = books.authors_id
+--------------+--------------+------------------+
| id | name | title |
+--------------+--------------+------------------+
| 1 | Mark Twain | Huckleberry Fin. |
| 2 | Terry Prat.. | Good Omens |
+--------------+--------------+------------------+
^ First two columns from ^ Third column appended
from authors table from books table linked
by "author id"
Think of a join as appending columns to your results, a union is appending rows with the same columns.
In your situation we can rule out a union as you don't want to append more student rows, you want class and student information side by side.
Right now I'm working on expanding my website to new functionality. I want to enable notifications from different sources. Similar to groups and people on facebook. Here is my table layout right now.
course_updates
id | CRN (id of course) | update_id
------------------------------------
courses
id | course_name | course_subject | course_number
-------------------------------------------------
users
id | name | facebook_name
---------------------------------------------------
user_updates
id | user_id | update_id
------------------------
updates
id | timestamp | updateObj
---------------------------
What I would like to be able to do is take course_update and user_updates in one query and join them with updates along with the correct information for the tables. So for course_updates i would want course_name, course_subject, etc. and for user_updates i would want the username and facebook name. This honestly probably belongs in two separate queries, but I would like to arrange everything by the timestamp of the updates table, and I feel like sorting everything in php would be inefficient. What is the best way to do this? I would need a way to distinguish between notification types if i were to use something like a union because user_updates and course_updates can store a reference to the same column in updates. Any ideas?
You might not need updates table at all. You can include timestamp columns to course_updates and user_updates tables
CREATE TABLE course_updates
(
`id` int,
`CRN` int,
`timestamp` datetime -- or timestamp type
);
CREATE TABLE user_updates
(
`id` int,
`user_id` int,
`timestamp` datetime -- or timestamp type
);
Now to get an ordered and column-wise unified resultset of all updates you might find it convenient to pack update details for each update type in a delimited string (using CONCAT_WS()) in one column (let's call it details), inject a column to distinguish an update type (lets call it obj_type) and use UNION ALL
SELECT 'C' obj_type, u.id, u.timestamp,
CONCAT_WS('|',
c.id,
c.course_name,
c.course_subject,
c.course_number) details
FROM course_updates u JOIN courses c
ON u.CRN = c.id
UNION ALL
SELECT 'U' obj_type, u.id, u.timestamp,
CONCAT_WS('|',
s.id,
s.name,
s.facebook_name) details
FROM user_updates u JOIN users s
ON u.user_id = u.id
ORDER BY timestamp DESC
Sample output:
| OBJ_TYPE | ID | TIMESTAMP | DETAILS |
-------------------------------------------------------------------------
| C | 3 | July, 30 2013 22:00:00+0000 | 3|Course3|Subject3|1414 |
| U | 2 | July, 11 2013 14:00:00+0000 | 1|Name1|FB Name1 |
| U | 2 | July, 11 2013 14:00:00+0000 | 3|Name3|FB Name3 |
...
Here is SQLFiddle demo
You can then easily explode details values while you iterate over the resultset in php.
I don't think you should mix both of those concepts (user and course) together in a query. They have different number of columns and relate to different concepts.
I think you really should use two queries. One for users and one for courses.
SELECT courses.course_name, courses.course_subject, courses.course_number,
updates.updateObj,updates.timestamp
FROM courses, updates, course_updates
WHERE courses.id = course_updates.course_id
AND course_updates.udpate_id = updates.id
ORDER BY updates.timestamp;
SELECT users.name,users.facebook_name,updates.updateObj,updates.timestamp
FROM users ,updates, user_updates
WHERE users.id = user_updates.user_id
AND user_updates.update_id = updates.id
ORDER BY updates.timestamp;
If you are going to merge the two table you need to keep in mind 2 things:
Number of columns should ideally be the same
There should be a way to distinguish the source of the data.
Here is one way you could do this:
SELECT * FROM
(SELECT courses.course_name as name, courses.course_subject as details,
updates.updateObj as updateObj, updates.timestamp as timestamp,
"course" as type
FROM courses, updates, course_updates
WHERE courses.id = course_updates.course_id
AND course_updates.udpate_id = updates.id)
UNION ALL
SELECT users.name as name,users.facebook_name as details,
updates.updateObj as updateObj,updates.timestamp as timestamp,
"user" as type
FROM users ,updates, user_updates
WHERE users.id = user_updates.user_id
AND user_updates.update_id = updates.id) as out_table
ORDER BY out_table.timestamp DESC
The type will let you distinguish between user and course updates and could be used by your front end to differently colour the rows. The course_id does not appear in this but you can add it, just keep in mind that you will have to add some dummy text to the user select statement to ensure both queries return the same number of rows. Note that in case there is an update referring to both user and course, it will appear twice.
You could also order by type to differentiate user and course data.
For the sake of simplicity, let's say I have tables users and interests
users
id | name
---------
1 | amy
2 | brian
3 | carole
interests
uid | interest
--------------
1 | apples
3 | catfish
3 | cobwebs
3 | cryogenics
What I want to get back is output that looks something like
name | interests
----------------
amy | apples
brian |
carole| catfish, cobwebs, cryogenics
Where interests could be a string consisting of the concatenation of all relevant values with some delimiter, or a vector of discrete values. I'm interested in dumping this to a file, rather than putting it in a table or doing any kind of further SQL stuff with it. Doing
SELECT name, (SELECT interest from interests where uid=id) as interests from users;
Is giving me the error I mentioned in the title. Is this just not possible in the SQL paradigm? I know I can dump a join of these tables to a file, and then aggregate the values I need using a python script or something, but this feels inelegant.
try this
SELECT name , group_concat(interest) as interests from interests
LEFT JOIN users on users.id = interests.uid
GROUP BY name
DEMO HERE
update:
if you want spaces do this group_concat(interest SEPARATOR ', ')
SELECT u.name, i.interest
FROM users AS u, interests AS i
WHERE u.uid = i.id
Try a join instead, this might work.
You might be getting errors because of the ordering of your select and from statements, or do you need to have a comma after name. Like:
SELECT name, ( *then your select statement here ...* )
For simplicity, I will give a quick example of what i am trying to achieve:
Table 1 - Members
ID | Name
--------------------
1 | John
2 | Mike
3 | Sam
Table 1 - Member_Selections
ID | planID
--------------------
1 | 1
1 | 2
1 | 1
2 | 2
2 | 3
3 | 2
3 | 1
Table 3 - Selection_Details
planID | Cost
--------------------
1 | 5
2 | 10
3 | 12
When i run my query, I want to return the sum of the all member selections grouped by member. The issue I face however (e.g. table 2 data) is that some members may have duplicate information within the system by mistake. While we do our best to filter this data up front, sometimes it slips through the cracks so when I make the necessary calls to the system to pull information, I also want to filter this data.
the results SHOULD show:
Results Table
ID | Name | Total_Cost
-----------------------------
1 | John | 15
2 | Mike | 22
3 | Sam | 15
but instead have John as $20 because he has plan ID #1 inserted twice by mistake.
My query is currently:
SELECT
sq.ID, sq.name, SUM(sq.premium) AS total_cost
FROM
(
SELECT
m.id, m.name, g.premium
FROM members m
INNER JOIN member_selections s USING(ID)
INNER JOIN selection_details g USING(planid)
) sq group by sq.agent
Adding DISTINCT s.planID filters the results incorrectly as it will only show a single PlanID 1 sold (even though members 1 and 3 bought it).
Any help is appreciated.
EDIT
There is also another table I forgot to mention which is the agent table (the agent who sold the plans to members).
the final group by statement groups ALL items sold by the agent ID (which turns the final results into a single row).
Perhaps the simplest solution is to put a unique composite key on the member_selections table:
alter table member_selections add unique key ms_key (ID, planID);
which would prevent any records from being added where the unique combo of ID/planID already exist elsewhere in the table. That'd allow only a single (1,1)
comment followup:
just saw your comment about the 'alter ignore...'. That's work fine, but you'd still be left with the bad duplicates in the table. I'd suggest doing the unique key, then manually cleaning up the table. The query I put in the comments should find all the duplicates for you, which you can then weed out by hand. once the table's clean, there'll be no need for the duplicate-handling version of the query.
Use UNIQUE keys to prevent accidental duplicate entries. This will eliminate the problem at the source, instead of when it starts to show symptoms. It also makes later queries easier, because you can count on having a consistent database.
What about:
SELECT
sq.ID, sq.name, SUM(sq.premium) AS total_cost
FROM
(
SELECT
m.id, m.name, g.premium
FROM members m
INNER JOIN
(select distinct ID, PlanID from member_selections) s
USING(ID)
INNER JOIN selection_details g USING(planid)
) sq group by sq.agent
By the way, is there a reason you don't have a primary key on member_selections that will prevent these duplicates from happening in the first place?
You can add a group by clause into the inner query, which groups by all three columns, basically returning only unique rows. (I also changed 'premium' to 'cost' to match your example tables, and dropped the agent part)
SELECT
sq.ID,
sq.name,
SUM(sq.Cost) AS total_cost
FROM
(
SELECT
m.id,
m.name,
g.Cost
FROM
members m
INNER JOIN member_selections s USING(ID)
INNER JOIN selection_details g USING(planid)
GROUP BY
m.ID,
m.NAME,
g.Cost
) sq
group by
sq.ID,
sq.NAME