Mandatory disjoint: beautiful query? - mysql

I have a schema with table
user (username, password, fullname, usertype)
there are 4 types of users and for each there is a table with additional attributes for specific type:
individual(username (FOREIGN), education, work_since)
corporation*(username (FOREIGN), headquarters, office, num_employees)
and a couple more...
there could be only 1 record in all the additional tables for user.
I need to display all of the user information from user table and additional attribute table based on user type.
The first thing that came to mind was to first query user table, and then, based on type returned, query one of the related tables... but that would be too many queries, so I was wondering, is it possible to do it in a single query?

Use left join:
select u.*,
(case when i.username is not null then 'individual'
when c.username is not null then 'corporation'
end) as usertype,
i.education, i.work_since,
c.headquarters, c.office, c.num_employees
from users u left join
individual i
on i.username = u.username left join
corporation c
on c.username = u.username;

Related

Performing bidirectional join queries on tables with one to many relationship

I wanna have a bidirectional relationship between my two tables that are related by a one to many relationship.
So, i have a User table, with columns such as user_id, and transaction_id etc (and other user datails columns).
I have another table called transaction which has columns like transaction_id and user_id (to refer to the user this particular transaction belongs to).
Now, the issue is, a particular user can have multiple transactions attached to them, which means, this transaction table can have multiple rows for the same user. This makes it easy for me to get user details via join query. But I want to make this relationship bi-directional.
As in, if all i have is user details, i should be able to get the transaction details of that person. but since, a user can have multiple transactions at once, I'm not able to write a join query.
As such, I am yet to figure out how to store two transaction id's for a particular user, keeping in mind that i should be able to make a join query having the user details (I could always store transaction id's in the form of an array, but that won't let my join query happen).
Assuming this table structures :
transactions
id
user_id
tdate
amount
user
id
name
email
you seem to be already aware that you can list all transactions of a user, along with his information, with :
SELECT
u.id,
u.name,
u.email,
t.id,
t.tdate,
t.amount
FROM
users u
INNER JOIN transactions t ON t.user_id = u.id
WHERE
u.id = ?
ORDER BY
u.id,
t.tdate
The question mark represents the id of the user whose transactions you want to see. This returns one record for each transaction of the user.
If you are looking to, for a given transaction, list all other transactions of the same user, then one solution is to add another join to the query : basically, this would first retrieve the given transaction by id (t0), and then list all transactions by the same user (t).
SELECT
u.id,
u.name,
u.email,
t.id,
t.tdate,
t.amount
FROM
users u
INNER JOIN transactions t0 ON t0.user_id = u.id
INNER JOIN transactions t ON t.user_id = u.id
WHERE
t0.id = ?
ORDER BY
u.id,
t.tdate

Pull multiple rows from one table but only one row from a related table

I currently am trying to join two tables but prevent duplication of information from one of the tables.
The user's table has 4 columns, uid, name, email and status.
The stats table has 4 columns, uid, date, follows, views
What I would like to be able to do is pull every record from the stats table and only the name, email and status values from the user table. The issue I have with the below SQL is that it duplicates the data from the user table, is there a way around this?
SELECT u.name
, u.email
, u.status
, s.date
, s.follows
, s.views
FROM users u
JOIN stats s
ON u.id = s.uid
WHERE name = :name
If you don't want every matching stats row to be accompanied by its matching users row, then you have to run two queries:
SELECT u.id, u.name, u.email, u.status FROM users u WHERE name = :name
Note the result of u.id because you'll use it as a parameter for the second query:
SELECT s.date, s.follows, s.views FROM stats s WHERE s.uid = :uid
You have to understand that the relational model works because every query result is itself a relation. The matching data is returned in every row, and this is what allows JOIN to be part of an algebra, where the result can be used as the operand of another JOIN, or a GROUP BY, or some other relational operation.
You should read SQL and Relational Theory: How to Write Accurate SQL Code by C. J. Date.

Better MySQL query?

