Complicated MySQL Data Structure/Manipulation Problem - mysql

First off, I apologize for the length. This is kind of complicated (at least for me).
Background on the database:
I have a products, variables, and prices table. "Products" are the main information regarding a product (description, title, etc). "Prices" have information about each price (price, cost, minimum qty required, shipping cost, etc), as some products can have more than one price (a 10" widget is a different price than a 12" widget, for instance). "Variables" are variations to the product that do not change the price, such as color, size, etc.
Initially (when I built this database about 7 years ago) I had the variable information stored in the first price in a list of prices for the same product in a pipe-delimited format (yes, I know, badbadbad). This worked in general, but we've always had a problem, though, where sometimes a variable wouldn't be consistent among all the prices.
For instance, a Widget (product) may be 10" or 12" and sell for $10 and $20 (prices) respectively. However, while the 10" widget may be available in blue and red (variables), the 12" widget is only available in red. We ameliorated this problem by adding a little parenthetical statement in the incongruent variable like "Red (10" ONLY)". This sort of works, but customers are not always that smart and a lot of time is devoted to fixing mistakes when a customer selects a 12" widget in red.
I have since been tasked with modernizing the database and have decided to put the variables in their own table and making them more dynamic and easier to match with certain prices, as well as keep a more dummy-proof inventory (you can't imagine the nightmares).
My first step was to write a stored procedure on my test db (for when I do the conversion) to process all the existing variables into a new variable table (and label table, but that's not really important, I don't think). I effectively parsed out the variables and listed them with the correct product id and the product id they were initially associated with in the variable table. However, I realized this is only a part of the problem, since I (at least for the initial transformation of the database) want each variable to be listed as being connected to each price for a given product.
To do this, I created another table, like so:
tblvariablesprices
variablepriceid | variableid | priceid | productid
which is a many-to-many with the variable table.
Problems:
My problem now is, I don't know how to create the rows. I can create a left join on my prices and variables tables to get (I think) all the necessary data, I just don't know how to go through it. My sql is (mysql 5.0):
SELECT p.priceid, p.productid, variableid, labelid
FROM tblprices p
LEFT JOIN tblvariables v ON p.priceid = v.priceid
ORDER BY productid, priceid
This will get me every priceid and productid and any matching variable and label ids. This is good in certain instances, such as when I have something like:
priceid | productid | variableid | labelid
2 | 7 | 10 | 4
2 | 7 | 11 | 4
2 | 7 | 12 | 4
3 | 7 | (null) | (null) --- another price for product
because now I know that I need to create a record for priceid 2 and variableids 10, 11, 12, and then also for priceid 3 for that product. However, I also get results from this dataset for products with no variables, products with one price and multiple variables, and products with multiple prices and no variables, for instance:
priceid | productid | variableid | labelid
2 | 7 | 10 | 4
2 | 7 | 11 | 4
2 | 7 | 12 | 4
3 | 7 | (null) | (null)
4 | 8 | (null) | (null) --- 1 price no variables
5 | 9 | 13 | 5 --- mult vars, 1 price
5 | 9 | 14 | 5
5 | 9 | 15 | 6
5 | 9 | 16 | 6
6 | 10 | (null) | (null) --- mult price, no vars
7 | 10 | (null) | (null)
8 | 10 | (null) | (null)
Taking the above dataset, I want to add entries into my tblpricesvariables table like so:
variablepriceid | variableid | priceid | productid
1 | 10 | 2 | 7
2 | 11 | 2 | 7
3 | 12 | 2 | 7
4 | 10 | 3 | 7
5 | 11 | 3 | 7
6 | 12 | 3 | 7
7 | 13 | 5 | 9
8 | 14 | 5 | 9
9 | 15 | 5 | 9
10 | 16 | 5 | 9
I have thousands of records to process, so obviously doing this manually is not the answer. Can anyone at least point me in the correct direction, if not come up with a sproc that could handle this type of operation? I also would welcome any comments on how to better organize and/or structure this data.
Thank you so much for reading all this and helping me out.

How about:
SELECT DISTINCT b.variableid, a.priceid, a.productid
FROM tblprices AS a
JOIN tblprices AS b ON a.productid = b.productid
WHERE b.labelid IS NOT NULL
ORDER BY priceid;
+------------+---------+-----------+
| variableid | priceid | productid |
+------------+---------+-----------+
| 10 | 2 | 7 |
| 11 | 2 | 7 |
| 12 | 2 | 7 |
| 10 | 3 | 7 |
| 11 | 3 | 7 |
| 12 | 3 | 7 |
| 13 | 5 | 9 |
| 14 | 5 | 9 |
| 15 | 5 | 9 |
| 16 | 5 | 9 |
+------------+---------+-----------+
INSERTing into tblvariables is left as an exercise for the reader ;)

