Another complex MySQL query involving combinations - mysql

Lets say we have a table that looks like this
connection_requirements
+-----------------------------------+
| item_id | connector_id | quantity |
+---------+--------------+----------+
| 1 | 4 | 1 |
| 1 | 5 | 1 |
| 1 | 2 | 2 |
+---------+--------------+----------+
This table is a list of connectors that a electronic device requires to operate, and how many of each type of connector it requires. (Think connections on a motherboard requiring certain types of connectors from a power supply)
Now we also have this table...
connections_compatability
+-------------------------+
| connector_id | works_as |
+--------------+----------+
| 6 | 4 |
| 6 | 5 |
+--------------+----------+
Where the first column is the connector that can also act as the connector id of the second column. (For instance a power supply has connectors such as "6+2 Pin" which can work as "8 Pin" or "6 Pin")
Now finally we have how many of each connectors are available in this table
connector_quantities
+-------------------------+
| connector_id | quantity |
+--------------+----------+
| 1 | 1 |
| 2 | 3 |
| 3 | 2 |
| 4 | 1 |
| 5 | 0 |
| 6 | 4 |
| 7 | 0 |
| 8 | 5 |
+--------------+----------+
Based off these tables, as you can infer, we do have enough connectors for item number 1 to properly operate. Even though we do not have enough of connector #5, we have 4 connector #6s, which can work as connector #4 and #5.
The connection_requirements table is joined onto the items table, how can we filter items that require more connections than we have available? We already have the code in place to filter items that require connectors that are unavailable.
The problem has many more layers of complexity to it, so we tried to simplify the problem.
Much appreciation for all the help!

One approach is to determine the "real" inventory of items including their substitutions. E.g., the real inventory of part 4 is actually 5: 1-part #4 + 4-part #6. So using that:
Select ...
From connection_requirements As CR
Where Not Exists (
Select 1
From connector_quantities As Q1
Left Join (
Select C2.connector_id, C2.works_as, Q2.quantity
From connections_compatibility As C2
Join connector_quantities As Q2
On Q2.connector_id = C2.connector_id
) As Subs1
On Subs1.works_as = Q1.connector_id
Where Q1.connector_id = CR.connector_id
And ( Coalesce(Subs1.quantity, 0) + Q1.quantity ) > CR.quantity
)
There is of course a catch with this approach. Suppose you have an item with a makeup of: 4 #4 connectors and 2 #6 connectors. Technically, you do have 4 #4 connectors (1 #4, and 3 #6 substitutions) but in combination with the requirement of 2 #6 connectors, you do not have enough parts. To solve this problem you would likely have to use a loop or multiple queries which would determine on-hand inventory after you use up all your primary parts.

Related

How to pull parents in a hierarchical table design

I'm working on a database with a category tree that's hierarchical. I'd like to be able to be able to write a query that returns all of the parents. For example, assume this structure/content. A parent of 0 means that it's a root element, no parents.
ID | Name | Parent
1 | Tools | 0
2 | Drill | 1
3 | Impact | 2
4 | Cordless | 2
5 | Series X | 4
How could I write a query that would get all of the parents of Series x (ID 5)? I don't care if it's inclusive of ID 5, since I would already have that one. I'd like to see it return the below results.
ID | Name | Parent
1 | Tools | 0
2 | Drill | 1
4 | Cordless | 2
5 | Series X | 4
Bonus if there's a way to find how many generations they are at the same time. Something like:
ID | Name | Parent | Generation
1 | Tools | 0 | 0
2 | Drill | 1 | 1
4 | Cordless | 2 | 2
5 | Series X | 4 | 3
I'm really stuck on this right now. I am thinking it might need to be a custom sql function?
In MySQL 8.0, they now support recursive CTE queries:
WITH RECURSIVE cte AS (
SELECT * FROM MyTable WHERE id = 5
UNION ALL
SELECT MyTable.* FROM cte JOIN MyTable WHERE MyTable.id = cte.parent
)
SELECT * FROM cte ORDER BY id;
Getting the "Generation" when your CTE starts at the leaf of the hierarchy is tricky.
If you are using a version of MySQL older than 8.0, you may like my answer to What is the most efficient/elegant way to parse a flat table into a tree? or my presentation Recursive Query Throwdown.

