Stacked SQL Query error and complications - mysql

I just learned you can stack SQL queries instead of running 4 different ones and combining the data. So I'm read tutorials and stuff but still can't figure this certain one out.
SELECT ID,
(SELECT firstname
FROM user
WHERE ID = fundraiser.user_ID) AS firstname,
(SELECT lastname
FROM user
WHERE ID = fundraiser.user_ID) AS lastname,
(SELECT org_fund_id
FROM fundraiser
WHERE ID = fundraiser.ID) AS org_fund_ID,
(SELECT ref_ID
FROM fundraiser
WHERE ID = fundraiser.ID) AS ref_ID
FROM fundraiser
WHERE 1
ORDER BY org_fund_ID ASC
Here's the basic setup for the database/tables being called:
[fundraiser] - (ID, ref_ID, user_ID, org_fund_ID) and
[user] - (firstname, lastname)
Basically, I want to pull all of the fields from "fundraiser" from the database but get the corresponding "user.firstname" and "user.lastname" where "fundraiser.user_ID" = "user.ID".
So it would come out something like this as a row:
fundraiser.ID, fundraiser.user_ID, fundraiser.ref_ID, user.firstname, user.lastname
I've tried like 30 different ways of writing this query and all have failed. The error I get is "#1242 - Subquery returns more than 1 row".
Not sure how I can give you more information so you can visualize what I'm talking about, but I will provide whatever data I can.
Thanks in advance.

to select ALL columns:
SELECT *
FROM fundraiser f
INNER JOIN user u
ON u.ID = f.user_ID
ORDER BY f.ord_fund_id ASC;
to select needed columns:
SELECT
u.firstname,
u.lastname,
f.org_fund_id,
f.ref_ID
FROM fundraiser f
INNER JOIN user u ON u.ID = f.user_ID
ORDER BY f.ord_fund_id ASC;
this should be, what you need. See this Wikipedia page.

Related

MySql - joining tables and getting updated result when one table is updated

Could you please help with this query? I just have started learning SQL, I cannot see where my mistake is.
I have tables : USERS (columns: id, firstname and surname) and POSTS(columns:id, user_id and BODY).
I want to have a joined table which would reflect the count of users that have posted at least 2 times.
So I created a table POSTSBYNUMBER. Then I used INSERT as follows:
INSERT INTO POSTSBYNUMBER
SELECT USERS.FIRSTNAME, USERS.LASTNAME, COUNT(*) AS POSTS_NUMBER
from USERS
JOIN POSTS ON USERS.ID = POSTS.USER_ID
GROUP BY POSTS.USER_ID
HAVING COUNT(*) >= 2;
The table looks all right: has columns FIRSTNAME, SECONDNAME and Posts_Numbers.
But when (using java), I added two new posts for a third user, the post is reflected in table POSTS, but not in POSTSBYNUMBER. Is there any issue with how I have written joining tables in SQL?
An INSERT happens once when you run it. You would seem to want a view instead:
CREATE VIEW POSTSBYNUMBER AS
SELECT u.FIRSTNAME, u.LASTNAME, COUNT(*) AS POSTS_NUMBER
FROM USERS u JOIN
POSTS p
ON u.ID = p.USER_ID
GROUP BY u.FIRSTNAME, u.LASTNAME
HAVING COUNT(*) >= 2;
A view is a "stored query". So, the query runs when you execute it. That way, the data is always up-to-date.
Notice that I also fixed the GROUP BY so it is consistent with the unaggregated SELECT columns.

select a column corresponding to max value in two joined tables

