MySQL, per found record join a different parent table - mysql

I have the following parent <-> child datamodel:
(almost every line is a table, indented means child-of)
consumerGoods
food
meat
item
fruit
item
vegetable
item
The child-items of meat, fruit and vegetables are in the same table (named items) because they have identical attributes. In the items table I have fields that describes the parent and the parentId.
So an item record could be:
id:1
parentType:meat
parentId:4
price:3.25
expDate:2009-12-31
description:bacon
I'm now building a full text MySQL search for the contents of the description field in "items", but I also want each result to have the information of its parent table, so a "bacon-item" has the data that's in its parent record. I also want each returned result to have data that is in the parent food record and the parent consumerGoods record.
I've got the following query now, but I don't know how to join based on the value of a field in a record, or if that's even possible.
SELECT
*
FROM
item
WHERE MATCH
(description
AGAINST
('searchKey')
One way to do this is is to do multiple queries for each matching "item" record, but if I had a lot of results that would be a lot of queries and would also slow down any filtering I'd want to do for facet-based searching. Another option is to make a new table that contains all the parent item info for each item record and search through that, but then I'd have to constantly update that table if I add item records, which is redundant and quite some work.
I'd like to hear it if I'm thinking in the right direction, or if I'm totally misguided. Any suggestions welcome.

As a general rule of thumb your database structure should contain data, but should not itself be data. A sign that you're breaking this is when you feel that you have to join to a different table based on the data you're reading from some other table. At that point you need to back up and consider your overall data model because odds are very good that you're doing something not quite right.

You could join against a subquery containing the union of all parent types:
select *
from item
left join (
select 'meat' as type, Redness, '' as Ripeness from meat
union all
select 'fruit' as type, -1 as Redness, Ripeness from fruit
union all
select 'vegetable' as type, -1 as Redness, Ripeness from vegetable
) parent on parent.type = item.parentType
But if you can, redesign the database. Instead of the complex model, change it to one table of Items and one table of Categories. The categories should contain one row for meat, one for fruit, and one for vegetables.

Since your example is contrived, it's difficult to know what the actual information requirements are in your case. Damir's diagram shows you the correct way to model PKs and FKs when you have a super-type sub-type relationships.
This situation is one case of a pattern called "generalization-specialization". Almost any treatment of object modeling will deal with generalization-specialization, although it may use different terminology. However, if you want to find articles that help you build a relational database that uses specialization-generalization, search for "generalization specialization relational modeling".
The best of the articles will start by teaching you the same concept that Damir's response illustrated for you. From there, you will learn how to create queries and views that can search for either all kinds of items, or for particular kinds of items, if you know what you are searching for.
A sample view follows:
create view FruitItems as
select
c.ConsumerGoodsID,
Price,
Description,
ConsumerGoodType,
ExpiryDate,
FoodType,
IsTropic
from
ConsumerGoods c
INNER JOIN Food f on f.ConsumerGoodsID = c.ConsumerGoodsID
INNER JOIN Fruit fr on fr.ConsumerGoodsID = c.ConsumerGoodsID
Similarly, you could create views for VegetableItems, MeatItems, and HouseSupplyItems, and even one large view, namely Items, that's the union of each of the specialized views.
In the Items view IsTropic would be true for all tropical fruits, false for all non tropical fruits, and null for Meats, Vegetables, and HouseSupplies. I'm not going to show you the entire Item view for a contrived case, but you get the idea. Especially if you read the best of the articles on relational modeling of this pattern.
The Items view might be a little slow, but it could come in handy when you really don't know any better way to search. And if you search for Istropic = True, you'll automatically exclude all the Meats, Vegetables, and HouseSupplies.

As #Andomar suggested, the design is a bit off; having "multiple parent tables" does not map to DB foreign keys concept. Here is one possible suggestion. This one uses two levels of super-type/subtype relationships. Super-type table contains columns specific to all subtypes (categories), while subtype tables contain columns specific only to the category.

Related

Alternative to running multiple DB queries

I’m developing a web app with a search functionality. Generally speaking, the user can search under specific categories or groups of categories. Example:
Mammal (group)
Cat (category)
Dog (category)
Mammal, Cat, and Dog are tables in the DB, and are represented by their own class in the source code. Common fields between Cat and Dog are stored in Mammal; both Cat and Dog have a set of unique fields. I’m trying to figure out the best way to execute a query (or queries) when a user searches under a group (rather than a specific category). For example, the user searches for “all mammals under the age of 4”. As part of the response, I want to return all the fields in the tables belonging to the Mammal category (Cat and Dog, in this case).
Given that the tables Cat and Dog have unique fields, it seems (according to my googling) that I would need to run multiple queries (one for each category). Is this, indeed, the case? If so, what is most efficient way of doing this? And if not, how would I run such a request with a single query?
Essentially, my question is this: What is the most efficient way of executing a query for the situation I’ve described above?
[EDIT]
DB example w/ queries:
https://www.db-fiddle.com/f/na9ctPmi6CjyDB4MNnjycb/3
In the example in the link above, there are two queries which, together, can get all the data for the user's search (described above). I'm wondering if there's a way to do this with a single query, or at least a single call to the DB.
So far, I've tried the naive approach with the multiple query calls. This works fine (insofar as there aren't any errors). My concern is that when the stored data accumulates to the several hundreds/thousands, this approach will become too slow. Furthermore, my current approach requires additional data processing in the source code. For example, if the user wants the top 5 results from the search, then I have to get the top 5 results from each table, create an aggregated collection of the DB results, sort the collection, and pick the top 5 results from the sorted collection to return to the user. I'm wondering if there is a way to accomplish all this on the DB side (assuming that it would be faster).
From your sqlfiddle:
select * from cat left join mammal on (cat.id = mammal.id) where age < 4
union all
select * from dog left join mammal on (dog.id = mammal.id) where age < 4
A few points:
You only want left join if, for example, you'd want to show lines for mammals that don't have corresponding dogs or cats. In your case, it doesn't seem like that's what you want, so it would be better to use an inner join (or simply join, which defaults to an inner one). That way, if there are no dogs, and only 2 cats, the results would only show 2 lines, instead of 2 cats + 1 non-existing dog.
Making one table per mammal type won't scale. What happens if your users now want to add coyotes? Or rabbits? Every single time a new mammal needs to be added to the system, you need to create a new table. The proper way to do this would be to create a join table that would detail:
A table animal_type, with lines something like this:
animal_type_id name
Then you'd have a separate table called attribute_type with something like this:
attribute_id name
A separate table called animals:
animal_id animal_type_id
And then finally you'd have a separate table called animal_attributes:
id animal_id attribute_id value
Now, you can simply add animals by inserting a row in animals, specifying the animal_type_id, which could be cat/dog/whatever. You simply need to have created the animal_type beforehand. And then, you add attributes to your animal by creating rows in animal_attributes, referencing the animal_id you just created, as well as the proper animal_attribute_id, which could be shared attributes like color, length, size, and unique cat/dog/animal fields.