How to Query a closure table with repeated nodes for paths?

One very efficient way of storing hierarchies in SQL is by using some kind of 'closure table'.
It's a bit more difficult to edit and uses up more space, but can usually recursively be travelled with a single JOIN or a single query if you are only interested in IDs.
This table would contain 1 record for every possible ancestor/descendant relationship, as well as 1 record per item in the real table for which anc=des=id holds.
For this tree:
1
2 4 7
3 5 6
Our SQL table would contain:
+-----+-----+------+-------+
| anc | des | diff | depth |
+-----+-----+------+-------+
| 1 | 1 | 0 | 0 |
| 1 | 2 | 1 | 0 |
| 2 | 3 | 1 | 1 |
| 1 | 3 | 2 | 0 |
| 1 | 4 | 1 | 0 |
| 2 | 5 | 1 | 1 |
| 1 | 5 | 2 | 0 |
| 4 | 6 | 1 | 1 |
| 1 | 6 | 2 | 0 |
| 1 | 7 | 1 | 0 |
| 2 | 2 | 0 | 1 |
| 3 | 3 | 0 | 2 |
| 4 | 4 | 0 | 1 |
| 5 | 5 | 0 | 2 |
| 6 | 6 | 0 | 2 |
| 7 | 7 | 0 | 1 |
+-----+-----+------+-------+
Then the task: "Get all ancestors of node 5", in other words, the 'path to node 5' can be done with the following query:
SELECT `anc` FROM `closure` WHERE `des` = 5
And the task "Get all descendants of node 1" can be done with this query:
SELECT `des` FROM `closure` WHERE `anc` = 1
While "Get all direct descendants of node 1" is done like so:
SELECT `des` FROM `closure` WHERE `diff` = 1 AND `anc` = 1
Finally, "Get all root nodes" is done like this:
SELECT `anc` FROM `closure` WHERE `depth` = 0 AND `anc` = `des`
These four tasks together form the most utilized ways of selecting things from the tree.
However, in reality, when categorizing things, people can't decide where to put things. Inevitably, something is required to end up in multiple places in the tree. This throws a spanner in the works; two of these naïve queries no longer work (No 1 and No 2).
Note that, in order to prevent problems with stack overflow and recursion, it is true in the example that our graph remains a kind of 'tree'; there are no cycles in it.
The first problem is that "Get all descendants" now has duplicate results. This can be fixed with a GROUP BY clause.
The second, harder, for me unsolved problem is the 1st question: there are now multiple possible paths to a leaf node. Let's split the question into two possible satisfactory results:
Is there a way, using a single or fixed number of JOINs in a single or fixed number of queries, for an arbitrarily deep tree, to get either:
The Canonical path
This is the path with the least number of nodes leftmost in the tree representation to a specific leaf node. Note that it is not necessarily true that the tree is 'sorted' like the example in the data structure, as nodes are arbitrarily inserted and removed.
All paths
Gets all possible paths to a specific leaf node.
An illustrative example of why the naïve method fails:
Consider this tree:
1
2 3
5 4
6 6
Asking the question "What are the ascendants of 6" should have two logical answers:
1-2-5-6 and 1-3-4-6.
Yet, using the naïve query and sorting, we can only really get:
1-2-4-6 or 1-3-5-6.
Which are both not actually valid paths.
In all of the tutorials about closure tables I've read, it's plainly stated that closure tables are capable of handling hierarchies where the same item appears in multiple places, but it's never actually explained how to properly do this, just 'left to the reader'. I run into nontrivial problems trying to, however.

MySql add relationships without creating dupes

