MySQL subquery overview - mysql

Alright I am guessing I need a subquery to solve this and I am little rusty on these. So I have 3 tables:
tblAccount - Has User information and AccountID
tblItem - Has Item information and ItemID
tblAccountItem - Has 3 fields - AccountItemID / AccountID / ItemID
An account can have many items and an item can have many accounts. Example data:
tblAccount
AccountID AccountName AccountEmail
1 John Smith john#smith.com
2 Fred John fred#john.com
3 George Mike george#mike.com
tblItem
ItemID ItemName ItemDescription
1 Hammer Smashes things
2 Axe Breaks things
Ok so lets say the Hammer belongs to John,Fred and George. Axe only belongs to John and Fred.
tblAccountItem
AccountItemID AccountID ItemID
1 1 1
2 2 1
3 3 1
4 1 2
5 2 2
So I want to show what items John has and also show who else owns that item. The output I want to show is:
ItemName ItemDescription OtherOwners
Hammer Smashes things Fred, George
Axe Breaks things Fred
Any help would be greatly appreciated!
The answer by ctrahey is perfect but I have a slight condition to add. There are 2 types of accounts in tblAccount denoted by a field.
tblAccount
AccountID AccountName AccountEmail AccountDescription AccountTypeID
1 John Smith john#smith.com NULL 1
2 Fred John fred#john.com NULL 1
3 George Mike george#mike.com Runner 2
tblAccountTypeID
AccountTypeID AccountType
1 User
2 Admin
If the AccountTypeID is 1 then I need to output the AccountEmail. If the AccountTypeID is 2 I need to output the AccountDescription. Eg output (same story as above):
ItemName ItemDescription OtherOwners
Hammer Smashes things Fred, Runner
Axe Breaks things Fred
Going off the query that ctrahey I am guessing there needs to be an ALIAS field created. Something like:
WHERE AccountTypeID = 1 (SELECT AccountName)
WHERE AccountTypeID = 2 (SELECT AccountDescription)
I hope this makes sense, thanks for all the help so far!

Subqueries are very rarely actually needed, and are often replaced (with improved performance) by a well-designed JOIN.
Here, we start with AccountItem table (the WHERE clause immediately limits the query to only items owned by our account of interest); then we join the same table (aliasing it to 'others_items_join'), telling it to join with the same itemID but not owned by our account if interest. That's the essence of the entire query, the next two joins are only to bring in the actual strings we want to be in our output (the other people's names and the item names/descriptions). GROUP BY is used to give just one row per item which our account of interest has.
SELECT
ItemName,
ItemDescription,
GROUP_CONCAT(others.AccountName) as OtherOwners
FROM
tblAccountItem as my_items
LEFT JOIN tblAccountItem as others_items_join
ON others_items_join.ItemID = my_items.ItemID AND others_items_join.AccountID != ?
LEFT JOIN tblAccount as others
ON others_items_join.AccountID = others.AccountID
JOIN tblItems ON my_items.ItemID = tblItems.ItemID
WHERE my_items.AccountID = ?
GROUP BY ItemName

You better use a coding to resole this , here rough query that's may helps you get an idea :
SELECT AccountName
FROM tblAccount
WHERE AccountID = (SELECT AccoundID
FROM tblAccountItem
WHERE itemID = (SELECT itemID
FROM tblAccountItem
WHERE AccountID = 1 (john Id as example)));
Hope this helps

SELECT ItemName, ItemDescription, AccountItemID FROM tblitem RIGHT JOIN tblaccountitem ON tblitem.ItemID=tblaccountitem.ItemID
I hope this helps lead to your answer. I am still looking into this

Related

Excluding unique ID in a query if at least one criteria is met