MySQL - appropriate application of VIEWS or FOREIGN KEYS

Suppose I have a table that acts as an inventory of my house - inventory_items if you will. inventory_items contains everything I own, but only the most general information (i.e fields that will apply to everything I own, like a name, purchase date).
I then wish to have a separate table for electronics_data which is an inventory item, but has special information to store (lets say serial_number, wattage) and another for furniture_data which contains furniture specific information (number_of_legs, material).
In all instances, items in electronics_data will have a matching item in inventory_items linked by an id field. The same is true of furniture_data.
If I now wish to show a list of my inventory items, but include specific information from the child tables, logically I think to load the inventory_data, find out what type of item this is, and load the right information from the right table. I can think of two better ways:
1) Create a foreign key relationship between inventory_items and electronics_data - thus loading all items will get me all of my child data too. But, not all items in inventory_items will have a matching item in electronics_data so does this mean a foreign key can't work?
2) Create a view which loads the extra tables if a matching item exists in them, and load the view in my application. If I have lots of different 'types' of data, will this make my view unnecessarily slow (checking everything) and actually defeat the object of the view in the first place?
These are general questions - particularly 2) I would imagine is very data dependent.
Thanks!
1) Foreign keys will work, since the specialised tables are the child tables, so you need to make sure that each record in the child table has a corresponding record in the overall inventory_items table. The reverse is not necessarily true.
2) The view can left join the child tables on the inventory_items table. If the fields used in the join are indexed in all tables, then the operation is not that resource intensive. The biggest catch could be how you build the view, if you have lots of specialised child tables. But this is probably a wider application design question anyway (if you are looking at your electronic devices, then you probably do not want to see the fields from the furniture items table - in these specialised views I would use inner join, not left join).
well it will make your life easier if you could join the tables when extracting data. There are a lot of ways to join tables, in your case if all your tables have an I.D column then you could use an 'Equijoin' This is how you could do so
SELECT inventory_items.name, electronics_data.wattage, furniture_data.material
FROM inventory_items, electronics_data, furniture_data
WHERE inventory_items.i.d=electronics_data.i.d=furniture_data.id;
so with a join like this you can add as many columns as you wish but make sure to highlight the table they are from and in the 'WHERE' clause show where they are equal otherwise it wont return any data
I have posted an fairly detailed response to a similar question here, even how to define the views you mention. Note that the code shown in the view definition is for illustration only. It will not show the most efficient way to write it. Better ways should be fairly straight-forward, however.
A word about view performance. Take a view that joins very large tables in such a way that the query
select * from <view>
takes a long time, say 30 minutes. The query
select * from <view> where <criteria>
could take fractions of a second. In most modern DBMSs, the where criteria is merged with the existing query in the view definition to execute the query. It does not execute the view definition and then do the filtering. So test view performance with actual queries not "data dump" queries.