I created a table (t_subject) like this
| id | description | enabled |
|----|-------------|---------|
| 1 | a | 1 |
| 2 | b | 1 |
| 3 | c | 1 |
And another table (t_place) like this
| id | description | enabled |
|----|-------------|---------|
| 1 | d | 1 |
| 2 | e | 1 |
| 3 | f | 1 |
Right now data from t_subject is used for each of t_place records, to show HTML dropdowns, with all the results from t_subject.
So I simply do
SELECT * FROM t_subject WHERE enabled = 1
Now just for one of t_place records, one record from t_subject should be hidden.
I don't want to simply delete it with javascript, since I want to be able to customize all of the dropdowns if anything changes.
So the first thing I though was to add a place_id column to t_subject.
But this means I have to duplicate all of t_subject records, I would have 3 of each, except one that would have 2.
Is there any way to avoid this??
I thought adding an id_exclusion column to t_subject so I could duplicate records only whenever a record is excluded from another id from t_place.
How bad would that be?? This way I would have no duplicates, so far.
Hope all of this makes sense.
While you only need to exclude one course, I would still recommend setting up a full 'place-course' association. You essentially have a many-to-many relationship, despite not explicitly linking your tables.
I would recommend an additional 'bridging' or 'associative entity' table to represent which courses are offered at which places. This new table would have two columns - one foreign key for the ID of t_subject, and one for the ID of t_place.
For example (t_place_course):
| place_id | course_id |
|----------|-----------|
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 2 | 1 |
| 2 | 2 |
| 2 | 3 |
| 3 | 1 |
| 3 | 3 |
As you can see in my example above, place 3 doesn't offer course 2.
From here, you can simply query all of the courses available for a place by querying the place_id:
SELECT * from t_place_course WHERE place_id = 3
The above will return both courses 1 and 3.
You can optionally use a JOIN to get the other information about the course or place, such as the description:
SELECT `t_course`.`description`
FROM `t_course`
INNER JOIN `t_place_course`
ON `t_course`.`id` = `t_place_course`.`course_id`
INNER JOIN `t_place`
ON `t_place`.`id` = `place_id`

MySQL Storing different nested sets in same table