I have two tables, say Users and Interviews. One user can have multiple interview records.
Users
-----
UserID
FirstName
LastName
Interviews
----------
InterviewID
UserID
DateOfInterview
I want to get only the latest interview records. Here's my query
select u.UserID, firstname, lastname, max(DateOfInterview) as latestDOI
from users u
left join interviews i
on u.UserID = i.UserID
GROUP BY u.UserID, firstname, lastname
ORDER BY max(DateOfInterview) DESC
How do I update the query to return the InterviewID as well (i.e. the one which corresponds to max(DateOfInterview))?
Instead of using an aggregate function in your select list, you can use an aggregate subquery in your WHERE clause:
select u.UserID, firstname, lastname, i.InterviewId, DateOfInterview as latestDOI
from users u
left join interviews i
on u.UserID = i.UserID
where i.UserId is null or i.DateOfInterview = (
select max(DateOfInterview)
from interviews i2
where i2.UserId = u.UserId
)
That does suppose that max(DateOfInterview) will be unique per user, but the question has no well-defined answer otherwise. Note that the main query is no longer an aggregate query, so the constraints of such queries do not apply.
There are other ways to approach the problem, and it is worthwhile to look into them because a correlated subquery such as I present can be a performance concern. For example, you could use an inline view to generate a table of the per-user latest interview dates, and use joins to that view to connect users with the ID of their latest interview:
select u.*, im.latestDOI, i2.InterviewId
from
users u
left join (
select UserID, max(DateOfInterview) as latestDOI
from interviews i
group by UserID
) im
on u.UserId = im.UserId
left join interviews i2
on im.UserId = i2.UserId and im.latestDOI = i2.DateOfInterview
There are other alternatives, too, some standard and others DB-specific.
Rewrite to use an OUTER APPLY when grabbing your interview, that way you can use order by rather than MAX
select u.UserID, firstname, lastname, LatestInterviewDetails.ID, LatestInterviewDetails.DateOfInterview as latestDOI
from users u
OUTER APPLY (SELECT TOP 1 Id, DateOfInterview
FROM interviews
WHERE interviews.UserID = u.UserId
ORDER BY interviews.DateOfInterview DESC
) as LatestInterviewDetails
Note: This is providing you are using Microsoft SQL Server

MySQL query optimization: Multiple SELECT IN to LEFT JOIN

I usually go with the join approach but in this case I am a bit confused. I am not even sure that it is possible at all. I wonder if the following query can be converted to a left join query instead of the multiple select in used:
select
users.id, users.first_name, users.last_name, users.description, users.email
from users
where id in (
select assigned.id_user from assigned where id_project in (
select assigned.id_project from assigned where id_user = 1
)
)
or id in (
select projects.id_user from projects where projects.id in (
select assigned.id_project from assigned where id_user = 1
)
)
This query returns the correct result set. However, I guess the repetition of the query that selects assigned.id_project is a waste.
You could start with the project assignments of user 1 a1. Then find all assignments of other people to those projects a2, and the user in the project table p. The users you are looking for are then in either a2 or p. I added distinct to remove users who can be reached in both ways.
select distinct u.*
from assigned a1
left join
assigned a2
on a1.id_project = a2.id_project
left join
project p
on a1.id_project = p.id
join user u
on u.id = a2.id_user
or u.id = p.id_user
where a1.id_user = 1
Since both subqueries have a condition where assigned.id_user = 1, I start with that query. Let's call that assignment(s) the 'leading assignment'.
Then join the rest, using left joins for the 'optional' tables.
Use an inner join on user that matches either users of assignments linked to the leading assignment or users of projects linked to the leading project.
I use distinct, because I assumen you'd want each user once, event if they have an assignment and a project (or multiple projects).
select distinct
u.id, u.first_name, u.last_name, u.description, u.email
from
assigned a
left join assigned ap on ap.id_project = a.id_project
left join projects p on p.id = a.id_project
inner join users u on u.id = ap.id_user or u.id = p.id_user
where
a.id_user = 1
Here's an alternative way to get rid of the repetition:
SELECT
users.id,
users.first_name,
users.last_name,
users.description,
users.email
FROM users
WHERE id IN (
SELECT up.id_user
FROM (
SELECT id_user, id_project FROM assigned
UNION ALL
SELECT id_user, id FROM projects
) up
INNER JOIN assigned a
ON a.id_project = up.id_project
WHERE a.id_user = 1
)
;
That is, the assigned table's pairs of id_user, id_project are UNIONed with those of projects. The resulting set is then joined with the user_id = 1 projects to obtain the list of all users who share the projects with the ID 1 user. And now it only remains to retrieve the details for those users, which in this case is done in the same way as in your query, i.e. using an IN clause.
I'm sorry to say that I don't have MySQL to thoroughly test the performance of this query and so cannot be quite sure if it is in any way better or worse than your original query or than the one suggested both by #GolezTrol and by #Andomar. Generally I tend to agree with #GolezTrol's comment that a query with simple (semi- or whatever-) joins and repetitive parts might turn out more efficient than an equivalent sophisticated query that doesn't have repetitions. In the end, however, it is testing that must reveal the final answer for you.

Join operation duplication