MySql create a link between 1 or 2 tables

Does there is a cleaner way to do this ?
The Products table is linked to the sub_categories table, but if there is no sub_categories for a category, I make a link between the Products table and table categories ?
There are a number of ways to achieve this. Depending on the depth level of categories and also what your preferred implementation can implement using either of the following approaches.
Adjacency List model
A single categories table with a self referencing parent_id column that is populated for each sub category.
Nested Set model
A single categories table with "lft" and "rgt" columns to denote the position within the set. "lft" and "rgt" mean Left and Right respectively as "LEFT" and "RIGHT" are reserved words in SQL.
There is a fantastic full blog post with examples and diagrams explaining in great detail how both these approaches work - here.
I would also recommend looking at libraries, in your chosen language, that may take some of the work out what you want to achieve.

Matching items in one table with their price in another table

So I have a Query with a list of "Items" Say 'APPLE', 'Carrot', and 'Pear', and a field beside it called "Quantity". On a another table I have a column with a field called "Item", then beside it a field with "cost". (Thus to show the item and its cost.) What I want to do is search the list for the item then take that item and times the price by the quantity, and show it on the chart.
Let's also assume that in the table of items that some of the spelling is not exact so one person may have typed apple and another apples, but in my item column it just says Apple, how do I make sure that they will find each other or the closest match on the list?
I have done a little work and I assume I have to create a join of some sort, but I am not sure of the expression to do the paring or the prices with the items once that is done a simple multiplication expression can be done.
You are right that you will need to perform a JOIN between the two tables/queries to match up the rows that correspond to each other ("apples to apples", so to speak). If your first table/query was called [Orders] and your second table/query was called [Prices] then a simple query to "do the math" would be something like
SELECT
Orders.Item,
Orders.Quantity,
Prices.Price,
Orders.Quantity * Prices.Price AS Cost
FROM
Orders
INNER JOIN
Prices
ON Orders.Item = Prices.Item
Regarding your point...
Lets also assume that in the table of items that some of the spelling
is not exact so one person may have typed apple and another apples,
but in my item column it just says Apple, how do I make sure that they
will find each other
...the short answer is: You design your database in such a way that such discrepancies cannot occur. Use your favorite Internet search engine to do some research on the following topics:
Data Validation
Referential Integrity
Edit re: comment
I was just wondering if I could use a Like function with % sign of some sort to search for the closest answer to solve the [matching] problem
Generally speaking, no. Those types of approaches can be very difficult to implement reliably (think "apples" vs. "pineapples") and can be very difficult to audit if incorrect matching occurs.
If you have dirty data then you're most likely just going to have to get busy and clean it up.

Merge two results in a MySQL query if the records are related by a field value

We have a products table. Users can create new products as copies of existing products.
Instead of simply duplicating this data, we're thinking in order to minimize database size, we would store only the differences from the "parent" product. (were talking thousands of products)
My thinking is that, for each new "child" product, we create a new record in that same table which has a "parent" field which has the ID of the parent product.
So, when querying for the "child" product, is there a way to merge the results so that any empty fields in the child record will be taken from the parent?
(I hope this makes sense)
Yes, you can do this.
Say for example Your table name is Product and you want to retrieve name of child product, Then you can query as,
select IF(c.productName = '',p.productName,c.productName) as childProductName
from Products p,Products c
where c.ID = p.ParentID
Similarly you can do this for other fields.
I would anticipate that you'd want to have child products of child products (e.g. product C is based on product B, which is in turn based on product A.) And there would be children of those and so on (especially with user generated content.) This could get out of hand very quickly and require you to make either long cumbersome queries or collect the data with code rather than SQL queries.
I'm just offering this as a consideration because the saving is size often yield a cost of processing time. Just be sure you consider this before you jump into something that can't easily be undone.