Here's one example, I have a Car, User, Member, and Dealer tables. At the moment I'm trying to get the name of a dealer who owns a car by matching their userids up
Select userid, name FROM `User` WHERE userid IN
(SELECT userid FROM 'Car' WHERE userid in
(SELECT userid FROM `Member` WHERE userid IN
(SELECT userid FROM `Dealer`)))
This does what I want but I can't help feel there's a better way of doing it? Currently the userid in Car is a FK of the userid in Dealer which is a FK of the userid in Member which is a FK of the userid in User which stores the name.
Can I go straight to getting all the userid's and names of dealers who's id is in the Car table, whilst making sure they're actually a Dealer?
Basically your schema is a downstream schema
Users -> Members -> Dealer -> Car
Good thing is you made all the possible keys that you need here.
So to selct anything in any table just go down stream from Users for example for the data you want
Select * from `USER` u
where
dealer.user_id = car.user_id and
member.user_id = dealer.user_id and
u.user_id = member.user_id
The reason i went upstream in matching records is because we want to make as few matching operations as possible. As you can see user table is supposed to contain the maximum records. So i match with car table and get all the user_id where there is a match in dealer. similarly i go from dealer to member and then to user. this means all the records of users will be matched with a lot fewer records that they would have been if i went from users to members to dealer to car.
But this is not fool proof solution. it will depend on your data. because it may be a case where one user may have multiple cars, then it would be better to go downstream.
Use JOIN instead of subqueries to fetch the data.
Try this:
SELECT U.userid, U.NAME
FROM `User` U
INNER JOIN Car C ON U.userid = C.userid
INNER JOIN Member M ON C.userid = M.userid
INNER JOIN Dealer D ON M.userid = D.userid;

MySQL query for getting Joomla users and their profile fields

I am creating a user profile plugin in Joomla 2.5 to extend the standard user fields.
The issue is I need an efficient way to retrieve the users (along with the extra fields) via a MySQL query. Currently the best method I can think of is querying the #__user_profiles table and processing that data to determine the extra fields to load and then producing a query that creates a separate join on the #__user_profiles table for each extra user field.
Obviously that isn't very efficient and on a large user base the query is quite slow.
Is there a better way to combine extra user fields that are separate records in another table into one query?
EDIT:
I have an external script that needs to grab all the users and their extended fields so I need to combine the #_users and #_user_profiles tables
This is simply a join between the two tables
select u.*, p.*
from #__users u
left join #__user_profiles p on p.user_id = u.id
This retrieves all user and associated profile entries.
Depending on what rows or profile entries you really need, you can restrict this query with an additional where clause.
If you want the user and associated profile entries in one row, you can combine the profile entries with group_concat
select u.*, group_concat(p.profile_key, '=', p.profile_value)
from #__users u
left join #__user_profiles p on p.user_id = u.id
group by u.id
In the end I decided to go for a pivot table approach
example:
SELECT
#__users.*,
MAX(CASE WHEN profile_key = "example.firstname" THEN profile_value END) AS FirstName,
MAX(CASE WHEN profile_key = "example.lastname" THEN profile_value END) AS LastName,
MAX(CASE WHEN profile_key = "example.company" THEN profile_value END) AS Company
FROM #__users LEFT JOIN #__user_profiles ON #__users.id = #__user_profiles.user_id
GROUP BY #__users.id
This allows me to have all the extra user data in one row so I can easily order and filter.

Hybrid Left/Right Join based on condition?

I'm trying to write an SQL statement to retrieve a list of users from a database, along side their company name (if they have a company associated with them). However, there are a couple gotchas:
Not all users have companies, but I still need to show these people in the list.
Even if a user has a company, that company could be soft-deleted (the record is still in the database, but is flagged with is_deleted = 1), and I don't want to show users that are associated with "deleted" companies.
So essentially I want to SELECT from the User table and LEFT JOIN the company table, but I don't want to include the User record at all if the company they are assigned to is_deleted.
My first inclination is that I would have to use a UNION to merge two queries together, but I was hoping there would be a cleaner way to do it?
Using Mysql 5.1
SELECT U.name Username, C.name Company
FROM User U
LEFT OUTER JOIN Company C
ON U.companyid = C.id
WHERE C.id IS NULL OR C.is_deleted = 0
C.id IS NULL gets the users with no company, and C.is_deleted = 0 gets the users with companies that haven't been soft-deleted.
Try joining to a table that excludes the deleted companies:
SELECT U.Name, C.Name
FROM User U LEFT OUTER JOIN
(SELECT CompanyId, CompanyName
FROM Company
WHERE is_deleted = 0)
C ON U.CompanyId = C.CompanyId