I'm having this problem which I'm unsure how to resolve.
Here's the situation : I want to get a list of all individuals who have not completed a survey. It is however possible for someone to start/complete multiple surveys.
Therefore, I want the list of individuals who have not completed at least one survey.
Here's what my query looks likes to get the list of people with incomplete surveys :
SELECT Survey.UserID, Survey.Fullname
FROM [...]
WHERE Survey.SurveySubmitted = 0 -- 0 = Unsubmitted, 1 = submitted
Now this is what the database could look like
UserID Fullname SurveySubmitted
1 John Smith 0
2 Jane Doe 1
3 Tom Glass 0
3 Tom Glass 1
Now the above query will select both John Smith and Tom Glass. However, since Tom Glass already completed at least one survey, he should be excluded.
Any ideas to proceed? It most likely needs a SELECT within another SELECT but I'm having trouble picturing it.
You could check for the user not in the user that have submited/completed
select Survey.UserID, Survey.Fullname
from [.....]
where UserID NOT IN (
SELECT Survey.UserID, Survey.Fullname
FROM [...]
WHERE Survey.SurveySubmitted = 1
)
You should group by whatever uniquely identifies a User/Survey combination and then sum the # of surveys that have been submitted. You can then use a having clause to filter out rows > 0:
select *
from Survey
group by UserId, FullName
having sum(SurveySubmitted) = 0;
SQLFiddle Example

MYSQL, is this kind of request possible?

I have persons (table person) who have 0 or N roles (tables role and personne_role).
I want to select all the persons , with the roles they have, to have this kind of result :
PHIL COLLINS | Drummer | Singer
MIKE RUTHERFORD | Singer
ION ANDERSON | Singer
MIKE JAGGER |
CARLOS SANTANA | Guitarist
......
Each line can have 0 or N roles.
To do that, I make 2 requests
the first one to get the employees (table person)
the second one to loop all the retrieved employees and retrieve each role of them (tables role and person_role)
It works BUT in the case of there are a lot of lines, it is not very efficient.
I would like the same result in 1 request.
Is it possible ?
What are the mysql keywords I must use to do that ?
Thanks for your feedback.
dominique
You could use a JOIN with a GROUP_CONCAT, something like:
SELECT person.name, role.roles
FROM person
LEFT JOIN (
SELECT person_id, GROUP_CONCAT(DISTINCT role SEPARATOR ' | ') roles
FROM person_role
GROUP BY person_id
) role ON (person.id = role.person_id)
EDIT: the fields name are just a guess, since you didn't show us the full table schema; also, if the roles are actually in a separate tale, say joined by a role_id, you'd need to add it to the subquery.

Ordering the results from a junction table based on values of the row its foreign key belongs to

I have three tables for a many-to-many: Authors, Authorships, Books. I would like to select rows from authorship and order it alphabetically according to the author this row belongs to.
Example:
-- Authors --
ID Name
1 Peter
2 Gregory
3 Daniel
-- Authorships--
ID AuthorId BookId
1 1 1
2 2 1
3 3 1
-- Books--
ID Name
1 Foobook
I would like to write a select statement that returns all rows from authorship belonging to a specific book then orders the result by author name.
So something like this:
SELECT * FROM Authorships WHERE BookId = 1 ORDER BY (Authors.Name???);
Except I need to order the result.
I understand how this question might look silly because of its workaround/inefficient nature, but I am working with a lot of legacy code and am not allowed to really change anything else.
Thank you.
This will work:
SELECT auth.* FROM Authorships auth, Authors au, Books bk WHERE auth.BookId = bk.ID and auth.AuthorId = au.ID ORDER BY au.Name
SQLFiddle Link:
SQLFiddle

Update rows based on previous select statement

I need to do as many updates as needed based on a select.
Let's say I have two tables "Groups" and "Members".
"Groups"
GroupName GroupID
Genius 1
Clever 2
Normal 3
Stupid 4
Donkey 5
"Members"
MemberName GroupID
John 1
Peter 3
Mary 1
Ashley 2
Robin 1
Louis 5
Bill 4
Paul 5
I want to change members from a GroupID to another.
I.e: Members from "Clever" to "Donkey".
select MemberName from Members where GroupID='1';
while($arr = mysqli_fetch_array($rs, MYSQLI_ASSOC))
{
$name = $arr['MemberName'];
}
Then I will update all the selected members into the new group:
$sql .= update Members set GroupID='5' where MemberName='$name';
I know I have to put all names into an array in order to update each one separately, but I'm a bit confused also in the correct update syntax.
Why not simply
UPDATE Members SET GroupID=5 WHERE GroupID=1
?
Your method would only be required if you had to do some ugly/complicated processing that couldn't be done in SQL (or would be even uglier in SQL).
One way you can do this is by following:
SELECT #DonkeyGrpID:=GroupID
FROM Groups
WHERE GroupName = 'Donkey';
UPDATE Members M, Groups G
SET M.GroupID = #DonkeyGrpID
WHERE M.GroupID = G.GroupID AND G.GroupName = 'Clever' ;