Let's imagine we have two tables: Users (UserId, UserName, UserPhoto) and Articles (ArticleId, UserId, ArticleText). Now we execute inner join query to retrieve users with articles:
SELECT UserId, UserName, UserPhoto, ArticleId, ArticleText
FROM Users as u INNER JOIN Articles as a ON u.UserId = a.UserId
The structure of the query result will be the following:
UserId1 UserName1 UserPhoto1 ArticleId1 ArticleText1
UserId1 UserName1 UserPhoto1 ArticleId2 ArticleText2
So for the first user we have two articles and UserName1 and UserPhoto1 are duplicated. And what if UserPhoto stores several gigabytes blob?
I hope database protocols have some optimizations for such situations (may be some mapping telling that UserPhoto is equal for first and second lines) but I never met any notes about this. So I just want to be sure that such kind of optimization exists and I don't need to workaround it by myself
First, create a third table for Photos and associate UserId with Photo. Second, you'll need to run two separate queries in order to retrieve:
Each photo submitted by a user
Each article associated with a specific user/photo
You'll loop over all user/photo pairs, and query the articles inside your loop.
You could run two queries, one to get the User data (so each photo will travel once):
SELECT u.UserId
, u.UserName
, u.UserPhoto
FROM Users as u
and another to get the rest (Article) data:
SELECT a.UserId <--- only UserId this time
, a.ArticleId
, a.ArticleText
FROM Users as u
INNER JOIN Articles as a
ON u.UserId = a.UserId
Finally, combine the results in your application code, using the userids.
You can avoid fetching the photos multiple times like this:
SELECT * FROM (
SELECT UserId, UserName, UserPhoto, ArticleId, ArticleText
FROM Users as u INNER JOIN Articles as a ON u.UserId = a.UserId
WHERE ArticleId IN (SELECT MIN(ArticleId) FROM Articles GROUP BY UserId)
UNION ALL
SELECT UserId, UserName, NULL, ArticleId, ArticleText
FROM Users as u INNER JOIN Articles as a ON u.UserId = a.UserId
WHERE ArticleId NOT IN (SELECT MIN(ArticleId) FROM Articles GROUP BY UserId)
) base
ORDER BY ArticleId; // UserId,ArticleId will also work if you want it sorted by users.
This only fetches the photo with the first article fetched, and returns NULL for subsequent articles. Your application can cache the photo on first read.
1) No matter how many times the photoblob appears in your result set it will be read(from Disk to memory in the server) only once, There are optimizations built in to make sure this is happening.
2) However it can be transported(from server to client) multiple times, there are no optimization built in for that.
3) The best solution would be to wrap this as a stored procedure that returns 2 record sets, and you do the join in the clinet code, this approach is different from running 2 queries which needs two round trip.
4) if you dont want to do that you can get all the article ids of the user in a CSV format, and then you can easily split the csv into separate strings in the client code.
Here is the sample output
UserId UserName UserPhoto CSV_ArticleId CSV_ArticleText
------- --------- ---------- ------------------------ ----------------------------
UserId1 UserName1 UserPhoto1 ",ArticleId1,ArticleId2" ",ArticleText1,ArticleText2"
UserId2 UserName2 UserPhoto2 ",ArticleId3" ",ArticleText3"
here is how you can do it. Run the code verbatim on a test database and you can see the result
CREATE TABLE Users(UserId int , UserName nvarchar(256), UserPhoto nvarchar(256))
CREATE TABLE Articles (ArticleId int , UserId int , ArticleText nvarchar(256))
INSERT INTO Users(UserId,UserName,UserPhoto)
VALUES (2,'2a','2pa')
INSERT INTO Users(UserId,UserName,UserPhoto)
VALUES (1,'a','pa')
INSERt INTO Articles (ArticleId, UserId, ArticleText)
VALUES (2,2,'text2')
INSERt INTO Articles (ArticleId, UserId, ArticleText)
VALUES (1,2,'text1')
;WITH tArticles AS (SELECT ArticleId, UserId, ArticleText FROM Articles)
SELECT
UserId,
UserName,
UserPhoto,
(SELECT TOP 1 LTRIM(
(SELECT ',' + CONVERT(nvarchar(256),A.ArticleId) FROM Articles A WHERE U.UserId = A.UserId ORDER BY A.ArticleId FOR XML PATH(''))
)) as CSV_ArticleId,
(SELECT TOP 1 LTRIM(
(SELECT ',' + CONVERT(nvarchar(256),A.ArticleText) FROM Articles A WHERE U.UserId = A.UserId ORDER BY A.ArticleId FOR XML PATH(''))
)) as CSV_ArticleText
FROM Users U

returning results from 2 mysql tables

I am trying to figure out a complex (at least for me!) mysql query and hoped someone here might have some clue's for me..
I have 2 tables "Users" and "files".
Users:
id, name, address, etc..
Files:
id, user_id, file_name, etc..
I want to select all rows from Users and in the result create a last column that has a count of all file with where User.id = Files.user_id.
I tried SELECT * from Users UNION(SELECT COUNT Files.user_id WHERE Users.id = Files.user_id) but doesn't work of course..
select u.*, count(f.id)
from users u
left join files f on u.id = f.user_id
group by u.id