Updating a table based on contents of the same table - mysql

I have a table:
id, number, name, display_name
0001, 1, Category 1, null
0001-0002, 2, Category 2, null
0001-0002-0003, 3, Category 3, null
The id is the full path to the category, the number is just the final category number.
I'd like display_name updated to include the full names from all categories in the path, so they'd end up as
0001, 1, Category 1, Category 1
0001-0002, 2, Category 2, Category 1 > Category 2
0001-0002-0003, 3, Category 3, Category 1 > Category 2 > Category 3
I know I can generate these on the fly by lookups to the number column, but this table doesn't need to change that often but it has a lot of lookups -- it seems wasteful not to just calculate and store the data once. I can do this in php but it's slow and I assume there's a better way to do it? Or perhaps I'm just going about this in completely the wrong way. I realise there's plenty of redundancy in the table... I'm happy for any input.
I got as far as
update categories set display=(select name from (select name from categories where number=1) t) where number=1
but that obviously just copies the name to the display name.

Related

How can I build such a query using Laravel query builder?

There is a table of MySQL "Categories".
The Category model has a "hasMany" relationship that looks like this.
public function children(){
return $this->hasMany(self::class, 'parent_id');
}
On the category editing page, in order to assign the parent category to the current one (for this I need to fill in its parent_id field), I have to generate a Select tag that will contain all rows from the category table, except for the current one and all its child categories.
category_id | parent_id
------------------------
1 | NULL
2 | 1
3 | 2
4 | 3
5 | NULL
6 | 5
For example,
for category_id 1, should be selected lines with category_id [5, 6]
for category_id 2, should be selected lines with category_id [1, 5, 6]
for category_id 3, should be selected lines with category_id [1, 2, 5, 6]
for category_id 4, should be selected lines with category_id [1, 2, 3, 5, 6]
for category_id 5, should be selected lines with category_id [1, 2, 3, 4]
for category_id 6, should be selected lines with category_id [1, 2, 3, 4, 5]
If a category has parent_id is NULL, it means that it has no parent category.
Short answer: you need so select all records EXCEPT recursive records from the current category.
For the recursive part, you could be used this: https://github.com/staudenmeir/laravel-adjacency-list
Look at the descendantsAndSelf() relation it includes by default.
Next you need to build the EXCEPT query. The query builder does not include an except function, so you need to build one yourself.
For example I have this in one of my controllers:
I have an eloquent model called Thread, which is a model for holding message threads. People have different view rights on these threads so I need several queries to filter out the threads they can and cannot see.
I prebuild these separate queries using the element query builder, and some I UNION. Than there is one query which explicitly excludes threads. And than I combine the unioned and $excludeExplicit queries:
$query = Thread::query()
->fromRaw(
'(SELECT * FROM ((' . $unioned->toSql() . ') EXCEPT ' . $excludeExplicit->toSql() . ') AS threads) AS threads',
array_merge($unioned->getBindings(), $excludeExplicit->getBindings())
);
(See https://stackoverflow.com/a/64231210/9258054 )
The trick of this raw query is in the merging of the parameter bindings. Also, the result of this query are Eloquent models. Note that when using queries like UNION or EXCEPT you need to make sure that the SELECT statement of both return the same columns.
After digging around a bit in the code, I found a more elegant solution.
I am making a selection from the database using the "categories" relationship created in the "Article" model
public function categories(){
return $this->morphToMany(Category::class, 'categoryable');
}
In doing so, I only select those items that do not have a parent category.
But thanks to the "categories" relationship, all the children go to the "Article" collection, and the tree of items is built correctly, and I don't have to do complex queries with "Join".
public function topMenu(){
View::composer('layouts.header', function ($view){
$view->with('categories', Category::whereNull('parent_id')->where('published', 1)->get());
});
}

Query for showing stock as of given date in the past

I am developing software for warehouse management.
I got three tables: itemstock, documents, doc_pcs
itemstock fields (not all of them are shown):
rfid_no (which is unique and primary key)
item_name
status
documents fields:
id
doc_date
doc_type
doc_pcs fields:
id
id_doc | these two are primary key
pos | (id_doc is connected to documents.id)
rfid_no (from itemstock.rfid_no)
First table is for collecting records of items, nothing is ever erased from this table, there's just status change when something happens with particular item (goes out to client, comes back etc).
Documents and doc_pcs tables store document data.
doc_type is for marking whether items on document were going in or out of the warehouse.
The problem is:
I need to create query that shows all the stock as of given date.
In other words:
It should show all items from today's itemstock with status=6 ("stored") MINUS records with rfid_no that have matches in doc_pcs related to documents dated between today and a given date and with doc_type="out" PLUS records with rfid_no that have matches in doc_pcs related to documents dated between today and a given date and with doc_type="in"
Was searching for any clue here and there for few days now and I could not find anything that will lead me to any solution. I will be gratefull for any help!
Sample data:
itemstock (as of today):
00300D0909DA, "tshirt", 6
00300D0909DB, "apron", 6
00300D0909DC, "tshirt", 6
00300D0909DD, "trousers", 6
00300D0909DE, "tshirt", 1
00300D0909DF, "trousers", 1
00300D0909E0, "trousers", 6
documents:
0,2015-08-01,"in"
1,2015-08-02,"in"
2,2015-08-03,"out"
3,2015-08-04,"in"
4,2015-08-05,"out"
5,2015-08-06,"in"
doc_pcs:
0, 1, 00300D0909DA
0, 2, 00300D0909DE
1, 1, 00300D0909DF
1, 2, 00300D0909DD
2, 1, 00300D0909DE
3, 1, 00300D0909DB
4, 1, 00300D0909DF
5, 1, 00300D0909DC
5, 2, 00300D0909E0
query results for given doc_date=2015-08-04 including fields rfid_no and item_name should be:
00300D0909DA, "tshirt"
00300D0909DB, "apron"
00300D0909DD, "trousers"
00300D0909DF, "trousers"
EDIT:
I've managed to make following query but it executes endlessly hence I believe something is wrong:
SELECT DISTINCT c.rfid_no
FROM itemstock c LEFT JOIN doc_pcs d
ON c.rfid_no=d.rfid_no
WHERE c.status=6
OR
(d.id IN
(SELECT dk.id
FROM documents dk
WHERE DATE(dk.doc_date)>='2015-08-04'
AND dk.doc_type="out")
AND
d.id NOT IN
(SELECT dk.id
FROM documents dk
WHERE DATE(dk.doc_date)>='2015-08-04'
AND dk.doc_type="in"))
It seems like you could either go from today's values and work backwards or you could start from 'day 0' and work forwards. By storing the stock count as of 'today' you are denormalizing the data so I'd opt for the second approach (and maybe you could save yourself a table).
Assume that as of 'day 0' all stock is empty. To get the stock count as of a given date you would (p-query):
select sum((select count inbound where date <= inventory_date) - (select count outbound where date <= inventory_date))

Grabbing all of the information from a tree type table

I'm trying to extract ids from a table that acts as a category tree. The two main columns in that table are the ID and the PARENT_ID.
The tricky part with this table is that the ID can also be the PARENT_ID for another ID.
I wanted to figure out a way to grab all of the IDs and PARENT_IDs under one "tree" but can only get a couple levels in.
Here's where I'm at:
select *
from categories
where id = 2
or parent_id = 2
or parent in (select id
from categories
where parent_id = 2
or parent_id = id)
I've done something similar to this, but you'll need an extra column in your table, which is a concatenation of the all of its parents up the tree. The table would look like:
ID: ParentID: ID2:
1 1 1
2 1 1_2
3 1 1_3
4 1_2 1_2_4
5 1_3 1_3_5
6 1_3_5 1_3_5_6
The parentID doesn't necessarily have to be the concatenated parent ID, but I find that most useful. ID2 is the complete information of where the item is in the tree. If you want all of the branches below a single point, the query is easy:
select * from `table` where id2 like '1\_2\_%';
will give you all of the items underneath the id2='1_2' item.

Database Normalization For Table With Tree Like Data

How do I Normalize this table. It has a tree like structure which is expected to grow like a tree.
By tree like structure I mean that new students, subjects, levels and chapters will be constantly added or updated or removed
I want to store the result of a quiz in this table. the quiz has multiple subjects under which there are multiple levels under which there are mutliple chapter. and Every students can take different subjects.
So is this table good for storing the results or I need to do something with this table?
In this particular case you need to create several independent tables:
Table "Student"
ID, Name
1, John
2, Jack
Table "Subject"
ID, Name
1, Math
2, Science
3, Geography
4, History
5, English
Table "Levels"
ID, Name
1, Intermediate
2, Moderate
3, Difficult
Table "Chapters"
ID, Name
1, Chapter 1
2, Chapter 2
3, Chapter 3
And so on and so on.
Then you define the relations between the tables, like this:
Table "student_subject_level"
ID, student_id, subject_id, level_id
1, 1, 1, 1 (John, Math, Intermediate)
2, 1, 2, 2 (John, Science, Moderate)
So far you have the student, the corresponding subejct and the subject's level. Since we may have multiple chapters for each level, we need another relation:
Table "student_subject_level_chapter" (or use simpler name)
student_subject_level_id, chapter_id
1, 1 (John, Math, Intermediate, Chapter 1)
1, 2 (John, Math, Intermediate, Chapter 2)
2, 1 (John, Science, Moderate, Chapter 1)
And so on and so on. Start by isolating the individual tables and then figure out how you'd like to achieve the actual relation. Fore each new relation where you have redundant data, you'd like to have new table which keeps the relation you need. It's much easier once you have ID's to refer to, so start with the individual tables and figure your way through.

Getting most similar rows in MySQL table and order them by similarity

I have a database table that holds user's vehicles (cars, motorcycles). I want to get the most similar vehicles out of that table. Lets say the table holds the following columns (with some context to get the idea):
table: vehicles
vehicle_id (pk, auto-increment)
model_id (BMW 3er, Honda Accord)
fuel_type (gasoline, diesel)
body_style (sedan, coupe)
year
engine_size (2.0L)
engine_power (150hp)
So in short I want to select N (usually 3) rows that have the same make_id (at least) and rank them by the amount of similarities they share with the seed vehicle lets say if the fuel_type matches, I'd have rank points +3, but if the body_style matches, it would be +1. Ideally I would get N vehicles that have maximum points but the idea is to still get something when I don't.
As in my table currently I have only around 5k rows and they are slowly growing, I decided to actually use the following simple approach (it came to me just after I wrote the question).
The seed lets say is Honda Accord (model_id 456), 2004, gasoline, 2.0L, 155hp, sedan with auto-inc ID 123.
SELECT vehicles.*,
(IF(`fuel_type`='gasoline', 3, 0) +
IF(`body_style`='sedan', 1, 0) +
IF(`year` > 2001 AND `year` < 2007, 2, 0) +
IF(`engine_size` >= 1.8 AND `engine_size` <= 2.2, 1, 0) +
IF(`engine_power`=155, 3, IF(`engine_power`>124 AND `engine_power`<186, 1, 0))) AS `rank`
FROM vehicles
WHERE vehicle_id!=123 AND model_id=456
ORDER BY `rank` DESC
LIMIT 3
It will work, as long as I don't too many rows. If the table becomes 50-100k, I probably will have to switch to something like Lucene?