I need to be able to store files, directories within MySQL, but I don't know how to do it so I can also also have a fairly efficient query to get data from the table for example /swatcat/superscecret.txt
My initial thought was :
DROP TABLE IF EXISTS objects;
CREATE TABLE objects
(
ID INT NOT NULL AUTO_INCREMENT,
filename VARCHAR(200) NOT NULL,
objectTypeID INT NOT NULL,
parentID INT,
PRIMARY KEY (ID),
FOREIGN KEY (objectTypeID) REFERENCES objectTypes(ID)
);
Object type is either a file or directory:
CREATE TABLE objectTypes
(
ID INT NOT NULL AUTO_INCREMENT,
name VARCHAR(200) NOT NULL UNIQUE,
PRIMARY KEY (ID)
);
My thought process was parentID would be the directory that the file or directory resides in... The issue though is finding /swatcat/supersecret.txt and what if I decide to rename swatcat to test101 then how do I create a query that could cope with it?
Plan A: Have the directory path in a row in a separate table from the filename.
Plan B: Like Plan A, but with the path being multiple rows in a "hierarchical" arrangement in the new table.
Plan C: Leave the "dir/fn" as is, then use LIKE or RLIKE to locate a row, then use SUBSTRING_INDEX (or REGEXP_REPLACE in MariaDB) to substitute.
Related
I have a base table that represents hierarchial data using the nested sets model.
CREATE TABLE trees (
id INT NOT NULL AUTO_INCREMENT,
rootId INT DEFAULT NULL,
lft INT NOT NULL,
rgt INT NOT NULL,
PRIMARY KEY(id),
FOREIGN KEY(rootId) REFERENCES trees(id)
);
So this table can contain multiple, separate trees, where rootId points to the top node of each tree.
Now I would like to add a new table to extend trees with a subtype. For example:
CREATE TABLE happyTrees (
treeId INT NOT NULL,
PRIMARY KEY(treeId),
FOREIGN KEY(treeId) REFERENCES trees(id)
);
Rows inserted into happyTrees should point only to root level nodes in trees -- so rows in trees that have a rootId of null.
I can enforce this at the application level, but I am wondering: Is there a more elegant way to enforce this constraint in sql? So that it is only possible to insert a treeId into happyTrees if the corresponding rootId in trees is null?
I would think about the design in a different way.
Either put the attributes in the trees table (a field is_happy) or create a separate tree_attributes table that references the tree structure.
Because ultimately a "happy" tree structure is still a tree structure.
Although I appreciate this does not create a database enforced constraint when inserting rows. And while I prefer database models that prevent bad data from being inserted in the first place, sometimes simplicity is better.
The following works for you specific situation:
CREATE TABLE trees (
id INT NOT NULL AUTO_INCREMENT,
rootId INT DEFAULT NULL,
lft INT NOT NULL,
rgt INT NOT NULL,
PRIMARY KEY(id),
UNIQUE (rootId, id), -- redundant, but needed for foreign key relationship
FOREIGN KEY(rootId) REFERENCES trees(id)
);
CREATE TABLE happyTrees (
treeId INT NOT NULL,
_rootId INT NULL, -- actually, ALWAYS NULL
PRIMARY KEY(treeId),
FOREIGN KEY(_rootId, treeId) REFERENCES trees(rootId, id);
);
This is adding a dummy column which you want to always be NULL. It can then reference the other table, guaranteeing that happy trees only points to the root.
Whether this is more elegant than doing the work in the application (or, better yet in my opinion, a stored procedure), is a matter of aesthetic tastes. It is more clever, though ;)
Consider the following contrived example where a FOREST contains TREE(s) and TREE(s) have BRANCH(es). Additionally FLOCK(s) contain BIRD(s) and BIRD(s) may or may not be on a BRANCH.
CREATE TABLE 'FOREST' (
forest_id INT(11) NOT NULL AUTO_INCREMENT,
'name' VARCHAR(45) NOT NULL,
...
)
CREATE TABLE 'TREE' (
'tree_id' INT(11) NOT NULL AUTO_INCREMENT,
'forest_id' INT(11) NOT NULL ,
'tree_loc_x' INT NOT NULL,
'tree_loc_y' INT NOT NULL,
...
CONSTRAINT 'fk_tree_forest'
FOREIGN KEY ('forest_id' )
REFERENCES `FOREST` ('forest_id' )
)
CREATE TABLE 'BRANCH' (
'branch_id' INT(11) NOT NULL AUTO_INCREMENT,
'tree_id' INT(11) NOT NULL,
'br_loc_x' INT NOT NULL,
'br_loc_y' INT NOT NULL,
'br_loc_z' INT NOT NULL,
...
CONSTRAINT 'fk_branch_tree'
FOREIGN KEY ('tree_id' )
REFERENCES `TREE` ('tree_id' )
)
CREATE TABLE 'FLOCK' (
'flock_id' INT NOT NULL AUTO_INCREMENT ,
'name' VARCHAR(45) NOT NULL
...
)
CREATE TABLE 'BIRD' (
'bird_id' INT(11) NOT NULL AUTO_INCREMENT ,
'flock_id' INT(11) NOT NULL ,
'branch_id' INT(11) NULL ,
'bird_tag' VARCHAR(45) NOT NULL ,
...
CONSTRAINT 'fk_bird_flock'
FOREIGN KEY ('flock_id' )
REFERENCES 'FLOCK' ('flock_id' )
CONSTRAINT 'fk_bird_branch'
FOREIGN KEY ('branch_id' )
REFERENCES 'BRANCH' ('branch_id' )
)
I want to load each of the tables with a bulk type load (either a multi-insert statement or LOAD DATA INFILE) from a C++ application.
What is the best way to retrieve the database assigned auto_increment values to use as foreign keys for each of the subsequent table loads.
Note that tables cannot necessarily be loaded in a cascading fashion (i.e. BIRDS will be loaded after FLOCKS and not directly after BRANCHES so "LAST_INSERT_ID" would not be useful when loading BIRDS with respect to BRANCHES).
Each of the tables has candidate natural keys but I am trying to avoid using them as primary or foreign keys.
When you use LOAD DATA INFILE (or other multi-row INSERT like INSERT...SELECT), the subsequent call to LAST_INSERT_ID() returns only the first id value generated. But InnoDB allocates id's as a contiguous block, so if you inserted 1000 rows, and LAST_INSERT_ID() reports 1234, then you know that the data load used id's 1234 through 2233.
(Assuming auto_incrment_increment = 1; if not, it's more accurate to say that your data load will use the next 1000 consecutive id's.)
But if you then bulk-load branches after trees, you have the problem of not knowing which tree each branch belongs to. Some trees may have just one branch, others have six, etc. Just because you have 1000 trees and 1000 branches, you can't necessarily assume an even distribution. Then it gets even more complex if you have birds on branches, etc.
Ultimately, bulk-loading data into multiple tables and retaining all the corresponding generated id's is not practical. You really have to write code to loop over all the input files line by line yourself, and capture LAST_INSERT_ID() row by row to be used in dependent rows. I haven't found any better solution.
For the more complex case where birds have foreign keys to both their respective branch and their flock, you can try to keep track of the insert id for each branch, and map that to the natural key, but perhaps there are so many branches that it becomes impractical to keep that mapping in memory. At a certain scale, you find that you can choose to insert birds as you insert either branches or flocks, but not both (because there's a many-to-many relationship between branches and flocks, and you can't assume they sort nicely). So inserting birds may involve leaving NULLs for either their branch or their flock, and then you have to fill in the missing data later with UPDATE.
I'd like to set up the following database scenario:
CREATE TABLE IF NOT EXISTS `points` (
`po_id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`po_north` INT,
`po_east` INT,
PRIMARY KEY (`po_id`),
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS `lines`(
`li_id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`li_from` INT NOT NULL,
`li_to` INT NOT NULL,
PRIMARY KEY (`li_id`),
FOREIGN KEY (`li_from`) REFERENCES points(`po_id`),
FOREIGN KEY (`li_to`) REFERENCES points(`po_id`),
) ENGINE=InnoDB;
Now I want to set up a third table, that sores some metadata like who created or altered a point or a line:
CREATE TABLE IF NOT EXISTS `metadata` (
`me_type` ENUM('point','line') NOT NULL,
`me_type_id` INT UNSIGNED NOT NULL,
`me_permissions` VARCHAR(255) NOT NULL,
`me_created_by` INT UNSIGNED NOT NULL,
`me_created_on` DATETIME NOT NULL,
`me_last_modified_by` INT UNSIGNED NOT NULL,
`me_last_modified_by` DATETIME NOT NULL,
) ENGINE=InnoDB;
My first approach was to set an ENUM with two types (points and lines). But the problem is still, that I cannot properly reference a foreign key to one of the tables. Is there any recommended solution for such problem in MySQL?
BTW:
The fields for me_created_by and me_last_modified_by shall reference to a table storing some user data.
Your case appears to be yet another instance of the design pattern known as "generalization specialization" or perhaps "table design for class inheritance".
If you think of points and lines as classes of objects, they are both subclasses of some more general class of objects. I'm not sure what name to give the superclass in this case. Here's one of several previous questions that address the same issue.
Extending classes in the database
Fowler gives an extensive treatment of the subject. Your case has an added wrinkle, because you are dealing with metadata. But that need not alter the design. You need a third table, which I'll call "Items" for lack of a better term. The key, "it_id" would be assigned an auto number, and you would add an item every time you add either a point or a line. The two columns "po_id" and "li_id" would not be assigned an auto number. Instead they would be foreign keys, referencing "it_id" in the Items table.
The references to points or lines in the metadata table would then be references to "items" and you could use that information to find information about points or lines as the case may be.
How helpful this is depends on what you are trying to do with the metadata.
Your tables points and lines should contain a foreign key to metadata – not the other way around. Doing so will save you from defining any more complicated table setups. Using this approach, a single metadata-entry could be re-used several times for many different points or lines. This isn't even MySQL specific but a general, normalized database structure.
you can do this using a trigger, you need to trigger an event that can create reference key for either point or line before you insert a record based on respective tables
Apparently MySQL have the really annoying restriction of not being able to update a table inside a trigger defined for that same table.
I'm using MySQL version 5.1 and I get the error: "Can't update table in stored function/trigger because it is already used by statement which invoked this function/trigger".
What I have is this:
create table folder(
id int unsigned not null auto_increment PRIMARY KEY ,
name varchar(100) not null ,
parentId int unsigned not null
) ;
It's a hierarchical folder structure. A folder has a name and possibly a parent folder (if not then parentId is zero).
When a folder is deleted I need to change the parentId of all subfolders which were inside it, so that they don't become children of a nonexistent folder.
It's rather simple (almost trivial):
CREATE DEFINER=root#localhost TRIGGER onFolderDelete after delete ON folder
FOR EACH ROW update folder set parentId=0 where parentId=old.id ;
However, such a simple trigger is not allowed by MySQL because, as I said above, you cannot update a table inside its own trigger.
Is there any way of implementing such a trigger by emulating its effects in some way??
P.S.: Please dont suggest sending both statements in sequence (the DELETE and the UPDATE). That's obviously the last solution if nothing else is possible.
Edit:
I'm using MyISAM engine (performance reasons) so I can't use foreign keys.
Can't you add a foreign key with ON DELETE SET NULL (or DEFAULT) ?
UPDATE (DEFAULT is still not implemented;SET NULL is the only option...)
So you will have something like
create table folder(
id int unsigned not null auto_increment PRIMARY KEY ,
name varchar(100) not null ,
parentId int unsigned null ,
FOREIGN KEY(parentId) REFERENCES folder(id) ON UPDATE CASCADE ON DELETE SET NULL
) ;
I'm creating a tree view for a website using databases. I will create 2 tables, let's call them "Folder" and "File". A folder can contain multiple folders and files, and files will act as leaf nodes - which means they're a "dead end" in the tree if you're unfamiliar with the term.
The folder table will contain columns:
Folder_id, Folder_name, Folder_parent
File will contain:
File_id, File_name, File_parent
Obviously File_parent will reference a folder_id, thus creating a foreign key, but how do I make the Folder_parent reference the Folder_id?
Example:
FOLDER_NAME | FOLDER_ID | FOLDER_PARENT
root 1 null
Cars 2 1
Planes 3 1
BMW 4 2
create table folder (
folder_id int primary key,
folder_name varchar not null,
folder_parent int references folder(folder_id)
)
I would make a separate table to deal with relationships, since a file can easily exist in two folders (think aliases). In this schema, you could simplify things with just a node table and a relationships (or edges, if you're familiar with graph theory) table:
CREATE TABLE nodes (
node_id int primary key,
node_name varchar not null,
node_type enum('folder','file')
)
CREATE TABLE edges (
child_node_id int primary key,
parent_node_id int,
unique( child_node_id, parent_node_id)
)
I'm a little rusty on my sql, so my syntax may need some work, but that's how I would approach it. It's much more flexible this way.