Is it possible to construct a single query to match two tables enforcing a 1:1 relationship between them?

For those interested in the reasoning behind this question: I have an e-commerce site that works fine, but has no gift certificate capabilities. Adding monetary GCs should be pretty simple, but I'd also like to allow the gifting of specific products (sounds odd but is relevant to my industry). So I plan to create a new table to house gift certificates that are linked to a specific user and product, and I need an efficient way to evaluate that table on the cart and checkout pages.
Imagine tables exist that look similar to the following:
CartContents
CartID Integer (Unique sequential row identifier)
UserID Integer
ProductID Integer
Quantity Integer
Gifts
GiftID Integer (Unique sequential row identifier)
ProductID Integer
UserID Integer
Quantity Integer
This is an overly simplified layout, but demonstrates the idea. The first table lists items in the user's cart; one record per product (though real products will have additional details that may vary). The product table has further attributes on products but I don't list it here for simplicity. The second table is a set of gift certificates, each for a specific product, that have been presented to this user ID.
Table data may look like the following:
CartContents
CartID UserID ProductID Quantity
1 1 1 1
2 1 2 2
3 1 1 2
4 2 3 1
Gifts
ProductID UserID Quantity
1 1 1
2 1 1
3 3 1
Is it possible to construct a single query that provides one row per cart item and links the above two tables taking into account that each gift may only link to each cart item once? Or does this need to be handled in a script?
In other words, because user 1 has product 1 in their cart twice, and they have only been promised one free product 1, the query should return a matching Gifts record for cartID 1, but not cartID 3. The query, pulling for user ID 1, would return:
CartID ProductID Quantity unpaidQuantity
1 1 1 0
2 2 2 1
3 1 2 2
Or
CartID ProductID Quantity unpaidQuantity
1 1 1 1
2 2 2 1
3 1 2 1
I realize that the fact that there is more than one 'right' answer to this question raises a red flag. In reality it doesn't matter which cart record each GC is applied to, as the end result (the price) will work out the same. I'm perfectly happy to say the 'first' (lowest cartID) is the one that should be linked.
My assumption is that the database will be far more efficient at this than any script I could write; I'd even be willing to bet there's some crazy type of join I've never heard of specifically designed for it. I am also assuming that any such ColdFusion script may be somewhat complicated and thus take a fair amount of development and testing time while a single query may be relatively simple (though apparently beyond my limited SQL capabilities). If I'm incorrect in this I'd appreciate any thoughts on that as well.
My setup, if it matters:
MySQL 5.0
ColdFusion 9
Windows 2000 AS
Edit:
It sounds like the quantity column is really going to cause issues, so let's continue assuming that quantity does not exist on the Gifts table. It still must exist on cartContents, however.
I thought of another way of doing this that just requires and additional group by and join. However, it requires a unique id on CartContents. I'm not sure i this is what CartId is supposed to be. However, it seems that a user could have more than one cart, so I assume not.
The idea is to identify the first record for a given product in each cart. Then, use this information when joining in the gifts.
select CartID, UserID, ProductID, Quantity, FirstCCId
from CartContents cc join
(select CartID, UserID, ProductID, min(CartContentsId) as FirstCCId
from CartContents cc
group by CartID, UserID, ProductID
) ccmin
on cc.CartId = ccmin.CartId and cc.UserId = ccmin.UserId and
cc.ProductId = ccmin.ProductId left outer join
Gifts g
on cc.ProductID= g.ProductId and cc.UserID = g.userId and
cc.CartContentsId = ccmin.FirstCCId
This works when the gift is applied to only one product line row. If the quantity for the gift is actually larger than the quantity on any given line, this query still only puts it on one line.
Does this work?
select c.cartid, c.productid, c.quantity, c.quantity -
case
when (select sum(c2.quantity) from CartContents c2
where c.userid = c2.userid
and c.productid = c2.productid
and c.cartid < c2.cartid) <
(select g.quantity from gifts g
where c.userid = g.userid
and c.productid = g.productid) then
(select g.quantity from gifts g
where c.userid = g.userid
and c.productid = g.productid) -
(select sum(c2.quantity) from CartContents c2
where c.userid = c2.userid
and c.productid = c2.productid
and c.cartid < c2.cartid)
else 0
end UnpaidQuantity
from CartContents c
where userid = 1