Table with multiple items in one field - mysql

I'm pretty much a MySql newbie so sorry if this sounds daft or I ramble off topic.
I am building a table to list all the data about exhibitions. This comprises fields with data like venue, dates, times, etc and one field with a list of items exhibited. This field will have a delimited list of numbers. They will between 1 and 50 numbers in the range 1 to 999, currently the num_max is 170.
I realise that it would be better practice to hold this data in separate tables but that complicates the uploading process, would require a new table being created for each new exhibition and give rise to more opportunity for errors.
Assuming that this strategy is correct, my real problem is in processing the data.
How do I extract the list of numbers then use it to get an array of product numbers from the master product table?

You should only need one more table that holds items to be included in an exhibit. This table could have several rows for one exhibit, like so:
exhibit_id item_id description
______________________________________________
1 1 painting
1 2 statue
Then, all you would need to do is join the two tables together.
Otherwise, if you did not want to do this, use the php explode method to turn a delimited string into an array.
$data = mysql_query($query);
while ($row = mysql_fetch_assoc($result)) {
$items = $row['items'];
//the explode method in php allows you to turn a delimited string into an array.
$items_array = explode(',', $items);
//loop through each of the items in this exhibit
foreach($items_array as $current_item) {
//do something with $current_item
}
}

If you are going to hold a field that has data such as "2,45,67,126" then you're going to have to process that using the language in which you're extracting it, perhaps PHP.
The 'real' solution would be to have a unique identifier on the table holding the exhibitions and have a second table with the items. So for example, you'd have an id of '42' for the exhibition then a second table (called, perhaps 'items') holding:
id item
42 2
42 45
42 67
42 126
That shouldn't complicate your upload process too much and then you can easily return all the items in an exhibition using:
SELECT item FROM items WHERE id=$exhibition_id

Actually, it wouldn't require a new table for each exhibit, just adding rows to an existing table.
The rows in the child table would be "related" to the parent table by a foreign key.
For example
exhibit
( id int auto_increment primary key comment 'PK unique identifier for exhibit'
, exhibit_venue
, exhibit_date
et al.
)
exhibit_item
( exhibit_id int comment 'fk to exhibit.id'
, item_id int comment 'fk to item.id'
, primary_key (exhibit_id, item_id)
, foreign_key exhibit_items_fk1 (exhibit_id) references exhibit (id)
, foreign_key exhibit_items_fk2 (item_id) references item (id)
)
item
( id int auto_increment primary key comment 'PK unique identifier for item'
, name
, description
, size
, weight
et al.
)
exhibit
id venue date
---- ------- -----------
123 Fox 2014-02-24
124 Ice 2014-03-01
item
id name description
---- -------- -----------
41 madonna painting
42 david sculpture
43 mona lisa painting
exhibit_item
exhibit_id item_id
---------- -------
123 41
123 42
123 43
If you need to store a relative sequence or position (relative order) of the items within an exhibit, you can add another attribute to the exhibit_item table, to store an integer representing the position.
To get all items for one exhibit:
SELECT i.id
, i.name
, i.description
FROM exhibit_item s
JOIN item i
ON i.id = s.item_id
WHERE s.exhibit_id = 123
ORDER BY s.position
If it's handier for you to return a comma separated list of the item ids within an exhibit, as a single string...
SELECT GROUP_CONCAT(s.id ORDER BY s.position) AS item_list
FROM exhibit_item s
WHERE s.exhibit_id = 123

Related

Creating a column in a table based on aggregation (without creating another table)

