Using MySQL, I am trying to randomly assign rows in a child table to point to a row in a parent table, per following:
Parent table: There are 50 WorkGroups (Team Alpha, Team Bravo, etc.), each of which can have a maximum number of WorkEmployees.
Child table: There are 2,000 WorkEmployees (Ann, Bob, Carl, etc.) each of whom needs to be assigned to exactly one WorkGroup at random.
Using MySQL Each WorkEmployee needs to be assigned to exactly one WorkGroup
Each WorkGroup has a specified maximum number of WorkEmployees it can accommodate
There are sufficient slots in the WorkGroups to accommodate all WorkEmployees
I cannot used stored procedures
Following are the table structures and the UPDATE script I have developed for this purpose. The script is not working. Any guidance on what I have done wrong would be very much appreciated. Thank you.
CREATE TABLE WorkGroups ( ID varchar(64), GroupName varchar(64), MaxMembers int );
CREATE TABLE WorkEmployees ( ID varchar(64), EmployeeName varchar(64), WorkGroupFK varchar(64) );
UPDATE WorkEmployees
SET WorkGroupFK = ( SELECT WorkGroups.ID
FROM WorkGroups
WHERE ( SELECT COUNT(*)
FROM (SELECT * FROM WorkEmployees) WorkEmployees2
WHERE WorkEmployees2.WorkGroupFK = WorkGroups.ID )
< WorkGroups.MaxMembers ) /* max capacity */
WHERE WorkEmployees.WorkGroupFK IS NULL /* employee not yet assigned */
;
The problem besides, that it tales tome to make sample data, is that you havn't introduced the random factor and that you can only add 1 workgroup to a emplyoee
So adding a ORDER BY RAND() and a 'LIMIT 1 does the trick
CREATE TABLE WorkGroups ( ID varchar(64), GroupName varchar(64), MaxMembers int );
CREATE TABLE WorkEmployees ( ID varchar(64), EmployeeName varchar(64), WorkGroupFK varchar(64) );
INSERT INTO WorkGroups VALUES(1,'test1',2),(2,'test1',3),(3,'test1',2)
INSERT INTO WorkEmployees VALUES (1,'emp1', NULL),(2,'emp2', NULL),(3,'emp3', NULL),(4,'emp4', NULL)
,(5,'emp5', NULL),(6,'emp6', NULL),(7,'emp7', NULL)
UPDATE WorkEmployees
SET WorkGroupFK = ( SELECT WorkGroups.ID
FROM WorkGroups
WHERE ( SELECT COUNT(*)
FROM (SELECT * FROM WorkEmployees) WorkEmployees2
WHERE WorkEmployees2.WorkGroupFK = WorkGroups.ID )
< WorkGroups.MaxMembers
ORDER BY RAND()
LIMIT 1) /* max capacity */
WHERE WorkEmployees.WorkGroupFK IS NULL /* employee not yet assigned */
;
SELECT * FROM WorkEmployees
ID | EmployeeName | WorkGroupFK
:- | :----------- | :----------
1 | emp1 | 2
2 | emp2 | 2
3 | emp3 | 3
4 | emp4 | 3
5 | emp5 | 1
6 | emp6 | 3
7 | emp7 | 2
db<>fiddle here
Related
I have some parent and daughter design-wise locations-id in the MySQL database.
Where the daughter linked to the parent. I will show the database design below -
I can able to fetch the data when I search it through daughter location id wise but I don't have any idea how I combined the daughter value when I click parent location.
For example -
MainLocation (123) //total stock 23+10+56= 89
|
|
|---- DaughterLoc1 (456) //suppose stock 23
|
|---- DaughterLoc2 (789) //suppose stock 10 and total stock 10+56 = 66
|
|
|---DaughterLocA (963) //suppose stock 56
SQL : SELECT stock FROM table WHERE location = '456'
OUTPUT = 23 (Corrent)
But I want when searching location 123 I want output 89
My table design is like this below -
table: LocParent
-------------------------
| ID | stock | loc_id |
-------------------------
| 1 | 10 | 789 |
-------------------------
`location`
--------------------------------------------------------------------------------
| ID | main_loc | main_loc_id | loc_under | loc_under_id | stock |
--------------------------------------------------------------------------------
| 1 | MainLocation | 123 | DaughterLoc1 | 456 | 23 |
--------------------------------------------------------------------------------
| 2 | MainLocation | 123 | DaughterLoc2 | 789 | 10 |
--------------------------------------------------------------------------------
It is hard to tell from your sample structure what things actually look like still, and it is further complicated by multiple things called an "id". But, generally speaking, if your depth is finite, you can make small sub-queries, and if your depth is infinite (or unbound) you can make a recursive query.
Here is a sample database. It doesn't match yours, but hopefully it make sense still. If it doesn't, it would help if you provided an actual schema and data (excluding irrelevant columns).
This table is self-referencing to make things easier for demo.
CREATE TABLE sample
(
id int AUTO_INCREMENT NOT NULL,
parent_id INT NULL,
stock int NOT NULL,
PRIMARY KEY (`id`),
CONSTRAINT FOREIGN KEY (`parent_id`) REFERENCES `sample` (`id`)
);
And here's some sample data. There are two records that are "root" and don't have parent values (IDs 1 and 5), two child values (IDs 2 and 3) and one grandchild value (ID 4)
INSERT INTO sample VALUES (1, null, 11);
INSERT INTO sample VALUES (2, 1, 22);
INSERT INTO sample VALUES (3, 1, 33);
INSERT INTO sample VALUES (4, 2, 4);
INSERT INTO sample VALUES (5, null, 55);
Finite/bound
If you have a finite/bound depth, you can make use of subqueries like the below. This one goes to a depth of 3 and sums to 70. Hopefully it is fairly easy to read, but I've included a couple of comments.
SELECT
s.id,
s.stock -- root
+
(
(
SELECT
SUM(c.stock) -- child
FROM
sample c
WHERE
c.parent_id = s.id
)
+
(
SELECT
SUM(p.stock) -- grandchild
FROM
sample c
JOIN
sample p
ON
p.parent_id = c.id
WHERE
c.parent_id = s.id
)
)
as three_level_sum
FROM
sample s
WHERE
s.id = 1;
Infinite/unbound
If you have an infinite hierarchy, however, things get more complicated. MySQL and other database platforms have a thing called "Common Table Expressions" (CTEs) that allow you to make recursive queries. These can be harder to wrap your head around because of the recursion, but it basically does the same as the previous version, just with infinite depth. This version also returns the sum of 70.
WITH RECURSIVE sample_rec AS
(
SELECT
id AS root_id,
id,
parent_id,
stock
FROM
sample
WHERE
parent_id IS NULL
UNION ALL
SELECT
R.root_id,
E.id,
E.parent_id,
E.stock
FROM
sample E
INNER JOIN
sample_rec R
ON
E.parent_id = R.id
)
SELECT
SUM(stock)
FROM
sample_rec
WHERE
root_id = 1
I am new to MYSQL and would like to create a table where a constant Letter depicting the department is added to an auto increment number. This way I would be able to identify the category of the worker upon viewing the ID.
Ex. Dept A and employee 135. The ID I am imaging should read A135 or something similar. I have created the table, the auto increment works fine, the constant letter has been declared and is featuring. However I would like to concatenate them in order to use the A135 as a primary key.
Any Help Please?
This quite tricky, and you would be probably better off doing manual concatenation in a select query.
But since you asked for it...
In normal usage you would have used a computed column for this, but they do not support using autoincremented columns in their declaration. So you would need to use triggers:
on insert, query information_schema.tables to retrieve the autoincremented id that is about to be assigned and use it to generate the custom id
on update, reset the custom id
Consider the following table structure:
create table workers (
id int auto_increment primary key,
name varchar(50) not null,
dept varchar(1) not null,
custom_id varchar(12)
);
Here is the trigger for insert:
delimiter //
create trigger trg_workers_insert before insert ON workers
for each row
begin
if new.custom_id is null then
select auto_increment into #nextid
from information_schema.tables
where table_name = 'workers' and table_schema = database();
set new.custom_id = CONCAT(new.dept, lpad(#nextid, 11, 0));
end if;
end
//
delimiter ;
And the trigger for update:
delimiter //
create trigger trg_workers_update before update ON workers
for each row
begin
if new.dept is not null then
set new.custom_id = CONCAT(new.dept, lpad(old.id, 11, 0));
end if;
end
//
delimiter ;
Let's run a couple of inserts for testing:
insert into workers (dept, name) values ('A', 'John');
insert into workers (dept, name) values ('B', 'Jim');
select * from workers;
| id | name | dept | custom_id |
| --- | ---- | ---- | ------------ |
| 1 | John | A | A00000000001 |
| 2 | Jim | B | B00000000002 |
And let's test the update trigger
update workers set dept = 'C' where name = 'Jim';
select * from workers;
| id | name | dept | custom_id |
| --- | ---- | ---- | ------------ |
| 1 | John | A | A00000000001 |
| 2 | Jim | C | C00000000002 |
Demo on DB Fiddle
Sorry, my answer does not fit in a comment.
I agree with #GMB.
This is a tricky situation and in some cases (selects mainly) will lead in a performance risk due you'll have to split PK in where statements, which is not recommended.
Having a column for department and another for auto_increment is more logical. And the only gap you have is to know the number of employees per department you'll have to make a count grouping by dept. Instead of a max() splitting your concatenated PK, which is is at high performance cost.
Let atomic and logic data remain in separate columns. I would suggest to create a third column with the concatenated value.
If, for some company reason, you need B1 and A1 values for employees of different departments, I'd suggest to have 3 columns
Col1 - letter(not null)
Col2 - ID(Not auto-increment, but calculated as #GMB's solution) (Not NULL)
Col3 - Concatenation of Col1 and Col2 (not null)
PK( Col1, col2)
PURCHASE TABLE
id (Primary Key) ItemNo, ItemName, Units, Qty, Location, CPrice,
SPrice, Supplier, PONo
STOCK TABLE
id (Primary Key), ItemNo, Qty, Location
IF EXISTS (SELECT ItemNo, Location FROM stock WHERE stock.ItemNo = 'poitem.ItemNo' AND stock.Location = 'poitem.Location') THEN
UPDATE stock SET stock.Qty = stock.Qty + poitem.Qty WHERE stock.ItemNo = 'poitem.ItemNo';
ELSE
INSERT INTO stock (stock.ItemNo, stock.Qty, stock.Location) SELECT poitem.ItemNo, poitem.Qty, poitem.Location FROM poitem;
END If
THis is my Code, but its not working as it should be
I need to update stock table when i enter a record through purchase table only is the record already exists, otherwise new record need to be entered.
Example:
ItemNo - Qty - Location
1001 - - - 15 - - - A
1001 - - - 12 - - - B
1002 - - - 50 - - - C
1003 - - - 12 - - - A
Can any one please write MYSQL code for this.
Thank you.
Your if exists will always evaluate to true (unless the stock table is empty) so the update will never happen (unless the stock table is empty). I think a procedure is inappropriate here unless you are calling it for every insert. Even if you are a trigger is probably a better bet.
drop trigger if exists t;
delimiter $$
create trigger t after insert on poitems
for each row
begin
IF EXISTS (SELECT ItemNo, Location FROM stock WHERE stock.ItemNo = new.ItemNo AND stock.Location = new.Location) THEN
UPDATE stock SET stock.Qty = stock.Qty + new.Qty WHERE stock.ItemNo = new.ItemNo and stock.Location = new.Location;
ELSE INSERT INTO stock (ItemNo, Qty, Location) values(new.ItemNo, new.Qty, new.Location) ;
END If;
end $$
I have also amended your update statement to match the if exists clause and fixed the insert statement and a few other things which looked wrong.
so given
drop table if exists poitems,stock;
create TABLE poitems(id int auto_increment Primary Key, ItemNo int, ItemName varchar(10), Units int, Qty int, Location int, CPrice int
, SPrice int, Supplier int, PONo int);
create table stock(id int auto_increment Primary Key ,ItemNo int, Qty int, Location int);
insert into poitems(itemno,qty,location) values (1,1,1),(1,1,1),(2,2,1);
results in
+----+--------+------+----------+
| id | ItemNo | Qty | Location |
+----+--------+------+----------+
| 1 | 1 | 2 | 1 |
| 2 | 2 | 2 | 1 |
+----+--------+------+----------+
2 rows in set (0.00 sec)
I am currently trying to manage revisions of a data set in a postgreSql database. The table I would like to use has the following structure:
CREATE TABLE dataset (
id BIGSERIAL PRIMARY KEY,
revision INTEGER NOT NULL,
object_id BIGINT NOT NULL
);
The id field is a unique auto-increment identifier. The object_id should be the identifier for a object, while revision keeps track of the revisions:
id | object_id | revision
-------------------------
1 | 1 | 1
2 | 2 | 1
3 | 1 | 2
4 | 1 | 3
5 | 3 | 1
6 | 4 | 1
What I now need is a function, that:
Sets a auto-increment object_id and sets revision to 1, if no object_id is provided.
Sets a auto-increment revision for this object_id, if an object_id is provided.
I already found this answer, but this does not really solve the problem of creating consecutive revisions for a object_id and it does not solve the problem of auto creating consecutive object_ids.
EDIT:
I would do something like the following, but this doesn't feel very comfortable:
CREATE OR REPLACE FUNCTION update_revision() RETURNS TRIGGER LANGUAGE plpgsql AS
$$
BEGIN
IF tg_op='INSERT' THEN
IF NEW.object_id != NULL THEN
NEW.object_id = SELECT nextval(object_id_seq_id);
NEW.revision = 1;
ELSE
NEW.revision = SELECT MAX(revision)+1 FROM dataset WHERE spot_id = NEW.spot_id;
END IF;
END IF;
RETURN NEW;
END;
$$;
CREATE TRIGGER update_revision BEFORE INSERT OR UPDATE ON dataset
FOR EACH ROW EXECUTE PROCEDURE update_revision();
Make (object_id, revision) unique. BTW why aren't they the primary key?
create table dataset (
id bigserial primary key,
object_id bigint not null,
revision integer not null,
unique (object_id, revision)
);
create or replace function include_revision (_object_id integer)
returns dataset as $$
with object_id as (
select coalesce(max(object_id), 0) + 1 as object_id
from dataset
), revision as (
select coalesce(max(revision), 0) + 1 as revision
from dataset
where object_id = _object_id
)
insert into dataset (object_id, revision)
select
coalesce(_object_id, (select object_id from object_id)),
(select revision from revision)
returning *
;
$$ language sql;
object_id is set to coalesce(_object_id, (select object_id from object_id)), that is, only if _object_id is null it will use the calculated max(object_id)
Testing:
select include_revision(null);
include_revision
------------------
(1,1,1)
select include_revision(1);
include_revision
------------------
(2,1,2)
select include_revision(null);
include_revision
------------------
(3,2,1)
Hi For many days I have been working on this problem in MySQL, however I can not figure it out. Do any of you have suggestions?
Basically, I have a category table with domains like: id, name (name of category), and parent (id of parent of the category).
Example Data:
1 Fruit 0
2 Apple 1
3 pear 1
4 FujiApple 2
5 AusApple 2
6 SydneyAPPLE 5
....
There are many levels, possibly more than 3 levels. I want to create an sql query that groups the datas according to he hierarchy: parent > child > grandchild > etc.
It should output the tree structure, as follows:
1 Fruit 0
^ 2 Apple 1
^ 4 FujiApple 2
- 5 AusApple 2
^ 6 SydneyApple 5
- 3 pear 1
Can I do this using a single SQL query? The alternative, which I tried and does work, is the following:
SELECT * FROM category WHERE parent=0
After this, I loop through the data again, and select the rows where parent=id. This seems like a bad solution. Because it is mySQL, CTEs cannot be used.
You can do it in a single call from php to mysql if you use a stored procedure:
Example calls
mysql> call category_hier(1);
+--------+---------------+---------------+----------------------+-------+
| cat_id | category_name | parent_cat_id | parent_category_name | depth |
+--------+---------------+---------------+----------------------+-------+
| 1 | Location | NULL | NULL | 0 |
| 3 | USA | 1 | Location | 1 |
| 4 | Illinois | 3 | USA | 2 |
| 5 | Chicago | 3 | USA | 2 |
+--------+---------------+---------------+----------------------+-------+
4 rows in set (0.00 sec)
$sql = sprintf("call category_hier(%d)", $id);
Hope this helps :)
Full script
Test table structure:
drop table if exists categories;
create table categories
(
cat_id smallint unsigned not null auto_increment primary key,
name varchar(255) not null,
parent_cat_id smallint unsigned null,
key (parent_cat_id)
)
engine = innodb;
Test data:
insert into categories (name, parent_cat_id) values
('Location',null),
('USA',1),
('Illinois',2),
('Chicago',2),
('Color',null),
('Black',3),
('Red',3);
Procedure:
drop procedure if exists category_hier;
delimiter #
create procedure category_hier
(
in p_cat_id smallint unsigned
)
begin
declare v_done tinyint unsigned default 0;
declare v_depth smallint unsigned default 0;
create temporary table hier(
parent_cat_id smallint unsigned,
cat_id smallint unsigned,
depth smallint unsigned default 0
)engine = memory;
insert into hier select parent_cat_id, cat_id, v_depth from categories where cat_id = p_cat_id;
/* http://dev.mysql.com/doc/refman/5.0/en/temporary-table-problems.html */
create temporary table tmp engine=memory select * from hier;
while not v_done do
if exists( select 1 from categories p inner join hier on p.parent_cat_id = hier.cat_id and hier.depth = v_depth) then
insert into hier
select p.parent_cat_id, p.cat_id, v_depth + 1 from categories p
inner join tmp on p.parent_cat_id = tmp.cat_id and tmp.depth = v_depth;
set v_depth = v_depth + 1;
truncate table tmp;
insert into tmp select * from hier where depth = v_depth;
else
set v_done = 1;
end if;
end while;
select
p.cat_id,
p.name as category_name,
b.cat_id as parent_cat_id,
b.name as parent_category_name,
hier.depth
from
hier
inner join categories p on hier.cat_id = p.cat_id
left outer join categories b on hier.parent_cat_id = b.cat_id
order by
hier.depth, hier.cat_id;
drop temporary table if exists hier;
drop temporary table if exists tmp;
end #
Test runs:
delimiter ;
call category_hier(1);
call category_hier(2);
Some performance testing using Yahoo geoplanet places data
drop table if exists geoplanet_places;
create table geoplanet_places
(
woe_id int unsigned not null,
iso_code varchar(3) not null,
name varchar(255) not null,
lang varchar(8) not null,
place_type varchar(32) not null,
parent_woe_id int unsigned not null,
primary key (woe_id),
key (parent_woe_id)
)
engine=innodb;
mysql> select count(*) from geoplanet_places;
+----------+
| count(*) |
+----------+
| 5653967 |
+----------+
so that's 5.6 million rows (places) in the table let's see how the adjacency list implementation/stored procedure called from php handles that.
1 records fetched with max depth 0 in 0.001921 secs
250 records fetched with max depth 1 in 0.004883 secs
515 records fetched with max depth 1 in 0.006552 secs
822 records fetched with max depth 1 in 0.009568 secs
918 records fetched with max depth 1 in 0.009689 secs
1346 records fetched with max depth 1 in 0.040453 secs
5901 records fetched with max depth 2 in 0.219246 secs
6817 records fetched with max depth 1 in 0.152841 secs
8621 records fetched with max depth 3 in 0.096665 secs
18098 records fetched with max depth 3 in 0.580223 secs
238007 records fetched with max depth 4 in 2.003213 secs
Overall i'm pretty pleased with those cold runtimes as I wouldn't even begin to consider returning tens of thousands of rows of data to my front end but would rather build the tree dynamically fetching only several levels per call. Oh and just incase you were thinking innodb is slower than myisam - the myisam implementation I tested was twice as slow in all counts.
More stuff here : http://pastie.org/1672733
Hope this helps :)
There are two common ways of storing hierarchical data in an RDBMS: adjacency lists (which you are using) and nested sets. There is a very good write-up about these alternatives in Managing Hierarchical Data in MySQL. You can only do what you want in a single query with the nested set model. However, the nested set model makes it more work to update the hierarchical structure, so you need to consider the trade-offs depending on your operational requirements.
You can't achieve this using a single query. Your hierarchical data model is ineffective in this case. I suggest you try two other ways of storing hierarchical data in a database: the MPTT model or the "lineage" model. Using either of those models allows you to do the select you want in a single go.
Here is an article with further details: http://articles.sitepoint.com/article/hierarchical-data-database
The linear way:
I am using a ugly function to create a tree in a simple string field.
/ topic title
/001 message 1
/002 message 2
/002/001 reply to message 2
/002/001/001/ reply to reply
/003 message 3
etc...
the table can be used to select all the rows in the tree order with a simple SQL Query:
select * from morum_messages where m_topic=1234 order by m_linear asc
INSERT is just select the parent linear (and children) and calculate the string as needed.
select M_LINEAR FROM forum_messages WHERE m_topic = 1234 and M_LINEAR LIKE '{0}/___' ORDER BY M_LINEAR DESC limit 0,1
/* {0} - m_linear of the parent message*/
DELETE is simple as delete the message, or delete by linear all replies of the parent one.