I think this should work:
SELECT v.variableid, p.productid, p.priceid
FROM tblvariables v, tblprices p
WHERE v.priceid IN (SELECT s.priceid
FROM tblprices s
WHERE s.productid = p.productid);
Next time, can you throw in create and insert statements to replicate your setup? Thanks.

Related

Mysql - Get season from current month

I have the following table of seasons:
| id | name | start_month | end_month |
------------------------------------------
| 101 | Summer | 12 | 2 |
| 102 | Winter | 6 | 8 |
| 103 | Spring | 9 | 11 |
| 104 | Fall | 3 | 5 |
I need to get the season by month. Say current month is 2 (February), I want Summer to be the output.
I can get other seasons to work by simply having the where condition start_month >= 4 and end_month <= 4. But this won't work with Summer since the season crosses into next year.
What do I have to do to handle the case of Summer?
One solution I thought was to use dates instead of month number like 1980-12-01 and use between function but it gets a bit complicated for the user end.
It'd be great if it could work with just month numbers.
You could do:
(month(d) between start_month and end_month) or
(start_month>end_month and (month(d)>=start_month or month(d)<=end_month))
See db-fiddle

Median of multiple columns in MySql

How do you get the median of a row in MySQL?
I have a table which gives monthly stock for a series of categories:
cat_id | mar_stk | feb_stk | jan_stk
1 | 5 | 7 | 9
2 | 2 | 1 | 3
3 | 6 | 8 | 10
I need the median, maximum and minimum stock for each category.
Currently have minimum and maximum using:
SELECT
cat_id,
GREATEST(mar_stk, feb_stk, jan_stk) AS max_stk,
LEAST(mar_stk, feb_stk, jan_stk) AS min_stk
FROM example_table
Which leaves me with:
cat_id | max_stk | min_stk
1 | 9 | 5
2 | 3 | 1
3 | 10 | 6
But I can't find any straightforward way to find the median.
By statistics, Median is the middle number in a given out distribution. For instance if in the column cat_id where you have value 1,2,3 etc. Your median is 2 since its the number or value at the middle. Query the middle value and then hurray. Give me a shout if you still need further guide. ..Sectona

Self join and recursive selection in a table

Assuming a table as below
| ID | NAME | ROLE | MGRID |
---------------------------
| 1 | ONE | 5 | 5 |
| 2 | TWO | 5 | 5 |
| 3 | THREE | 5 | 6 |
| 4 | FOUR | 5 | 6 |
| 5 | FIVE | 15 | 7 |
| 6 | SIX | 25 | 8 |
| 7 | SEVEN | 25 | 7 |
| 8 | EIGHT | 5 | 8 |
How do I get a list of all employees reporting to an employee, including the ones who are in subsequent reporting levels below?
I mean, given emp id 5, I should get [1, 2] and given 7, I should get [1, 2, 5, 7]. How do I get this done?
Will self joins be of help here? Need to brush up my knowledge on joins now.
SELECT id
FROM emp
START WITH id = 7
CONNECT BY NOCYCLE mgrid = PRIOR id
SQLFIDDLE LINK
Here is a SQL statement using Oracle.
select id, name, role, mgrID
from employees
start with id = 7
connect by NoCycle prior id = mgrid;
Please note that the manager for employee 7 is the employee 7 - they are their own manager. This will cause an error - "Connect By loop in user data'. By using the NoCycle keyword you can tell Oracle to detect this and avoid the error.
Does this solve your issue?
I know this isn't exactly what you were asking, but if you are willing to choose a finite number of level's to recurse it isn't too bad to write.
SELECT table_2.id
FROM table LEFT JOIN
(table AS table_1 LEFT JOIN table AS table_2 ON table_1.id = table_2.MgrID)
ON table.id = table_1.MgrID
WHERE (((table.id)=7));
ETC.

SQL query to find category and sub category