I have a table that stores nested sets. It stores different nested sets differentiated by a collectionid (yes i'm mixing terms here, really should be nestedsetid). it looks somewhat like this:
id | orgid | leftedge | rightedge | level | collectionid
1 | 123 | 1 | 6 | 1 | 1
2 | 111 | 2 | 3 | 2 | 1
3 | 23 | 4 | 5 | 2 | 1
4 | 67 | 1 | 2 | 1 | 2
5 | 123 | 3 | 4 | 1 | 2
6 | 600 | 1 | 6 | 1 | 3
7 | 11 | 2 | 5 | 2 | 3
8 | 111 | 3 | 4 | 3 | 3
Originally I wanted to take advantage of the R-Tree Indexes, but the code i have seen for this: LineString(Point(-1, leftedge), Point(1, rightedge)) won't quite work since it doesn't take into account the collectionid and this id:1 and id:6 would end up being the same.
Is there a way I can use the R-Tree index with my current set up... Surely you can have different nested sets in the same table? My main aim is to be able to use the MBRWithin and MBRContains functions. Using MySQL 5.1
For single-dimensional data (these are 1d intervals, right?) there exist better index structures than r-trees. These are designed for dynamic data in 2-10 dimensions (at higher dimensions, performance isn't too good, as the split strategies and distance functions don't work very well anymore)
Actually for your use case, classic SQL should work very well. And the database can make use of its indexes efficiently. Having a good index structure is one thing, but you want to have the database exploit the indexes it has as good as possible.
As such, I'd just index leftEdge and rightEdge and the <, <=, >, >= functions. They are fast! And for the collectionid column, a bitmap index should be good.

Joining from another table multiple times in a MySQL query

I am trying to do multiple joins on the same MySQL table, but am not getting the results that I expect to get. Hopefully someone can point out my mistake(s).
Table 1 - cpe Table
|id | name
|----------
| 1 | cat
| 2 | dog
| 3 | mouse
| 4 | snake
-----------
Table 2 - AutoSelect
|id | name | cpe1_id | cpe2_id | cpe3_id |
|-----------------------------------------------
| 1 | user1 | 1 | 3 | 4 |
| 2 | user2 | 3 | 1 | 2 |
| 3 | user3 | 3 | 3 | 2 |
| 4 | user4 | 4 | 2 | 1 |
------------------------------------------------
I would like to see an output of
user1 | cat | mouse | snake |
user2 | mouse | snake | dog |
..etc
Here is what I have tried
SELECT * FROM AutoSelect
LEFT JOIN cpe ON
( cpe.id = AutoSelect.cpe1_id ) AND
( cpe.id = AutoSelect.cpe2_id ) AND
( cpe.id = AutoSelect.cpe3_id )
I get blank results. I thought i knew how to do these joins, but apparently when I'm trying to match cpe?_id with the name of the cpe table.
Thanks in advance for any assistance.
You need left join 3 times as well. Currently your query only joins 1 time with 3 critieria as to the join. This should do:
SELECT a.name, cpe1.name, cpe2.name, cpe3.name FROM AutoSelect as a
LEFT JOIN cpe as cpe1 ON ( cpe1.id = a.cpe1_id )
LEFT JOIN cpe as cpe2 ON ( cpe2.id = a.cpe2_id )
LEFT JOIN cpe as cpe3 ON ( cpe3.id = a.cpe3_id )
And you probably mean to INNER JOIN rather than LEFT JOIN unless NULL values are allowed in your AutoSelect table.
I think your design is wrong.
With tables like that, you get it the way it's meant to be in relational databases :
table 1 : animal
id name
1 cat
2 dog
3 mouse
4 snake
table 2 : user
|id | name |
|--------------
| 1 | user1 |
| 2 | user2 |
| 3 | user3 |
| 4 | user4 |
table 3 : association
|id_user | id_animal|
|--------------------
| 1 | 1 |
| 1 | 3 |
| 1 | 4 |
| 2 | 3 |
| 2 | 1 |
| 2 | 2 |
| 3 | 3 |
| 3 | 2
| 4 | 4 |
| 4 | 2 |
| 4 | 1 |
---------------------
Then :
select u.name, a.name from user u, animal a, association ass where ass.user_id = u.id and ass.animal_id = a.id;
In this case, your solution won't produce a good dynamic database. There are other ways to make combinations of multiple tables. I can show you by my own database what you should use and when you should use this solution. The scheme is in dutch, but you'll probably understand the keywords.
Like you, I had to combine my windmills with a kWh-meter, which has to measure the energyproduction of my windmills. What you should do, is this case, is making another table(in my case molenkWhlink). Make sure your tables are INNODB-types(for making Foreign keys). What I've done is combining my meters and mills by putting a pointer(a foreign key) of their ID(in Dutch Volgnummer) in the new table. An advantage you may not need, but I certainly did, is the fact I was able to extend the extra table with connection and disconnection info like Timestamps and metervalues when linking or unlinking. This makes your database way more dynamic.
In my case, I Also had a table for meassurements(metingoverzicht). As you can see in the scheme, I've got 2 lines going from Metingoverzicht to molenkwhlink. The reason for this is quite simple. All meassurements I take, will be saved in table Metingoverzicht. Daily meassurements(which are scheduled) will have a special boolean put on, but unscheduled meassurements, will also me saved here, with the bollean turned off. When switching meters, I need the endvalue from the leaving meter and the startvalue from the new meter, to calculate the value of todays eneryproduction. This is where your solution comes in and an extra table won't work. Usually, when you need just one value from another table a JOIN will be used. The problem in this case is, I've got 2 meassurementIDs in 1 link(1 for connecting and 1 for disconnecting). They both point to the same tablecolumn, because they both need to hold the same type of information. That is when you can use a double JOIN from one table towards the other. Because, both values will only be used once, and just needed to be saved in a different place to avoid having 1 action stored on different locations, which should always be avoided.
http://s1101.photobucket.com/user/Manuel_Barcelona/media/schemedatabase.jpg.html