In the above tree each node has a name and value. Each node can have 6 children at max. How to store it in MySQL database to perform the below operations efficiently?
Operations
1) grandValue(node) - should give (sum of all of the descendants' values, including self)
Eg.,
grandValue(C) = 300
grandValue(I) = 950
grandValue(A) = 3100
2) children(node) - should give the list of all children (immediate descendants only)
Eg.,
children(C) = null
children(I) = L,M,N
children(A) = B,C,D,E
3) family(node) - should give the list of descendants
family(C) = null
family(I) = L,M,N
family(A) = B,C,D,E,F,G,H,I,J,K,L,M,N
4) parent(node) - should give the parent of the node
parent(C) = A
parent(I) = D
parent(A) = null
5) insert(parent, node, value) - should insert node as a child of parent
insert(C, X, 500) Insert a node name X with value 500 as C's child
I am thinking of using recursive methods to do these manipulations as we do with binary trees. But I am not sure if that's the optimal way to do it. The tree may hold 10 to 30 million nodes and maybe skewed. So dumping the data into memory stack is my area of concern.
Please help.
NOTE: I am using PHP, MySQL, Laravel, on VPS Machine.
UPDATE: Tree will grow in size. New nodes will be added as a child of leaf nodes or nodes which has less than 6 nodes and not in-between 2 nodes.
You could store the data in a table using nested sets.
http://en.wikipedia.org/wiki/Nested_set_model#Example
I worry that your millions of nodes may make life difficult if you intend to constantly add new items. Perhaps that concern could be mitigated by using rational numbers instead of integers as the left and right values. Add a column for depth to speed up your desire to ask for descendants. I wrote some SQL to create the table and the stored procedures you asked for. I did it in SQL Server do the syntax might be slightly different but it's all standard SQL statements being executed. Also I just manually decided the upper and lower bounds for each Node. Obviously you'd have to deal with writing the code to get these nodes inserted (and maintained) in your database.
CREATE TABLE Tree(
Node nvarchar(10) NOT NULL,
Value int NOT NULL,
L int NOT NULL,
R int NOT NULL,
Depth int NOT NULL,
);
INSERT INTO Tree (Node, Value, L, R, Depth) VALUES ('A', 100, 1, 28, 0);
INSERT INTO Tree (Node, Value, L, R, Depth) VALUES ('B', 100, 2, 3, 1);
INSERT INTO Tree (Node, Value, L, R, Depth) VALUES ('C', 300, 4, 5, 1);
INSERT INTO Tree (Node, Value, L, R, Depth) VALUES ('D', 150, 6, 25, 1);
INSERT INTO Tree (Node, Value, L, R, Depth) VALUES ('E', 200, 26, 27, 1);
INSERT INTO Tree (Node, Value, L, R, Depth) VALUES ('F', 400, 7, 8, 2);
INSERT INTO Tree (Node, Value, L, R, Depth) VALUES ('G', 250, 9, 10, 2);
INSERT INTO Tree (Node, Value, L, R, Depth) VALUES ('H', 500, 11, 12, 2);
INSERT INTO Tree (Node, Value, L, R, Depth) VALUES ('I', 350, 13, 21, 2);
INSERT INTO Tree (Node, Value, L, R, Depth) VALUES ('J', 100, 21, 22, 2);
INSERT INTO Tree (Node, Value, L, R, Depth) VALUES ('K', 50, 23, 24, 2);
INSERT INTO Tree (Node, Value, L, R, Depth) VALUES ('L', 100, 14, 15, 3);
INSERT INTO Tree (Node, Value, L, R, Depth) VALUES ('M', 300, 16, 17, 3);
INSERT INTO Tree (Node, Value, L, R, Depth) VALUES ('N', 200, 18, 19, 3);
CREATE PROCEDURE grandValue
#Node NVARCHAR(10)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #lbound INT;
DECLARE #ubound INT;
SELECT #lbound = L, #ubound = R FROM Tree WHERE Node = #Node
SELECT SUM(Value) AS Total FROM TREE WHERE L >= #lbound AND R <= #ubound
RETURN
END;
EXECUTE grandValue 'C';
EXECUTE grandValue 'I';
EXECUTE grandValue 'A';
CREATE PROCEDURE children
#Node NVARCHAR(10)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #lbound INT;
DECLARE #ubound INT;
DECLARE #depth INT;
SELECT #lbound = L, #ubound = R, #depth=Depth FROM Tree WHERE Node = #Node
SELECT Node FROM TREE WHERE L > #lbound AND R < #ubound AND Depth = (#depth + 1)
RETURN
END;
EXECUTE children 'C';
EXECUTE children 'I';
EXECUTE children 'A';
CREATE PROCEDURE family
#Node NVARCHAR(10)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #lbound INT;
DECLARE #ubound INT;
SELECT #lbound = L, #ubound = R FROM Tree WHERE Node = #Node
SELECT Node FROM TREE WHERE L > #lbound AND R < #ubound
RETURN
END;
EXECUTE family 'C';
EXECUTE family 'I';
EXECUTE family 'A';
CREATE PROCEDURE parent
#Node NVARCHAR(10)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #lbound INT;
DECLARE #ubound INT;
DECLARE #depth INT;
SELECT #lbound = L, #ubound = R, #depth = Depth FROM Tree WHERE Node = #Node
SELECT Node FROM TREE WHERE L < #lbound AND R > #ubound AND Depth = (#depth - 1)
RETURN
END;
EXECUTE parent 'C';
EXECUTE parent 'I';
EXECUTE parent 'A';
CREATE PROCEDURE ancestor
#Node NVARCHAR(10)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #lbound INT;
DECLARE #ubound INT;
SELECT #lbound = L, #ubound = R FROM Tree WHERE Node = #Node
SELECT Node FROM TREE WHERE L < #lbound AND R > #ubound
RETURN
END;
EXECUTE ancestor 'C';
EXECUTE ancestor 'I';
EXECUTE ancestor 'A';
For creating the nested sets in the table in the first place you can run some code to generate the inserts or start with the first node and then successively add each additional node - although since each add potentially modifies many of the nodes in the set there can be a lot of thrashing of the database as you build this.
Here's a stored procedure for adding a node as a child of another node:
CREATE PROCEDURE insertNode
#ParentNode NVARCHAR(10), #NewNodeName NVARCHAR(10), #NewNodeValue INT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #ubound INT;
DECLARE #depth INT;
SELECT #ubound = R, #depth = Depth FROM Tree WHERE Node = #ParentNode
UPDATE Tree SET L = L + 2 WHERE L >= #ubound
UPDATE Tree SET R = R + 2 WHERE R >= #ubound
INSERT INTO Tree (Node, Value, L, R, Depth) VALUES (#NewNodeName, #NewNodeValue, #ubound, #ubound + 1, #depth + 1);
RETURN
END;
I got this from http://www.evanpetersen.com/item/nested-sets.html who also shows a nice graph walking algorithm for creating the initial L and R values. You'd have to enhance this to keep track of the depth as well but that's be easy.
Related
I want display selected table with quantity adding using stored procedure in php
here is my code
DELIMITER $$ CREATE DEFINER=`root`#`localhost` PROCEDURE `GetCustomerLevel`(IN p_barcode INT, OUT q_value INT) BEGIN
DECLARE q1 int; DECLARE q2 int;
DECLARE q3 int; DECLARE total int;
SET total :=0;
SELECT coalesce(sum(adjustment_quantity), 0) INTO q1 FROM adjustment_inventory WHERE item_barcode = p_barcode; SELECT coalesce(sum(opening_stock), 0) INTO q2 FROM openingstock WHERE item_barcode = p_barcode; SELECT coalesce(sum(inwardquantity), 0) INTO q3 FROM inwardmaster WHERE item_barcode = p_barcode; set total = q1+q2+q3; set q_value = total;
END$$ DELIMITER ;
When i call the particular data display but i want display selected table and if adding quantity display only positive and 0 not minus value show in php
I have a table with a lot of columns and I want to select all columns but I want unique one of these columns.
This works for me but I don't get all columns:
$result = mysql_query("SELECT DISTINCT company FROM table t $order");
I also tested this but doesn't do anything:
$result = mysql_query("SELECT * DISTINCT company FROM table t $order");
$result = mysql_query("SELECT DISTINCT company * FROM table t $order");
EDIT
My table has a lot of columns let's say it has 5 so an example of my records is this:
company x y price numOfEmpl
one 1 5 1.3 15
one 2 6 1.4 15
two 3 7 1.5 16
three 4 8 1.6 17
So I want to cut the second line and take all the others.
The DISTINCT keyword can be used to return only distinct (different) values within defined columns, like
SELECT DISTINCT column_name,column_name
or you can return the amount of DISTINCT values by
SELECT COUNT(DISTINCT column_name)
See some samples on W3Schools
I think you might need to use a seperate sql query for all the records
Edited previous answer based on extra information
Have a solution for you in MySQL and in SQL Server if you need it
MySQL Example (Using User Variables/Sub-Queries)
CREATE TABLE SomeTable (company VARCHAR(20), x INT, y INT, price FLOAT, numOfEmploy INT);
INSERT INTO SomeTable
(company, x, y, price, numOfEmploy)
VALUES
('one', 1, 5, 1.3, 15),
('one', 1, 6, 1.4, 15),
('two', 1, 7, 1.5, 16),
('three', 1, 8, 1.6, 17);
SET #count = NULL, #value = NULL;
SELECT company, x, y, price, numOfEmploy FROM (
SELECT
company, x, y, price, numOfEmploy,
#count := IF(#value = company, #count + 1, 1) AS rc,
#value := company
FROM SomeTable
) AS grouped_companies WHERE rc = 1
SQL Server Example (Using CTE)
--Create the table
declare #sometable table ( company varchar(10), x int, y int, price float, numOfEmploy int)
--insert the data
insert into #sometable values ('one', 1, 5, 1.3, 15)
insert into #sometable values ('one', 2, 6, 1.4, 15)
insert into #sometable values ('two', 3, 7, 1.5, 16)
insert into #sometable values ('three', 4, 8, 1.6, 17)
--WITH Common Table Expression
;WITH grouped_companies AS (
SELECT *,
ROW_NUMBER() OVER(PARTITION BY company
ORDER BY company) AS rc
FROM #sometable)
SELECT gc.company, gc.x, gc.y, gc.price, gc.numOfEmploy
FROM grouped_companies gc
WHERE gc.rc = 1
So I have an imploded array in a mysql table that is basically just a sequence of numbers (ex. 1,5,3,1,4,5) and I was wondering if it was possible to order them by the average (or even the sum if that were possible) of the sets of numbers in the table
Instead of storing your numbers in a delimited string, take advantage of MySQL's relational capabilities and normalise your data: store your numbers as (key, value) pairs in a separate table that relates a foreign key (i.e. that of your existing table) to a single number in the list. If order is important, include an additional column to indicate the number's position within the list.
CREATE TABLE `newtable` (
`key` INT,
`value` INT,
FOREIGN KEY (`key`) REFERENCES `existingtable` (`key`)
)
Then you need only join the tables together, GROUP BY the key in your existing table and ORDER BY the AVG() or SUM() of the values in the new table; you can even reclaim the comma-separated list if so desired using MySQL's GROUP_CONCAT() function:
SELECT `existingtable`.*, GROUP_CONCAT(`newtable`.`value`) AS `values`
FROM `existingtable` LEFT JOIN `newtable` USING (`key`)
GROUP BY `key`
ORDER BY AVG(`newtable`.`value`) -- or use SUM() if preferred
As others have mentioned, it's not clear what your table looks like, or the data in you table looks like. It's not at all clear what an "imploded array" looks like. This is where an EXAMPLE would really help you get the answer you are looking for.
But let's go with the "worst case" here, and assume you've got a string containing those values you want to average. Like this:
CREATE TABLE foo (str VARCHAR(1000));
INSERT INTO foo VALUES ('1,5,3,1,4,5');
INSERT INTO foo VALUES ('2.8,4.2');
INSERT INTO foo VALUES ('bar');
INSERT INTO foo VALUES (' 0, -2, 3');
It's possible to create a function that returns an average of the values in your "imploded array" string. As an example:
DELIMITER $$
CREATE FUNCTION `do_average`(p_str VARCHAR(2000))
RETURNS DOUBLE
BEGIN
DECLARE c INT;
DECLARE i INT;
DECLARE s VARCHAR(2000);
DECLARE v DOUBLE;
SET c = 0;
SET v = 0;
SET s = CONCAT(p_str,',');
SET i = INSTR(s,',');
WHILE i > 0 DO
SET v = v + SUBSTR(s,1,i);
SET c = c + 1;
SET s = SUBSTR(s,i+1,2000);
SET i = INSTR(s,',');
END WHILE;
RETURN v / NULLIF(c,0);
END$$
DELIMITER ;
We can demonstrate this working:
SELECT f.str
, do_average(f.str) AS davg
FROM foo f
ORDER BY do_average(f.str)
str davg
----------- -------------------
bar 0
0, -2, 3 0.333333333333333
1,5,3,1,4,5 3.16666666666667
2.8,4.2 3.5
Note that with this function, we're using MySQL's implicit conversion of strings to numbers, so empty strings, or invalid numbers are going to be converted to zero, and that zero is going to get added and counted in computing the average.
A do_sum function would be nearly identical, just return the total rather than the total divided by the count.
sqlfiddle example here http://sqlfiddle.com/#!2/4391b/2/0
I'm looking for the simplest way to recursively get all the parent elements from a database using the adjacency list / single table inheritance model (id, parent_id).
My select currently looks like this:
$sql = "SELECT
e.id,
TIME_FORMAT(e.start_time, '%H:%i') AS start_time,
$title AS title,
$description AS description,
$type AS type,
$place_name AS place_name,
p.parent_id AS place_parent_id,
p.city AS place_city,
p.country AS place_country
FROM event AS e
LEFT JOIN place AS p ON p.id = e.place_id
LEFT JOIN event_type AS et ON et.id = e.event_type_id
WHERE e.day_id = '$day_id'
AND e.private_flag = 0
ORDER BY start_time";
Each event is linked to a place, and each place can be a child of another place (upto about 5 levels deep)
Is this possible in a single select with mysql?
At the moment I am thinking it could be a separate function which loops through the returned $events array, adding place_parent_X elements as it goes, but am not sure how to implement this.
It's possible to do it in MySQL, but you'll need to create a function and use in in a query.
See this entry in my blog for detailed explanations:
Hierarchical queries in MySQL
Here are the function and the query:
CREATE FUNCTION hierarchy_connect_by_parent_eq_prior_id(value INT) RETURNS INT
NOT DETERMINISTIC
READS SQL DATA
BEGIN
DECLARE _id INT;
DECLARE _parent INT;
DECLARE _next INT;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET #id = NULL;
SET _parent = #id;
SET _id = -1;
IF #id IS NULL THEN
RETURN NULL;
END IF;
LOOP
SELECT MIN(id)
INTO #id
FROM place
WHERE parent = _parent
AND id > _id;
IF #id IS NOT NULL OR _parent = #start_with THEN
SET #level = #level + 1;
RETURN #id;
END IF;
SET #level := #level - 1;
SELECT id, parent
INTO _id, _parent
FROM place
WHERE id = _parent;
END LOOP;
END
SELECT id, parent
FROM (
SELECT hierarchy_connect_by_parent_eq_prior_id(id) AS id, #level AS level
FROM (
SELECT #start_with := 0,
#id := #start_with,
#level := 0
) vars, t_hierarchy
WHERE #id IS NOT NULL
) ho
JOIN place hi
ON hi.id = ho.id
The latter query will select all descendants of a given node (which you should set in the #start_with variable)
To find all ancestors of a given node you can use a simple query without functions:
SELECT #r AS _id,
#r := (
SELECT parent
FROM place
WHERE id = _id
) AS parent
FROM (
SELECT #r := #node_id
) vars,
place
This article in my blog described this query in more detail:
Sorting lists
For both these solutions to work in reasonable time, you need to have the indexes on both id and parent.
Make sure your id is defined as a PRIMARY KEY and you have a seconday index on parent.
It's not possible with the standard parent-child DB design.
However, you could use a nested set approach and do it in one query, though that will take quite a bit of work to get to that point.
I've got the following query to determine how many votes a story has received:
SELECT s_id, s_title, s_time, (s_time-now()) AS s_timediff,
(
(SELECT COUNT(*) FROM s_ups WHERE stories.q_id=s_ups.s_id) -
(SELECT COUNT(*) FROM s_downs WHERE stories.s_id=s_downs.s_id)
) AS votes
FROM stories
I'd like to apply the following mathematical function to it for upcoming stories (I think it's what reddit uses) -
http://redflavor.com/reddit.cf.algorithm.png
I can perform the function on the application side (which I'm doing now), but I can't sort it by the ranking which the function provides.
Any advise?
Try this:
SELECT s_id, s_title, log10(Z) + (Y * s_timediff)/45000 AS redditfunction
FROM (
SELECT stories.s_id, stories.s_title, stories.s_time,
stories.s_time - now() AS s_timediff,
count(s_ups.s_id) - count(s_downs.s_id) as X,
if(X>0,1,if(x<0,-1,0)) as Y,
if(abs(x)>=1,abs(x),1) as Z
FROM stories
LEFT JOIN s_ups ON stories.q_id=s_ups.s_id
LEFT JOIN s_downs ON stories.s_id=s_downs.s_id
GROUP BY stories.s_id
) as derived_table1
You might need to check this statement if it works with your datasets.
y and z are the tricky ones. You want a specific return based on x's value. That sounds like a good reason to make a function.
http://dev.mysql.com/doc/refman/5.0/en/if-statement.html
You should make 1 function for y and one for z. pass in x, and expect a number back out.
DELIMINATOR //
CREATE FUNCTION y_element(x INT)
RETURNS INT
BEGIN
DECLARE y INT;
IF x > 0 SET y = 1;
ELSEIF x = 0 SET y = 0;
ELSEIF x < 0 SET y = -1;
END IF;
RETURN y;
END //;
DELIMINATOR;
There is y. I did it by hand without checking so you may have to fix a few typo's.
Do z the same way, and then you have all of the values for your final function.