I have a table which have category_id and parent_category_id. How I can get 1 category and 5 sub category by using SQL query.
Suppose table name is : Category
----------------------------------------------------------------
Category ID | Parent ID | Name
----------------------------------------------------------------
1 | NULL | Electronics
2 | 1 | Computer
3 | 1 | Calculator
4 | 1 | Mobile
5 | NULL | Four Wheeler
6 | 5 | Cars
7 | 5 | Trucks
8 | 5 | Jeep
9 | 5 | Van
Since MySQL does not support recursive queries/CTEs, you will have to emulate that (which is not, say, straightforward).
Here's an excellent tutorial on the subject:
http://explainextended.com/2009/03/17/hierarchical-queries-in-mysql/
I will have the decency of not copying the code here :)
For SQL Server, you can use the WITH query to get the complete path (more here http://msdn.microsoft.com/en-us/library/ms175972.aspx).

Is there a query in MySQL that would allow variable group numbers and limits akin to this

I've checked out a few of the stackoverflow questions and there are similar questions, but didn't quite put my fingers on this one.
If you have a table like this:
uid cat_uid itm_uid
1 1 4
2 1 5
3 2 6
4 2 7
5 3 8
6 3 9
where the uid column in auto_incremented and the cat_uid references a
category of relevance to filter on and the itm_uid values are the one
we're seeking
I would like to get a result set that contains the following sample results:
array (
0 => array (1 => array(4,5)),
1 => array (2 => array(6,7)),
2 => array (3 => array(8,9))
)
An example issue is - select 2 records from each category (however many categories there may be) and make sure they are the last 2 entries by uid in those categories.
I'm not sure how to structure the question to allow an answer, and any hints on a method for the solution would be welcome!
EDIT:
This wasn't a very clear question, so let me extend the scenario to something more tangible.
I have a set of records being entered into categories and I would like to select, with as few queries as possible, the latest 2 records entered per category, so that when I list out the contents of those categories, I will have at least 2 records per category (assuming that there are 2 or more already in the database). A similar query was in place that selected the last 100 records and filtered them into categories, but for small numbers of categories with some being updated faster than others can lead to having the top 100 not consisting of members from every category, so to try to resolve that, I was looking for a way to select 2 records from each category (or N-records assuming it's the same per-category) and for those 2 records to be the last entered. A date field is available to sort on, but the itm_uid itself could be used to indicate inserted order.
SELECT cat_uid, itm_uid,
IF( #cat = cat_uid, #cat_row := #cat_row + 1, #cat_row := 0 ) AS cat_row,
#cat := cat_uid
FROM my_table
JOIN (SELECT #cat_row := 0, #cat := 0) AS init
HAVING cat_row < 2
ORDER BY cat_uid, uid DESC
You will have two extra columns in the results, just ignore them.
This is the logic:
We sort the table by cat_uid, uid descending, then we start from the top and give each row a "row number" (cat_row) we reset this row number to zero whenever cat_uid changes:
---------------------------------------
| uid | cat_uid | itm_uid | cat_row |
| 45 | 4 | 34 | 0 |
| 33 | 4 | 54 | 1 |
| 31 | 4 | 12 | 2 |
| 12 | 4 | 51 | 3 |
| 56 | 6 | 11 | 0 |
| 20 | 6 | 64 | 1 |
| 16 | 6 | 76 | 2 |
| ... | ... | ... | ... |
---------------------------------------
now if we keep only the rows that have cat_row < 2 we get the results we want:
---------------------------------------
| uid | cat_uid | itm_uid | cat_row |
| 45 | 4 | 34 | 0 |
| 33 | 4 | 54 | 1 |
| 56 | 6 | 11 | 0 |
| 20 | 6 | 64 | 1 |
| ... | ... | ... | ... |
---------------------------------------
This is called an adjacent tree model or a parent-child tree model. It's one of the simplier tree model where there is only 1 pointer or 1 leaf. You would solve your query with a recursion or using a Self Join. Sadly MySQL doesn't support recursive queries, maybe it's working with prepared statements. I want to suggest you an Self Join. With a Self Join you can get all the rows from the right side and the left side with a special condition.
select t1.cat_uid, t2.cat_uid, t1.itm_uid, t2.itm_uid From t1 Inner Join t2 On t1.cat_uid = t2.cat_uid