I have a problem with creating optimal SQL query. I have private messages system where user can send single message to many of users or groups of users. Recipients are stored in single text column (don't ask me why is that I wasn't responsible for designing that) like that:
[60,63,103,68]
Additionaly I've added new text column where is placed group which user belongs to so I can have as a groups in database:
[55,11,11,0]
Now I want to get all users (receivers) and their groups. I have table where relation between user and group id. The problem is that single user can belong to multiple groups, for example user 60 can be in group ID 55 and 11. I would like to do it in the most optimal way (there can be 50+ receivers stored in column...) so I can write query like that:
SELECT u.name, u.last_name, g.group_name
FROM
user u
LEFT JOIN
group g ON u.id = g.user_id
WHERE
u.id IN (".$users.") and
g.id IN (".$groups.")
Unfortunately group name returned by query might by not proper - connected with the group ID i placed in WHERE. I may create PHP foreach and get user and his group using IDs I have:
foreach($user as $key => $single)
{
$sql = "...
where u.id = $single AND g.id = $group[$key] ";
}
but I think this is very bad way. Is there any way to get user and specified group in single query?
Since users and groups are only linked by their ordinal positions in the list, you need to make use of that.
The quick and dirty method would be to unnest() in parallel:
SELECT u.name, u.last_name, g.group_name
FROM (
SELECT unnest(string_to_array('3,1,2', ',')::int[]) AS usr_id -- users
, unnest(string_to_array('10,11,12', ',')::int[]) AS grp_id -- groups
) sel
JOIN usr_grp ug USING (usr_id, grp_id)
JOIN usr u USING (usr_id)
JOIN grp g USING (grp_id);
Note how I replaced SQL key words like user or group as identifiers.
-> SQLfiddle
This way, elements with the same ordinal positions in the array (converted from a comma-separated list) form a row. Both arrays need to have the same number of elements or the operation will result in a Cartesian product instead. That should be the case here, according to your description. Add code to verify if that condition might be violated.
Cleaner alternatives
While the above works reliably, it is a non-standard Postgres feature of SRF (set returning functions) which is frowned upon by some.
There are cleaner ways to do it. And the upcoming version 9.4 of Postgres will ship a new feature: WITH ORDINALITY, allowing for much cleaner code. This related answer demonstrates both:
PostgreSQL unnest() with element number
Related
I have a database table for a user of my website, this table gives each user a user_id.
Using normalization, I have linked the user to a group with a user_group table including user_id and group_id to link a user to a group.
I then have a group table that links a group name to the group_id.
I am trying to output the users on my webpage in a list next to the name of the group, not its id.
I was thinking of using a foreach loop to do this, but the data would need to get into one array? I dont know how I would take the user_id, find out which group _id it it paired with, find out which group name that id was paired with, and then add that to the array with the names and be able to display the group name next to the user's name using foreach.
Thanks for any help.
If i understand you correctly, you want to join you users data to group to show the user details with their group name.
Try out this:
select u.*,g.*,ug.* from users u
left join user_group ug on u.user_id=ug.user_id
left join `group` g on ug.group_id=g.group_id;
From above query you may get blank values for some user's, who are not assigned to any group.
I don't see why a left join is necessary. Should be a regular join unless he wants to display users that don't have groups.
By the way, this is the perfect example of something that should be done in the DB with a select. Pulling everything from these tables and trying to sort through them in java is not a good idea. Maybe I read this post wrong though.
I'm trying to write a simple interface for a list of companies using MySQL and PHP. So, I want to fetch some information from my database.
Here are my tables:
companies_data - only for system information.
corporate_data - here I want to keep information about big companies.
individual_data - and here I want to keep information about little companies.
So, here is the tables
And here is the query that I've written:
SELECT
a.id,
a.user_id,
a.added,
a.`status`,
a.company_id,
a.company_type,
a.deposit,
a.individual_operations_cache,
a.corporate_operations_cache,
a.physical_operations_cache,
b.full_name,
b.tax_number,
b.address,
b.statement_date,
b.psrn,
c.full_name,
c.tax_number,
c.address,
c.statement_date,
c.psrn
FROM
companies_data a
LEFT OUTER JOIN corporate_data b
ON (a.company_id = b.id) AND a.company_type = 0
LEFT OUTER JOIN individual_data c
ON (a.company_id = c.id) AND a.company_type = 1
WHERE
a.user_id = 3
This is just the code for a test, I'll expand it soon.
As you see, I've got result with extra fields like %field_name%1, %another_field_name%1 and so on. Of course it is not the mysql error - what I've asked that I've got - but I want to remove this fields? It's possible or I must convert this output on the application side?
thos %field_name%1, %another_field_name%1 , are visible since you are selecting them in your query:
b.full_name,
b.tax_number,
b.address,
b.statement_date,
b.psrn,
c.full_name,
c.tax_number,
c.address,
c.statement_date,
c.psrn
When you use fields with the same name in distinct tables, then the result column name come with this identifier field1, field2, fieldn... in order to distinguish from which table does the field come from.
If you want to avoid this names, you can use aliases as follows:
[...]
b.full_name as corporate_full_name,
[...]
Probably, if every common fields are coincident, you won´t need to show them all, so just remove them from the select.
Hope being usefull for you.
Br.
I have a SQL SELECT statement in which I'm using 3 tables.
I'm using INNER JOINs to join the tables, however I've come across a bit of an issue because two of the columns that I'd like the join conditional to be based on are different data types;
One is an integer - the id of the products table and can be seen below as p.id.
The other is a comma delimited string of these id's in the order table. customers can order more than one product at a time, so the product id's are stored as a comma delimited list.
here's how far I've gotten with the SQL:
"SELECT o.transaction_id, o.payment_status, o.payment_amount, o.product_id, o.currency, o.payment_method, o.payment_time, u.first_name, u.last_name, u.email, p.title, p.description, p.price
FROM orders AS o
INNER JOIN products AS p ON ( NEED HELP HERE--> p.id IN o.product_id comma delimited list)
INNER JOIN users AS u ON ( o.user_id = u.id )
WHERE user_id = '39'
ORDER BY payment_time DESC
LIMIT 1";
Perhaps I could use REGEX? currently the comma delimited list reads as '2,1,3' - however the number of characters isn't limited - so I need a conditional to check if my product id (p.id) is in this list of o.product_id?
What you have is a perfect example for one-to-many relationship where you have one order and several items attached to it. You should have a link table like
order_product - which makes the connection between a orderid and productid where you can also put specific data for the relationship between the two (like when the item was added, quantity, etc)
Then you make the join using this table and you have same field types everywhere.
simple example:
select
/* list of products */
from
order o,
order_product op,
product p
where
o.id = 20
and o.id = op.orderid
and op.productid = p.id
This in one of those very common nightmares when working with legacy database.
The rule is simple: never ever store multiple values in one table columns. This is known as first normal form.
But how to deal with that in existing DB?
The good thing™
If you have the opportunity to refactor your DB, extract the "comma separated values" to their own table. See http://sqlfiddle.com/#!2/0f547/1 for a basic example how to do that.
Then to query the tables you will have to use a JOIN as explained in elanoism's answer.
The bad thing™
I you can't or don't want do that, you probably have to rely on the FIND_IN_SET function.
SELECT * FROM bad WHERE FIND_IN_SET(target_value, comma_separated_values) > 0;
See http://sqlfiddle.com/#!2/29eba/2
BTW, why is this bad thing™? Because as you see, it is not easy to write query against multi-valued columns -- but, probably more important, you are not able to use index on that columns, nor, as a consequence, to easily perform join operations or enforce referential integrity.
The so-so thing™
As a final note, if the set of possible value is small (less that 65), an alternative approach would be to change the column type to a SET().
Prerequisites
I have two tables. A list of people in one table, and how they prefer each other in a foreign key lookup table. The first table is only the list of people. The other is where they all list a few other people they would prefer to have as a roommate.
Table People:
List of people with ID, name and surname, etc
Table Choices:
List of choosers (FK People ID)
List of chosen ones (FK People ID)
Question
How can I list matches with SQL (or PHP)? That is, where one person is also on the list on the person he wanted to have as a roommate? Basically you have a chooser with a list of chosen ones. How would you check if the chooser is also on the list of one of his or her chosen ones?
Basically I want a report with every stable match, that is where the chooser is also on the list of at least one of his or her chosen ones.
I am guessing a for loop would do the trick, but how would you even put together the first iteration? Much less the rest of the loop?
Join based solution:
SELECT
r1.name as name1,
r2.name as name2
FROM
roommate r1
JOIN
roommate_pair rp1 ON r1.id = rp1.chooser_id
JOIN
roommate r2 ON r2.id = rp1.choosen_id
JOIN
roommate_pair rp2 ON r2.id = rp2.chooser_id
WHERE
rp2.choosen_id = r1.id
GROUP BY
CONCAT(GREATEST(r1.id,r2.id),'-',LEAST(r1.id,r2.id))
Last GROUP BY is to remove duplicate matches in swapped columns. Working SQL Fiddle
SELECT a.chooser, a.chosen
FROM roommates a,roommates b
WHERE a.chooser = b.chosen
AND a.chosen = b.chooser;
Using the above query you should get the cross-referenced id's... You do, however, get doubles (both references are returned). See SQL Fiddle.
You could do a check on that in your PHP-code.
This piece of code should provide you some hint. First you will iterate through all the people. Then from the list of possible preferred people, you select only those who, in turn, have the original person in their list of preferred people.
for cc in (select * from people) loop
for dd in (select * from preferences pr where pr.source_id = cc.people_id and exists (select 1 from preferences pr1 where pr1.source_id = pr.friend_id and pr1.friend_id = cc.people_id)) loop
--do your stuff here
end loop
end loop
Everything I've seen so far is about removing duplicate entries in a database automatically. I want to stress at the beginning of this that there is no duplicate data in the database. I'll also start with the fact that I'm very much still learning about RDBMS design, normalization, relationships, and, most of all, SQL!
I have a table of clients, with a clientid (PK) and a client_name. I have a table of roles, with a roleid (PK) and a role_name. Any client can have multiple roles associated with it. So I created a client_role_link table, with clientid and roleid as the two fields. Then I run this:
SELECT client.client_name, role.role_name FROM client
LEFT JOIN client_role_link ON client_role_link.clientid=client.clientid
LEFT JOIN role ON client_role_link.roleid=role.roleid
WHERE (role.roleid='1' OR role.roleid='2')
So let's say I have a client that has two roles associated with it (roles '1' and '2'). This query returns two rows, one for each role. When I get these results back I'm using a while loop to cycle through the results and output them into a <select> list. It's then causing two <option>'s with the same client listed.
I understand why my query is returning two rows, it makes sense. So here comes the two fold question:
Is there a better database/table design that I should be using, or a more optimized query?
Or is this something I should handle in the PHP? If so, is there a more elegant solution that adding all the results into an array, then looping back through the array and removing duplicates?
Thoughts?
If you want to show both roles, then your query is OK.
MySQL does not support array datatypes, so you should fill an associative array on the PHP side using the resultset with the duplicate client names.
If you just need to show clients having either of the roles, use this query:
SELECT c.*
FROM client c
WHERE c.clientid IN
(
SELECT roleid
FROM client_role_link crl
WHERE crl.roleid IN (1, 2)
)
This will return one record per client but won't show any roles.
The third way would implode the role names on the server side:
SELECT c.*, GROUP_CONCAT(role_name SEPARATOR ';') AS roles
FROM client c
LEFT JOIN
client_role_link crl
ON crl.clientid = c.clientid
AND crl.roleid IN (1, 2)
LEFT JOIN
role r
ON r.roleid = crl.roleid
GROUP BY
c.id
and explode them on PHP side, but make sure role names won't mix with the separator.
You could use mysql_fetch_assoc() to get them back in array form. Then you could have something like code untested, but may work:
$sql = "SELECT client.id, client.client_name, role.role_name FROM client LEFT JOIN client_role_link ON client_role_link.clientid=client.clientid LEFT JOIN role ON client_role_link.roleid=role.roleid WHERE (role.roleid='1' OR role.roleid='2')";
$result = mysql_query($sql);
$res = array();
while ($row = mysql_fetch_assoc($result)) {
$res[$row['id']]['roles'][] = $row['role_name'];
$res[$row['id']]['client_name'] = $row['client_name']; //you'll be overwriting each iteration probably a better way
}