I have a table with this structure
(int)id | (text)title | (text)description | (int)related
and a query which joins the table with itself
SELECT t1.*, t2.title as relatedTitle
FROM mytable t1 LEFT JOIN mytable t2 ON t2.related=t1.id
to produce in one SELECT list like this
title: Hi, description: informal greetings, see also: Hello
When a new record is stored into the table, only one other record can be referenced
What I try to achieve is cross reference
which can be among 2-5 objects
All objects should be cross referenced in every combination. I want this feature: if related is set, the script should automagically create cross reference in the related records. If record is deleted, the script should update the reference in the related records.
For 3+ records cross referenced, I am considering this joining table
(int)id | (int)related
but it would be 20 records for 5 cross referenced objects. I could also create one-column table
(varchar)relatedList
but how to create the left join and how to delete relations in this structure? Or should I try some other approach like triggers, views or temporary tables? I want to avoid redundance and keep it as simple as possible and just can't figure this out.
If your groups are typically bigger than 2, then you should create a list of groups - if A is connected to B and C it makes a group A,B,C.
So, as soon as a relation is inserted, you check if the related item is already in a group. If it is set, then the "new" related entry is also in that group.
If not, you just created a new group which contains those two Items.
So if from your Example "Hi" is alone, and "Ho" gets connected to "Hi", then both form a new group.
When "Ahoi" also gets into connection to "Hi", it just needs to copy the group_id from Hi.
EDIT: according to the comment asking for the select:
The structure:
table groups: group_id int not null primary key auto_increment, created_tmstmp timestamp
table items: item_id int, group_id int default null
The select:
select * from items i1
inner join items i2 on i2.item_id != i1.item_id
and i2.group_id = i1.group_id
where i1.id = <given item>.
The insert of a relation may be connected to insert of one of the Items, this depends on the scenario of the Thread Owner. If it is a new relation for both entries then a new group is inserted.
Other questions are: is an item only in one group? Otherwise one needs a item_group table to connect a item to more then one group.
No join on strings, sorry for the possibility to be understood so cruelly. ;-)
As mzedeler said a many-to-many relation is usually realized using a join table. Please consider using a separate id column so you'd get
Id,rel1,rel2
That way frameworks like hibernate would know what to do. This makes even more sense considering the fact that you are talking about a transitive and symmetric relation. So for 4 related items 3 entries could suffice if your script does some basic inference. Of course you would need to fill the gap in a given relation chain if a connecting entry where removed.
Go with this one:
(int)id | (int)related
It is a very common approach.
If you use the list approach, your SQL queries will be extremely complicated.
Generally, SQL engines are very good at optimizing queries against tables with very large numbers of rows, so at most reasonable hardware, you shouldn't have to worry about millions of rows in such table. (Depending on what you are going to use it for, of course.)
To model non-directed edges, always insert the lowest id in the id column (you can add a CHECK constraint to enforce this). By doing this, you'll eliminate half of the tuples.
If you run into performance issues because you want to model a complete graph, consider only using the table above for "neighbor" connections, calculate the completion of the graph and insert it into a table that contains partitions of all items, one partition for each complete subgraph:
(int)partition | (int)id
Lets take a look at an example. Given the items (1 .. 8) and the edges (1, 2), (1, 3), (4, 3), (6, 5), (6, 7) - not including the edges that are required to complete the graph, you get
(int)id | (int)related
1 | 2
1 | 3
3 | 4
5 | 6
6 | 7
(And no records with the item 8.)
And then in the partition table:
(int)partition | (int)id
1 | 1
1 | 2
1 | 3
1 | 4
2 | 5
2 | 6
2 | 7
3 | 8
To check if an item is related to another item will only be a self-join on the partition table, but changing the graph requires manipulation of both tables.
Related
This question already has an answer here:
Closed 10 years ago.
Possible Duplicate:
mysql is array in multiple columns
I have two tables:
Posts Table
PostID | Gallery
1 | 4,7,8,12,13
2 | 1,2,3,4
3 | 5,8,9
4 | 3,6,11,14
The values in Gallery are the primary keys in the Images table:
Images Table
ImageID | FileName
1 | something.jpg
2 | lorem.jpg
3 | ipsum.jpg
4 | what.jpg
5 | why.jpg
The reason I do this instead of just adding a PostID key to the Images table is because those images can be associated with a lot of different posts. I suppose I could add another table for the relationships, but the comma-separated value is easier to work with as far as the jQuery script I am using to add to it.
If I'm on a page that requires the images associated with PostID 3, what kind of query can I run to output all of the FileNames for it?
You can use this solution:
SELECT b.filename
FROM posts a
INNER JOIN images b ON FIND_IN_SET(b.imageid, a.gallery) > 0
WHERE a.postid = 3
SQLFiddle
However, you should really normalize your design and use a cross-reference table between posts and images. This would be the best and most efficient way of representing N:M (many-to-many) relationships. Not only is it much more efficient for retrieval, but it will vastly simplify updating and deleting image associations.
...but the comma-separated value is easier to work with as far as the jQuery script I am using to add to it.
Even if you properly represented the N:M relationship with a cross-reference table, you can still get the imageid's in CSV format:
Suppose you have a posts_has_images table with primary key fields (postid, imageid):
You can use GROUP_CONCAT() to get a CSV of the imageid's for each postid:
SELECT postid, GROUP_CONCAT(imageid) AS gallery
FROM posts_has_images
GROUP BY postid
In terms of proper SQL, you definitely should have another table to relate the two rather than the delimited column.
That said, here's how you could do it:
SELECT * FROM Images i WHERE EXISTS (SELECT 1 FROM Posts p WHERE p.PostID = 3 AND i.ImageID IN (p.Gallery))
Here is your problem. This is bad design as you need to search for specific values of Gallery field. You can use FIND_IN_SET, but your query will be slow. Turn to atomic values for Gallery - normalize it.
I have inherited a database in which a person table has a field called authorised_areas. The front end allows the user to choose multiple entries from a pick list (populated with values from the description field of the area table) and then sets the value of authorised_areas to a comma-delimited list. I am migrating this to a MySQL database and while I'm at it, I would like to improve the database integrity by removing the authorised_areas field from the person table and create a many-to-many table person_area which would just hold pairs of person-area keys. There are several hundred person records, so I would like to find a way to do this efficiently using a few MySQL statements, rather than individual insert or update statements.
Just to clarify, the current structure is something like:
person
id name authorised_areas
1 Joe room12, room153, 2nd floor office
2 Anna room12, room17
area
id description
1 room12
2 room17
3 room153
4 2nd floor office
...but what I would like is:
person
id name
1 Joe
2 Anna
area
id description
1 room12
2 room17
3 room153
4 2nd floor office
person_area
person_id area_id
1 1
1 3
1 4
2 1
2 2
There is no reference to the area id in the person table (and some text values in the lists are not exactly the same as the description in the area table), so this would need to be done by text or pattern matching. Would I be better off just writing some php code to split the strings, find the matches and insert the appropriate values into the many-to-many table?
I'd be surprised if I were the first person to have to do this, but google search didn't turn up anything useful (perhaps I didn't use the appropriate search terms?) If anyone could offer some suggestions of a way to do this efficiently, I would very much appreciate it.
While it is possible to do this I would suggest that as a one off job it would probably be quicker to knock up a php (or your favorite scripting language) script to do it with multiple inserts.
If you must do it in a single statement then have a table of integers (0 to 9, cross join against itself to get as big a range as you need) and join this against your original table, using string functions to get the Xth comma and from that each of the values for each row.
Possible, and I have done it but mainly to show that having a delimited field is not a good idea. It would likely be FAR quicker to knock up a script with multiple inserts.
You could base an insert on something like this SELECT (although this also comes up with a blank line for each person as well as the relevant ones, and will only cope with up to 1000 authorised areas per person)
SELECT z.id, z.name, x.an_authorised_area
FROM person z
LEFT OUTER JOIN (
SELECT DISTINCT a.id, SUBSTRING_INDEX( SUBSTRING_INDEX( authorised_areas, ",", b.ournumbers ) , ",", -1 ) AS an_authorised_area
FROM person a, (
SELECT hundreds.i *100 + tens.i *10 + units.i AS ournumbers
FROM integers AS hundreds
CROSS JOIN integers AS tens
CROSS JOIN integers AS units
)b
)x ON z.id = x.id
This question already has an answer here:
Closed 10 years ago.
Possible Duplicate:
mysql is array in multiple columns
I have two tables:
Posts Table
PostID | Gallery
1 | 4,7,8,12,13
2 | 1,2,3,4
3 | 5,8,9
4 | 3,6,11,14
The values in Gallery are the primary keys in the Images table:
Images Table
ImageID | FileName
1 | something.jpg
2 | lorem.jpg
3 | ipsum.jpg
4 | what.jpg
5 | why.jpg
The reason I do this instead of just adding a PostID key to the Images table is because those images can be associated with a lot of different posts. I suppose I could add another table for the relationships, but the comma-separated value is easier to work with as far as the jQuery script I am using to add to it.
If I'm on a page that requires the images associated with PostID 3, what kind of query can I run to output all of the FileNames for it?
You can use this solution:
SELECT b.filename
FROM posts a
INNER JOIN images b ON FIND_IN_SET(b.imageid, a.gallery) > 0
WHERE a.postid = 3
SQLFiddle
However, you should really normalize your design and use a cross-reference table between posts and images. This would be the best and most efficient way of representing N:M (many-to-many) relationships. Not only is it much more efficient for retrieval, but it will vastly simplify updating and deleting image associations.
...but the comma-separated value is easier to work with as far as the jQuery script I am using to add to it.
Even if you properly represented the N:M relationship with a cross-reference table, you can still get the imageid's in CSV format:
Suppose you have a posts_has_images table with primary key fields (postid, imageid):
You can use GROUP_CONCAT() to get a CSV of the imageid's for each postid:
SELECT postid, GROUP_CONCAT(imageid) AS gallery
FROM posts_has_images
GROUP BY postid
In terms of proper SQL, you definitely should have another table to relate the two rather than the delimited column.
That said, here's how you could do it:
SELECT * FROM Images i WHERE EXISTS (SELECT 1 FROM Posts p WHERE p.PostID = 3 AND i.ImageID IN (p.Gallery))
Here is your problem. This is bad design as you need to search for specific values of Gallery field. You can use FIND_IN_SET, but your query will be slow. Turn to atomic values for Gallery - normalize it.
I'm trying to select some data from a MySQL database.
I have a table containing business details, and a seperate one containing a list of trades. As we have multiple trades
business_details
id | business_name | trade_id | package_id
1 | Happy News | 12 | 1
This is the main table, contains the business name, the trade ID and the package ID
shop_trades
id | trade
1 | newsagents
This contains the trade type of the business
configuration_packages
id | name_of_trade_table
1 | shop_trades
2 | leisure_trades
This contains the name of the trade table to look in
So, basically, if I want to find the trade type (e.g., newsagent, fast food, etc) I look in the XXXX_trades table. But I first need to look up the name of XXXX from the configuration_packages table.
What I would normally do is 2 SQL queries:
SELECT business_details.*, configuration_packages.name_of_trade_table
FROM business_details, configuration_packages
WHERE business_details.package_id = configuration_packages.id
AND business_details.id = '1'
That gives me the name of the database table to look in for the trade name, so I look up the name of the table
SELECT trade FROM XXXX WHERE id='YYYY'
Where XXXX is the name of the table returned as part of the first query and YYYY is the id of the package, again returned from the first query.
Is there a way to combine these two queries so that I only run one?
I've used subqueries before, but only on the SELECT side of the query - not the FROM side.
Typically, this is handled by a union in a single query.
Normalization gets you to a logical model. This helps better understand the data. It is common to denormalize when implementing the model. Subtypes as you have here are commonly implemented in two ways:
Seperate tables as you have, which makes retrieval difficult. This results in your question about how to retreive the data.
A common table for all subtypes with a subtype indicator. This may result in columns which are always null for certain subtypes. It simplifies data access, and may alter the way that the subtypes are handled in code.
If the extra columns for a subtype are relatively rarely accessed, then you may use a hybrid implementation where the common columns are in the type table, and some or all of the subtype columns are in a subtype table. This is more complex to code.
That's not possible, and it sounds like a problem with your model.
Why don't you put shop_trades and leisure_traces into the same table with one column to distinct between the two?
If this is possible, try this
SELECT trade
FROM (SELECT 'TABLE_NAME' FROM 'INFORMATION_SCHEMA'.'TABLES'
WHERE 'TABLE_SCHEMA'='*schema name*')
WHERE id='YYYY'
UPDATE:
I think the code I have above is not possible. :|
I'm planing to build some database project.
One of the tables have a lot of attributes.
My question is: What is better, to divide the the class into 2 separate tables or put all of them into one table. below is an example
create table User { id, name, surname,... show_name, show_photos, ...)
or
create table User { id, name, surname,... )
create table UserPrivacy {usr_id, show_name, show_photos, ...)
The performance i suppose is similar due to i can use index.
It's best to put all the attributes in the same table.
If you start storing attribute names in a table, you're storing meta data in your database, which breaks first normal form.
Besides, keeping them all in the same table simplifies your queries.
Would you rather have:
SELECT show_photos FROM User WHERE user_id = 1
Or
SELECT up.show_photos FROM User u
LEFT JOIN UserPrivacy up USING(user_id)
WHERE u.user_id = 1
Joins are okay, but keep them for associating separate entities and 1->N relationships.
There is a limit to the number of columns, and only if you think you might hit that limit would you do anything else.
There are legitimate reasons for storing name value pairs in a separate table, but fear of adding columns isn't one of them. For example, creating a name value table might, in some circumstances, make it easier for you to query a list of attributes. However, most database engines, including PDO in PHP include reflection methods whereby you can easily get a list of columns for a table (attributes for an entity).
Also, please note that your id field on User should be user_id, not just id, unless you're using Ruby, which forces just id. 'user_id' is preferred because with just id, your joins look like this:
ON u.id = up.user_id
Which seems odd, and the preferred way is this:
ON u.user_id = up.user_id
or more simply:
USING(user_id)
Don't be afraid to 'add yet another attribute'. It's normal, and it's okay.
I'd say the 2 separate tables especially if you are using ORM. In most cases its best to have each table correspond to a particular object and have its field or "attributes" be things that are required to describe that object.
You don't need 'show_photos' to describe a User but you do need it to describe UserPrivacy.
You should consider splitting the table if all of the privacy attributes are nullable and will most probably have values of NULL.
This will help you to keep the main table smaller.
If the privacy attributes will mostly be filled, there is no point in splitting the table, as it will require extra JOINs to fetch the data.
Since this appears to be a one to one relationship, I would normally keep it all in one table unless:
You would be near the limit of the number of bytes that can be stored in a row - then you should split it out.
Or if you will normally be querying the main table separately and won't need those fields much of the time.
If some columns is (not identifiable or dependent on the primary key) or (values from a definite/fixed set is being used repeatedly) of the Table make a Different Table for those columns and maintain a one to one relationship.
Why not have a User table and Features table, e.g.:
create table User ( id int primary key, name varchar(255) ... )
create table Features (
user_id int,
feature varchar(50),
enabled bit,
primary key (user_id, feature)
)
Then the data in your Features table would look like:
| user_id | feature | enabled
| -------------------------------
| 291 | show_photos | 1
| -------------------------------
| 291 | show_name | 1
| -------------------------------
| 292 | show_photos | 0
| -------------------------------
| 293 | show_name | 0
I would suggest something differnet. It seems likely that in the future you will be asked for 'yet another attribute' to manage. Rather than add a column, you could just add a row to an attributes table:
TABLE Attribute
(
ID
Name
)
TABLE User
(
ID
...
)
TABLE UserAttributes
(
UserID FK Users.ID
Attribute FK Attributes.ID
Value...
)
Good comments from everyone. I should have been clearer in my response.
We do this quite a bit to handle special-cases where customers ask us to tailor our site for them in some way. We never 'pivot' the NVP's into columns in a query - we're always querying "should I do this here?" by looking for a specific attribute listed for a customer. If it is there, that's a 'true'. So rather than having these be a ton of boolean-columns, most of which would be false or NULL for most customers, AND the tendency for these features to grow in number, this works well for us.