I have a table called Sets for LEGO:
set_number (Primary Key)
set_name
other_fields
123
Firetruck
abc
234
Star Wars
abc
I have another table called Parts for LEGO:
part_number (Primary Key)
name
set_number (references set_number from Sets)
1
Truck Roof
123
2
Truck Body
123
3
Neimoidian Viceroy Robe
234
I want to create another column in the Sets table to indicate the number of unique parts the particular set has.
I was able to output the number of unique parts with the following:
SELECT s.set_number, COUNT(*) AS num_diff_parts
FROM Sets s, Parts p
WHERE p.set_number = s.set_number
GROUP BY s.set_number
This outputs the following table (let's call it results):
set_number
num_diff_parts
123
2
234
1
However, I wonder if I can put the column (num_diff_parts) into the Sets table as a new column, instead of having to run this query every time when I need this information, or create another table just to contain the content of the results table.
Ideally, the Sets table should look like this:
set_number (Primary Key)
set_name
other_fields
num_diff_parts
123
Firetruck
abc
2
234
Star Wars
abc
1
I've also tried to do GROUP BY on multiple fields, but I don't think that's safe to do as those fields can have repeats and will throw off the results.
select distinct
set_number
,set_name
,other_fields
,count(*) over(partition by set_number) as num_diff_parts
from Sets join Parts using(set_number)
set_number
set_name
other_fields
num_diff_parts
123
Firetruck
abc
2
234
Star Wars
abc
1
We can also count() before joining the tables.
with parts_cnt as (
select set_number
,count(*) as num_diff_parts
from Parts
group by set_number
)
select *
from Sets join parts_cnt using(set_number)
Fiddle
However, I wonder if I can put the column (num_diff_parts) into the Sets table as a new column, instead of having to run this query every time when I need this information
I would recommend using a view ; with this technique, the information is always available, and you don’t need to keep it up to date by yourself.
In MySQL, a correlated subquery comes handy to efficiently compute the count of parts per set :
create view v_sets as
select s.*,
(
select count(*)
from parts p
where p.set_number = s.set_number
) num_diff_parts
from sets s

mysql self join on parent_id but order alphabetically

I have the following tables. I've simplified the code for readability
CREATE TABLE entries (
ID int(),
entry varchar(),
parent_id int()
);
CREATE TABLE nouns (
entry_id int(),
plural varchar(),
[...]
);
CREATE TABLE verbs (
entry_id int(),
pres_part varchar(),
past_part varchar(),
[...]
);
CREATE TABLE definitions (
ID int(),
definition varchar(),
[...]
);
CREATE TABLE definitions_entries (
definition_id int(),
entry_id int()
);
and I am displaying all the entries on a glossary page as follows
SELECT *
FROM entries
LEFT JOIN nouns ON nouns.entry_id = entries.ID
LEFT JOIN verbs ON verbs.entry_id = entries.ID
LEFT JOIN definitions_entries ON entries.ID = definitions_entries.entry_id
LEFT JOIN definitions ON definitions_entries.definition_id = definitions.ID
ORDER BY entries.entry
But I want to reorder the list so that child-entries appear beneath their parents but that parent entries are still sorted alphabetically. I image the output would look something like this...
1 entry a null definition 1
4 entry a entry x definition 2
5 entry a entry y definition 3
2 entry b null definition 4
5 entry b entry z definition 5
3 entry c null definition 6
I've tried to follow this and this and this but I'm not getting anywhere.
I don't have any SQL examples left to show because they've all just gone to shit and I'm lost and confused.
The best result I got looked something like this though...
4 entry a entry x definition 2
5 entry a entry y definition 3
5 entry b entry z definition 5
1 entry a null definition 1 missing
2 entry b null definition 4 missing
3 entry c null definition 6 missing
I'm running MySQL 5.6 so I can't use RECURSIVE WITH. Not that I even understand if that would help me.

Database structure for product attributes on orders

I'm making a POS and currently kind of stuck on the order proccess due to the product attributes.
As of now the DB looks something like this:
tbl_products
tbl_attributes (attr id, name, extra price)
tbl_sales ( where i store the time, total price etc)
tbl_salelines ( where i store what items have being order for a certain sale).
Problem is storing the attributes for an item order.
There isn't a fix amount of attributes one item can have. It could go from 1 to 10-15
therefor i don't think making some columns in the tbl_salelines with attr_1 attr_2 etc etc would solve this.
How should i approach this?
Can't you use a foreign key in tbl_attributes to youre tbl_salelines table. So you can have 1 saleline and for that saleline you can add multiple attributes which have a relation to the saleline?
Example:
tbl_saleline 1
Id -> 1
tbl_attributes
**Attribute 1**
Id -> 1
name -> 'attribute 1'
**SaleLine_Id -> 1 (FK->tbl_saleline)**
**Attribute 2**
Id -> 2
name -> 'attribute 2'
**SaleLine_Id -> 1 (FK->tbl_saleline)**

Storing IDs as comma separated values

How can I select from a database all of the rows with an ID stored in a varchar comma separated. for example, I have a table with this:
, 7, 9, 11
How can I SELECT the rows with those IDs?
Normalize your database. You should be using a lookup table most likely.
You have 2 options:
Use a function to split the string into a temp table and then join the table your selecting from to that temp table.
Use dynamic SQL to query the table where id in (#variable) --- bad choice if you choose this way.
select * from table_name where id in (7, 9, 11)
If you do typically have that comma at the start, you will need to remove it first.
Use match(column) against('7,9,11')
this willl show all varchar column of your id's where 7,9,11 is there.
But you have to be shure that ur column have fulltext index.
Just yesterday I was fixing a bug in an old application here and saw where they handled it like this:
AND (T.ServiceIDs = '#SegmentID#' OR T.ServiceIDs LIKE '#SegmentID#,%'
OR T.ServiceIDs LIKE '%,#SegmentID#,%' OR T.ServiceIDs LIKE '%,#SegmentID#')
I am assuming you are saying something like the value of ServiceIDs from the database might contain 7,9,11 and that the variable SegmentID is one or more values. It was inside a CFIF statement checking to see that SegmentID in fact had a value(which was always the case due to prior logic that would default it.
I personally though would do as others have suggested and I'd create what I always refer to as a bridging table that allows you to have 0 to many PKs from one table related to the PK of another.
I had to tackle this problem years ago where I could not change the table structure and I created a custom table type and a set of functions so I could treat the values via SQL as if they were coming from a table. That custom table type solution though was specific to Oracle and I'd not know how to do that in MySQL without some research on my part.
There is a reason querying lists is so difficult: databases are not designed to work with delimited lists. They are optimized to work best with rows (or sets) of data. Creating the proper table structure will result in much better query performance and simpler sql queries. (So while it is technically possible, you should seriously consider normalizing your database as Todd and others suggested.)
Many-to-many relationships are best represented by three (3) tables. Say you are selling "widgets" in a variety of "sizes". Create two tables representing the main entities:
Widget (unique widgets)
WidgetID | WidgetTitle
1 | Widget 1
2 | Widget 2
....
Size (unique sizes)
SizeID | SizeTitle
7 | X-Small
8 | Small
9 | Medium
10 | Large
11 | X-Large
Then create a junction table, to store the relationships between those two entities, ie Which widgets are available in which sizes
WidgetSize (available sizes for each widget)
WidgetID | SizeID
1 | 7 <== Widget 1 "X-Small"
1 | 8 <== Widget 1 + "Small"
2 | 7 <== Widget 2 + "X-Small"
2 | 9 ....
2 | 10
2 | 11
....
With that structure, you can easily return all widgets having any (or all) of a list of sizes. Not tested, but something similar to the sql below should work.
Find widgets available in any of the sizes: <cfset listOfSizes = "7,9,11">
SELECT w.WidgetID, w.WidgetTitle
FROM Widget w
WHERE EXISTS
( SELECT 1
FROM WidgetSize ws
WHERE ws.WidgetID = w.WidgetID
AND ws.SizeID IN (
<cfqueryparam value="#listOfSizeIds#"
cfsqltype="cf_sql_integer" list="true" >
)
)
Find widgets available in all three sizes: <cfset listOfSizes = "7,9,11">
SELECT w.WidgetID, w.WidgetTitle, COUNT(*) AS MatchCount
FROM Widget w INNER JOIN WidgetSize ws ON ws.WidgetID = w.WidgetID
WHERE ws.SizeID IN (
<cfqueryparam value="#listOfSizeIds#"
cfsqltype="cf_sql_integer" list="true" >
)
GROUP BY w.WidgetID, w.WidgetTitle
HAVING MatchCount = 3

MySQL knows the common column, how?

I usually perform a JOIN between two tables using a common column, in this case 'product_code'.
Now comes this query that does it without a join:
SELECT `rp_products`.`product_code`, `rp_log`.`customer_id`, `rp_products`.`product_name`
FROM rp_products, rp_log
WHERE (`rp_log`.`customer_id` = '111')
ORDER BY `rp_products`.`product_code` ASC, `rp_log`.`customer_id` ASC
That returns:
product_code customer_id product_name
105 111 Paintballs 2000PBS
105 111 Paintballs 2000PBS
106 111 Gloves
106 111 Gloves
What I want to know, is how is it showing the correct 'product_name' without joining the 'product_code' between the tables? product_code is not a primary key or anything like that. product_name is the name of both columns in the tables.
rp_log stores the customer_id, product_code, and date of purchase (not shown here).
rp_products stores product_code, price (not shown here) and product_name.
Thank for for your time,
Evan.
You're performing a cross product of the entire rp_products table and the sub-table of rp_log which has a customer_id of 111. I'd guess that you have two 111 entries in rp_log and two entries in rp_products; then each entry in each table (or sub-table) in the join will be pair with each entry in the other to produce the four (= 2 × 2) rows in your result set.
Including more columns from rp_log and rp_products might clarify things for you.