I am building a project with an organization chart using Codeigniter + MySQL + Active Record.
There are departments listed as organization tree, Staff for information of the persons, Staff Roles and Staff_Departments where i store the matching:
Department - Staff - Role
You can see the structure below:
Departments (parent_id is used to build the tree)
Staff (raw Staff information)
Staff Roles (the lowest the weight, the highest in hierarchy)
Staff Departments (In which department - Who - What Role)
In a later phase, a Staff will probably belong in 2 or more departments with different roles. That's why i used a separate table Staff_departments for many-to-many. In this case, let's keep it simple and assume that 1 Staff belongs to 1 Department.
What i am trying to do:
A Manager(role weight=0 || role_id=1) in a Department, can view the Staff(Employees AND Supervisors) who work in his Department AND all the Staff(Employees AND Supervisors) from the Departments that are Children of his Department. The depth of the tree is unknown.
A Supervisor can view the Staff(only Employees) who work only in his Department.
An Employee can only view himself.
For Supervisors and Employees, the process is simple, so i think i am ok with that. For Managers probably i have to do something recursive, but i'm struggling every time i'm starting writing some lines of code.
My idea is to have a function find_related_staff($staff_id) {} in my controller, which i will pass the ID of the Staff who is logged in and it will return an array with the IDs of his related Staff. The only thing i got, is the ID of the Staff who is logged in.
If Manager return IDs of Managers,Supervisors and Employees related in his Department AND Managers,Supervisors and Employees from
Child Departments of his Department.
If Supervisor return IDs of Supervisors and Employees related in his Department only.
If Employee return his ID
Any idea on how to achieve that?
Ok, I think that, in order to make things easier to understand, we need to break your problem into small pieces (and I'm only focus on the section that you say you really need help: the managers' recursion).
First, we get the current department associated with the user authenticated. As you've said, you only have the ID of the staff currently sign so we'll start with that. Let's say the user id is assigned to variable $user_id.
$user_department = $this->db->get_where('staff_departments', ['staff_id' => $user_id])->row();
Now that we have the department, we check to see what's the role of the user in that department. We'll add that info to the $user_department object:
$user_department->role = $this->db->get_where('staff_roles', ['role_id' => $user_department->role_id])->row();
Let's check the weight of the user's role, shall we? If it's 0, we know it's a manager on that department so we'll recursively find the nested departments and their staff information. As per your logic dictates, we can check, here, if the user is a supervisor, also, and escalate if necessary. Like this:
if ($user_department->role->role_weight <= 1) {
// the user is a supervisor OR a manager, but both those can see, at least, the current department's staff information
$user_department->staff = $this->db->get_where('staff_departments', ['department_id' => $user_department->department_id]);
// now is the user a manager? If so, let's find nested departments
if ($user_department->role->role_weight === 0) {
$user_department->childs = $this->getChildDepartmentsAndStaffOf($user_department->department_id);
}
}
As you may note, there's a function that'll be called recursively. It must be something along this lines:
public function getChildDepartmentsAndStaffOf($department_id)
{
$child_departments = $this->db->get_where('departments', ['parent_id' => $department_id]);
if (! $child_departments) {
return null;
}
foreach ($child_departments as &$department) {
$department->staff = $this->db->get_where('staff_departments', ['department_id' => $department->department_id]);
$department->childs = $this->getChildDepartmentsAndStaffOf($department->department_id);
}
return $child_departments;
}
Now, you have the structure you want. I know this might be refactored, but I think that is enough to get your answer and point you to the right path.
Hope I've helped a little.
Yes, to get it done you must use recursive procedures. (I'm using MySQL 5.6.19)
I have created some test data before the stored procedures:
Sample data based on your question requirements:
create table departments
(
id int not null primary key auto_increment,
parent_id int,
department_name varchar(100)
);
insert into departments (id,parent_id,department_name)
values
(1,0,'Test A'),
(2,1,'Test B'),
(3,2,'Test C');
create table staff
(
id int not null primary key auto_increment,
ip_address varchar(100),
username varchar(100)
);
insert into staff values
(1,'127.0.0.1','ats'),
(2,'127.0.0.1','admin'),
(3,'127.0.0.1','george'),
(4,'127.0.0.1','jhon')
;
create table staff_roles
(
role_id int not null primary key auto_increment,
role_name varchar(100),
role_height int
);
insert into staff_roles values
(1,'Manager',0),
(2,'Supervisor',1),
(3,'Employee',2)
;
create table staff_departments
(
staff_department_id int not null primary key auto_increment,
department_id int,
staff_id int,
role_id int
);
insert into staff_departments values
(1,1,2,1),
(2,2,1,2),
(3,3,3,3),
(4,3,4,3);
It's time to create the stored procedures:
find_related_staff is the procedure that receives the staff_id parameter, according to that value will find the role_id in staff_departments table.
The variable #result will accumulate the final result as comma separated values.
find_recursive is the procedure that search in child departments and get the staff_id into #result variable;
The procedure code:
delimiter $$
drop procedure if exists find_related_staff$$
create procedure find_related_staff(p_id int)
begin
declare p_role_id int;
declare p_department_id int;
declare p_return varchar(255) default '';
declare p_role varchar(100);
select d.role_id, d.department_id, r.role_name
into p_role_id,p_department_id, p_role
from staff_departments d
inner join staff_roles r on d.role_id = r.role_id
where d.staff_id = p_id
limit 1;
case p_role_id
when 3 then -- employee (return the same id)
set #result = p_id;
when 2 then -- supervisor
select group_concat(s.staff_id)
into #result
from staff_departments s
where
s.role_id = 3
and s.department_id in
( select d.id
from departments d
where d.parent_id = p_department_id )
and s.role_id <> p_id;
when 1 then -- manager (complex recursive query)
select coalesce(group_concat(s.staff_id),'')
into #result
from staff_departments s
where
s.department_id = p_department_id
and s.staff_id <> p_id;
-- here we go!
call find_recursive(p_department_id);
end case;
select #result as result, p_role as role;
end $$
delimiter ;
delimiter $$
drop procedure if exists find_recursive$$
create procedure find_recursive(p_dept_id int)
begin
declare done int default false;
declare p_department int default false;
declare tmp_result varchar(255) default '';
-- cursor for all depend departments
declare c_departments cursor for
select s.department_id
from staff_departments s
where
s.department_id in
( select d.id
from departments d
where d.parent_id = p_dept_id );
declare continue handler for not found set done = true;
-- getting current departmens
set tmp_result =
(select coalesce(group_concat(s.staff_id),'')
from staff_departments s
where
s.department_id in
( select d.id
from departments d
where d.parent_id = p_dept_id ));
if length(tmp_result) > 0 then
if length(#result) > 0 then
set #result = concat(#result,',',tmp_result);
else
set #result = tmp_result;
end if;
open c_departments;
read_loop: loop
fetch c_departments into p_department;
if done then
leave read_loop;
end if;
call find_recursive(p_department);
end loop;
close c_departments;
end if;
end $$
delimiter ;
Testing:
Important: The max deep in recursion is 0 as default, we must change that value:
SET max_sp_recursion_depth=255;
Now we have the bellow configuration on your staff_departments table:
+---------------------+---------------+----------+---------+
| staff_department_id | department_id | staff_id | role_id |
+---------------------+---------------+----------+---------+
| 1 | 1 | 2 | 1 |
| 2 | 2 | 1 | 2 |
| 3 | 3 | 3 | 3 |
| 4 | 3 | 4 | 3 |
+---------------------+---------------+----------+---------+
Running each case:
call find_related_staff(2);
+--------+---------+
| result | role |
+--------+---------+
| 1,3,4 | Manager |
+--------+---------+
call find_related_staff(1);
+--------+------------+
| result | role |
+--------+------------+
| 3,4 | Supervisor |
+--------+------------+
call find_related_staff(3);
+--------+----------+
| result | role |
+--------+----------+
| 3 | Employee |
+--------+----------+
call find_related_staff(4);
+--------+----------+
| result | role |
+--------+----------+
| 4 | Employee |
+--------+----------+
Enjoy!
I think the most powerfull schema for hierarchical data in a relational database is the transitive-closure-table.
Given your sample data for the departments table:
department_id | parent_id | department_name
--------------|-----------|----------------
1 | 0 | TEST A
2 | 1 | TEST B
3 | 2 | TEST C
Your closure table (let's just call it departments_tree) would be like:
super_id | sub_id
---------|-------
1 | 1
1 | 2
1 | 3
2 | 2
2 | 3
3 | 3
Read it as: super_id = superordinate department_id; sub_id = subordinate department_id.
Assuming the logged-in user is manager of department with department_id = 2, the query to get all "supervised" employees is:
SELECT DISTINCT s.*
FROM departments_tree t
JOIN stuff_departments sd ON sd.department_id = t.sub_id
JOIN staff s ON s.id = sd.staff_id
WHERE t.super_id = 2
You can use triggers to populate and update the closure table.
Insert trigger:
DELIMITER //
CREATE TRIGGER `departments_after_insert` AFTER INSERT ON `departments` FOR EACH ROW BEGIN
INSERT INTO departments_tree (super_id, sub_id)
SELECT new.department_id, new.department_id
UNION ALL
SELECT super_id, new.department_id
FROM departments_tree
WHERE sub_id = new.parent_id;
END//
DELIMITER ;
Delete trigger:
DELIMITER //
CREATE TRIGGER `departments_before_delete` BEFORE DELETE ON `departments` FOR EACH ROW BEGIN
DELETE FROM departments_tree
WHERE sub_id = old.department_id;
END//
DELIMITER ;
Update trigger:
DELIMITER //
CREATE TRIGGER `departments_before_update` BEFORE UPDATE ON `departments` FOR EACH ROW BEGIN
DELETE t
FROM departments_tree p
CROSS JOIN departments_tree c
INNER JOIN departments_tree t
ON t.super_id = p.super_id
AND t.sub_id = c.sub_id
WHERE p.sub_id = old.parent_id
AND c.super_id = new.department_id;
INSERT INTO departments_tree (super_id, sub_id)
SELECT p.super_id, c.sub_id
FROM departments_tree p
CROSS JOIN departments_tree c
WHERE p.sub_id = new.parent_id
AND c.super_id = new.department_id;
END//
Note
You will not need a delete trigger, if you use foreighn keys with ON DELETE CASCADE:
CREATE TABLE `departments_tree` (
`super_id` INT(10) UNSIGNED NOT NULL,
`sub_id` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (`super_id`, `sub_id`),
INDEX `sub_id_super_id` (`sub_id`, `super_id`),
FOREIGN KEY (`super_id`) REFERENCES `departments` (`department_id`) ON DELETE CASCADE,
FOREIGN KEY (`sub_id`) REFERENCES `departments` (`department_id`) ON DELETE CASCADE
);
Note 2
In many implementations of a transitive closure table, you will find a depth or level column.
But you don't need it for the given requirements. And I believe you will never really need it,
as long as you don't try to format tree output in SQL.
I think your main problem is how to traverse down so that will be solved by getRecursiveDepts(). I have not completed the code but you can try something like this
file db.php
class DB {
private $servername = "127.0.0.1";
private $username = "root";
private $password = "root";
private $dbname = "test";
private $port = '3306';
public function getRecursiveDepts($deptIds) {
if (!is_array($deptIds)) {
$deptIds = array($deptIds);
}
$sql = "SELECT id FROM Departments WHERE parentId IN (";
$sql .= implode(', ', $deptIds);
$sql .= ")";
$conn = new mysqli($this->servername, $this->username, $this->password, $this->dbname, $this->port);
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}
$result = $conn->query($sql);
if ($result->num_rows > 0) {
$newDept = array();
while($row = $result->fetch_assoc()) {
array_push($newDept, $row['id']);
}
$conn->close();
$moreDepts = $this->getRecursiveDepts($newDept);
if (is_null($moreDepts)) {
$finalIds = array_unique(array_merge($deptIds, $newDept));
} else {
$finalIds = array_unique(array_merge($deptIds, $newDept, $moreDepts));
}
return $finalIds;
} else {
$conn->close();
return null;
}
}
public function getRoles($empId) {
$sql = "SELECT role_id, department_id FROM staff_departmen_role WHERE staff_id = '$empId' GROUP BY role_id, department_id";
$conn = new mysqli($this->servername, $this->username, $this->password, $this->dbname, $this->port);
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}
$result = $conn->query($sql);
if ($result->num_rows > 0) {
$emp = array();
while($row = $result->fetch_assoc()) {
if (!array_key_exists($row['role_id'], $emp)) {
$emp[$row['role_id']] = array();
}
array_push($emp[$row['role_id']], $row['department_id']);
}
}
$conn->close();
return $emp;
}
public function getEmpDetails($empId) {
$sql = "SELECT role_id, department_id FROM staff_departmen_role WHERE staff_id = '$empId' GROUP BY role_id, department_id";
$conn = new mysqli($this->servername, $this->username, $this->password, $this->dbname, $this->port);
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}
$result = $conn->query($sql);
if ($result->num_rows > 0) {
$emp = array();
while($row = $result->fetch_assoc()) {
if (!array_key_exists($row['role_id'], $emp)) {
$emp[$row['role_id']] = array();
}
array_push($emp[$row['role_id']], $row['department_id']);
}
}
$conn->close();
return $emp;
}
}
file index.php
<?php
include_once 'db.php';
$objDB = new DB();
$empId = 2;
$emps = $objDB->getRoles($empId);
foreach ($emps as $roleId => $deptIds) {
switch ($roleId) {
case 1:
$allDeptIds = $objDB->getRecursiveDepts($deptIds);
break;
case 2://Supervisor GetEmpIds of current dept role >= 2
break;
case 3://Employee GetEmpIds of current dept role >= 2
$emp = $objDB->getEmpDetails($empId);
break;
default:
# code...
break;
}
}
$data = $objDB->getRecursiveDepts($empId);
print_r($data);
?>
Related
I'm trying to search for all the parent category of child category from 2 tables
Sample database
table1 table2
+------------+-------------+ +------------+-------------+
|Categ_name |Categ_parent | |Temp_name |Temp_parent |
+------------+-------------+ +------------+-------------+
|Items |0 | |Cookware |Kitchen |
|House |Item | |Utensils |Cookware |
|Car |Item | +------------+-------------+
|Kitchen |House |
|Living Room |House |
|Appliance |Living Room |
+------------+-------------+
if I search for 'Utensils', hoping to get
Categories :
Cookware
Kitchen
House
Items
as a result but I'm a little confused with SQL.
$i='Utensils';
while($i!='0'){
$sql="SELECT m.Categ_parent AS parent,t.Temp_parent AS parent, FROM table1 AS m,table2 AS t WHERE m.Categ_name='".$i."' OR t.Temp_name='".$i."' LIMIT 1";
$result=mysqli_query($con,$sql);
$row=mysqli_fetch_array($result);
$i=$row['parent'];
echo $i;
}
I'm not getting any result or error from this code
For repeatedly accessing the same table, you might be better off using a stored procedure.
## Create a view so that we can work more easily with the tables
create view items as select Categ_name as name, Categ_parent as parent from table1
union (select Temp_name as name, Temp_parent as parent from table2);
name parent
-------------------------
Item 0
House Item
Car Item
Kitchen House
Living Room House
Appliance Living Room
Cookware Kitchen
Utensils Cookware
DROP PROCEDURE IF EXISTS selectName;
CREATE PROCEDURE selectName(IN currentName VARCHAR(100))
BEGIN
drop temporary table if exists tmp_results;
create temporary table tmp_results (
name varchar(100)
);
while (exists(select parent from items where name = currentName)) DO
select parent into currentName from items where name = currentName;
insert into tmp_results select currentName;
END WHILE;
select * from tmp_results;
END;
To use this procedure:
call selectName('Utensils');
Cookware
Kitchen
House
Item
0
PHP code to initialize database (run the statements only ONCE to create the view and stored procedure):
// create view
$mysqli->query("
drop view if exists viewItems;
create view viewItems as select Categ_name as name, Categ_parent as parent from table1
union (select Temp_name as name, Temp_parent as parent from table2)";
");
// create stored procedure
$mysqli->query("
DROP PROCEDURE IF EXISTS selectParents;
CREATE PROCEDURE selectParents(IN currentName VARCHAR(100))
BEGIN
drop temporary table if exists tmp_results;
create temporary table tmp_results (
name varchar(100)
);
while (exists(select parent from viewItems where name = currentName)) DO
select parent into currentName from viewItems where name = currentName;
insert into tmp_results select currentName;
END WHILE;
select * from tmp_results;
END"
);
PHP code to query
$name = mysqli_real_escape_string($name);
$results = $mysqli->query("CALL selectParents($name)");
at first the SQL you wrote has an error which is the "," before "from ...".
I suggest using UNION instead of the current SQL command. The current command will mix two parent values and one of which may not be right.
Try the following solution, which works good on my computer:
$i='Utensils';
while($i!='0'){
$sql="(SELECT Categ_parent as parent from table1 WHERE Categ_name='".$i."')
UNION
(SELECT Temp_parent as parent from table2 where Temp_name='".$i."')";
$result = mysqli_query($con,$sql);
if($result && mysqli_num_rows($result)){
$row = mysqli_fetch_array($result);
$i = $row["parent"];
}
else{
$i = '0';
}
echo $i."<br>";
}
I have a query in mySQL
SELECT id FROM admin_products;
which return a list of ids, like so
+------+
| id |
+------+
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
+------+
And I was using PHP to dynamically generate tables like
vendor_1, vendor_2, vendor_3, vendor_4, vendor_5
Now I want to write a query to retrieve the price and quantity from the table id
For example
"ENTER QUERY HERE"
Should retrieve
+-----------------------------+
| id | price | quantity |
+-----------------------------+
| 1 | 23| 13| // price and quantity retrieved from table vendor_1 since id=1
| 2 | 158| 85| // price and quantity retrieved from table vendor_2 since id=2
| 3 | 15| 7| // price and quantity retrieved from table vendor_3 since id=3
| 4 | 112| 9| // price and quantity retrieved from table vendor_4 since id=4
| 5 | 123| 199| // price and quantity retrieved from table vendor_5 since id=5
+-----------------------------+
What I'm doing now in PHP is:
$conn = mysqli_connect($server,$user,$pwd,$db);
$sql = "SELECT id FROM admin_products";
$res = mysqli_query($conn,$sql);
if(mysqli_num_rows($res)>0){
while($row = mysqli_fetch_assoc($res)){
$product = array();
$innerSQL = "SELECT price,quantity FROM vendor_".$row['id'];
$innerRes = mysqli_query($conn,$innerSQL);
if(mysqli_num_rows($innerRes)>0){
while($innerRow = mysqli_fetch_assoc($innerRes)){
array_push($product,$row['id']);
array_push($product,$innerRow['price']);
array_push($product,$innerRow['quantity']);
}
}
}
}
But it takes two hits to the mySQL database. Can't it be reduced to one?
EDIT
I have later on realized that my database structure was incorrect and dynamically creating tables is a very bad idea and could spell disaster later on
-Solution 1:
Note: This will only work if you have in your vendor_x tables id for the vendor id to match them with. (As Strawberry said, this is a terrible idea to dynamically generate tables).
After selecting the correct id you can do something like this:
connect to the MySql Server
Then you can create the table name and store it in a variable.
$tableName = 'vendor_' . $id;
I would suggest after that to have a check if the table exists with a simple query:
$sql = "SHOW TABLES LIKE '$tableName'";
If this returns empty result you can throw an exception that the table does not exist or handle it whatsoever way you would like.
After checking every table, to be sure it exists, you can create your query.
$joins = "";
$sql = "
SELECT
v.id,
price,
quantity
FROM
vendors AS v
";
foreach ($ids as $id) {
$tableName = "vendor_" . $id;
$tableAlias = "v".$id;
$joins .= " LEFT JOIN " . $tableName . " AS ". $tableAlias ."
ON (v.id = ". $tableAlias .".vendor_id) ";
}
$sql .= $joins;
Then execute the query.
-Solution 2:
Create only one table to manage your vendors. It should have a structure like this :
`id` // AI value
`vendor_id` // The id of the vendor to easily join it afterwards
`price`
`quantity`
You can name it something like vendor_product or whatsoever
And now you have only one simple query:
$sql = "
SELECT
v.id,
vp.quantity,
vp.price
FROM
vendors AS v
LEFT JOIN vendor_product AS vp
ON (vp.vendor_id = v.id)
";
EDIT for the comment about the structure:
You will need one table for the vendors, such so:
`vendor`:
`id`, //AI value
`username`,
`password` // I suggest to you not to keep it in plain text.
`vendor_product` :
`id`, //AI value
`vendor_id`,
`price`,
`quantity`
I don't know here if you are going to store more information about each product, but this should do the trick.
How to show the product with least price ?
You need to match them by somehow and group by that selecting minimum price.
Try this if it suits
$table = "vendor"."_".$id; // this will create table name if $id = 1 then $table = vendor_1;
mysqli_query($connect , "SELECT * FROM $table");
2nd
If you want to fetch data of all table at once then
1) fetch id from admin_products and store in an array like
$ids = array(1,2,3,4,5);
2) Now loop throw array and create sql;
$sql = "SELECT * FROM ";
$ids = array(1,2,3,4,5);
foreach($ids as $id){
$table = "vendor"."_".$id; // this will create table name if $id = 1 then $table = vendor_1;
$sql .=" $table,";
}
$sql = rtrim($sql,",");// this will trim the last comma
echo $sql;
// output SELECT * FROM vendor_1, vendor_2, vendor_3, vendor_4, vendor_5
I need some help with an MySQL statement that I cannot really make to work.
I have a table like this:
+---+-------------------------------+
|id | fruit |
+---+-------------------------------+
| 1 | apple,orange,pear,grape,lemon |
| 2 | pear,melon,apple,kiwi,lemon |
| 3 | orange,kiwi,pear,apple,cherry |
| 4 | grape,lemon,cherry,apple,melon|
+---+-------------------------------+
What I need to do is to SELECT all rows where the column fruit contains the word melon. The word in question might be at any position in the array.
I tired with the below query but for some reason I only get 3-4 rows, definitely not all of them:
$fruit = $_GET['fruit'];
$query1= "SELECT * FROM tbl_fruits WHERE ".$fruit." IN (fruit)";
Any comments will be appreciated.
You can use FIND_IN_SET
SELECT * FROM tbl_fruits
WHERE find_in_set('$fruit', fruit)
But you actually should rather change your table design.
Never store multiple values in a single column!
A better table design would be
fruits table
------------
id name
1 melon
2 orange
3 apple
...
products table
-------------------
id name price
1 P1 1.50
2 P2 2.99
3 P3 0.99
product_fruits table
--------------------
product_id fruit_id
1 1
1 2
2 2
3 1
That is a classic many to many relation (m to n).
$fruit = $_GET['fruit'];
$query1= sprintf("SELECT * FROM tbl_fruits WHERE fruit LIKE '%%s%'",
mysql_real_escape_string($fruit ));
Try below sql
$fruit = $_GET['fruit'];
$query1= "SELECT * FROM tbl_fruits WHERE fruit LIKE '%".$fruit."%'";
Use a LIKE clause:
SELECT * FROM tbl_fruits
WHERE fruit LIKE '%apple%';
select * from tbl_fruits WHERE fruit like '%melon%'
As mentioned before you can/should build your table structure differently.
It's the "relational" in "relational database". see http://en.wikipedia.org/wiki/Database_normalization
As always: not a silver bullet. There are other kinds of daabases and there can be rasons for not normalizing (parts of) tables. But anyway, here's an sscce using PDO:
<?php
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'localonly', 'localonly');
//echo 'client version: ', $pdo->getAttribute(PDO::ATTR_CLIENT_VERSION), "\n";
//echo 'server version: ', $pdo->getAttribute(PDO::ATTR_SERVER_VERSION), "\n";
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
setup($pdo);
$stmt = $pdo->prepare("
SELECT
m.name
FROM
soFruits as f
JOIN
soMixXFruits as x
ON
f.id=x.idFruit
JOIN
soFruitMix as m
ON
x.idMix=m.id
WHERE
f.name=?
");
$stmt->setFetchMode(PDO::FETCH_ASSOC);
foo($stmt, 'apple');
foo($stmt, 'melon');
function foo(PDOStatement $stmt, $param) {
echo "----- $param -----\r\n";
$stmt->execute( array($param) );
foreach($stmt as $r) {
echo join(', ', $r), "\r\n";
}
}
function setup($pdo) {
$queries = array(
'
CREATE TEMPORARY TABLE soFruits (
id INT auto_increment,
name varchar(64),
primary key(id)
)
',
'
CREATE TEMPORARY TABLE soFruitMix (
id INT auto_increment,
name varchar(32),
primary key(id)
)
',
'
CREATE TEMPORARY TABLE soMixXFruits (
idMix int,
idFruit int,
primary key(idMix, idFruit)
)
',
"INSERT INTO soFruits (id,name) VALUES (1,'apple'),(2,'cherry'),(3,'grape'),(4,'kiwi'),(5,'lemon'),(6,'melon'),(7,'orange'),(8,'pear')",
"INSERT INTO soFruitMix (id,name) VALUES (1, 'mix1'),(2,'mix2'),(3,'mix3'),(4,'mix4')",
);
foreach( $queries as $q ) {
$pdo->exec($q);
}
$data = [
'1'=>['apple','orange','pear','grape','lemon'],
'2'=>['pear','melon','apple','kiwi','lemon'],
'3'=>['orange','kiwi','pear','apple','cherry'],
'4'=>['grape','lemon','cherry','apple','melon']
];
$stmt = $pdo->prepare('
INSERT INTO soMixXFruits (idMix, idFruit)
SELECT
:idMix, id
FROM
soFruits
WHERE
name=:nameFruit
');
$stmt->bindParam('idMix', $idMix);
$stmt->bindParam('nameFruit', $nameFruit);
foreach($data as $idMix=>$mix) {
foreach($mix as $nameFruit) {
$stmt->execute();
}
}
}
Take a look at the table definitions in function setup($pdo). You have entities like fruits and mixes and then there's a table that represents their relationship.
side-note: I didn't pay attention to the indicies. But for performance that is usually crucial, see http://www.sitepoint.com/using-explain-to-write-better-mysql-queries/
This question already has answers here:
mysql_fetch_array()/mysql_fetch_assoc()/mysql_fetch_row()/mysql_num_rows etc... expects parameter 1 to be resource
(31 answers)
Closed 9 years ago.
I have three databases in the same server.. They are margoplatinum, detos and plaza.
They also have the same structure of tables. One of the table is called movie.
Here is the table structure of movie table:
id_movie int(11) PK, AI;
movie_title varchar(100);
status varchar(30);
Data from margoplatinum in movie table are:
id_movie | movie_tittle | status
--------------------------------------------------
1 | Frozen | Now Playing
2 | The-Amazing-Spidey2 | Coming Soon
Data from detos in movie table are :
id_movie | movie_tittle | status
--------------------------------------------------
1 | Comic8 | Now Playing
2 | Godzilla | Coming Soon
and data from plaza in movie table are :
id_movie | movie_tittle | status
--------------------------------------------------
1 | The Killer | Now Playing
2 | Godzilla | Coming Soon
So what i want to do is to select from the three databases above (in my php code and convert it into JSON) for table movie where the status = "Now Playing"..
Here is my php code :
<?php
$db_host = "127.0.0.1";
$db_uid = "root";
$db_pass = "";
$db_con = mysql_connect($db_host,$db_uid,$db_pass) or die('could not connect');
$q = mysql_query('SELECT DISTINCT a.id_movie, b.id_movie, c.id_movie, a.movie_tittle,
b.movie_tittle, c.movie_tittle FROM `margoplatinum`.movie a
INNER JOIN `detos`.movie b ON a.id_movie = b.id_movie
INNER JOIN `plaza`.movie c ON b.id_movie = c.id_movie
WHERE a.status = "Now Playing"');
$v = '{"info" : [';
while($r=mysql_fetch_array($q))
{
$ob = array("<br>","<b>","</b>");
if(strlen($v)<12)
{
$v .= '{"id_movie" : "'.$r['id_movie'].'", "movie_tittle" :
"'.$r['movie_tittle'].'"}';
}
else
{
$v .= ',{"id_movie" : "'.$r['id_movie'].'", "movie_tittle" :
"'.$r['movie_tittle'].'"}';
}
}
$v .= ']}';
echo $v;
echo mysql_error();
mysql_close();
?>
But when I execute the php above, an error occured..Here is the error message :
Warning: mysql_fetch_array() expects parameter 1 to be resource, boolean given in C:\xampp\htdocs\CinemaInfo\list_movie.php on line 17
{"info" : []}Unknown column 'a.status' in 'where clause'
Is my query above correct? or i did something wrong with my query..
Can anyone help me to write the correct query based on my case above?
I really appreciate any help from you.
Thank you :)
I think that what you need is a UNION not a JOIN, a join operation will find movies with same ID but different names.
So, I would go for:
As you mentioned that you only want two columns, here is what you need.
select id_movie, movie_tittle
from
(
select id_movie, movie_tittle, status
from `margoplatinum`.movie
UNION
select id_movie, movie_tittle, status
from `detos`.movie
UNION
select id_movie, movie_tittle, status
from `plaza`.movie
) as a
where a.status = 'Now Playing'
First make sure your query is working by running it in mysql query editor directly.
If it is working then there is something wrong with the way query is executed using php.
The below query worked for me
select a.id from `test1`.app_user a
inner join `test2`.app_user r on r.id=a.id
inner join `test`.app_user f on a.id=f.id
where a.Status="ACTIVE"
So, i'm guessing the column name might be different or something like that
Try changing your query for the following:
$q = mysql_query("SELECT DISTINCT a.id_film, b.id_film, c.id_film, a.judul_film,
b.judul_film, c.judul_film FROM `margoplatinum`.film a
INNER JOIN `detos21`.film b ON a.id_film = b.id_film
INNER JOIN `depokplaza21`.film c ON b.id_film = c.id_film
WHERE a.status = 'Now Playing'");
Strings in SQL use simple ' not double "
It would be better to have a single database to handle this. The table structures could be:
CREATE TABLE `movies` (
id_movie INT AUTO_INCREMENT,
movie_title VARCHAR(50),
PRIMARY KEY( id_movie )
);
CREATE TABLE `locations` (
id_movie_fk INT,
location VARCHAR(50),
STATUS VARCHAR(25)
);
I've set up an sqlFiddle example.
Database: name_ allmoney
Table name: MONEY
+-----+--------+------------+---------+---------+
| id | name | date | income | account |
+-----+--------+------------+---------+---------+
| 1 | Ann | 01.01.2012 | 100 | 100 |
| 2 | Matt | 02.01.2012 | 100 | 200 |
| 3 | Tom | 03.01.2012 | 100 | 300 |
| 4 | Peter | 04.01.2012 | 100 | 400 |
+-----+--------+------------+---------+---------+
Hello Stackoverflowers! ;)
I'm newbie for MySQL... Last week I've spend searchin for solution for above situation. I'm tryin to create script summing income column, but not only showing the sum of the column like
SELECT *, SUM(kasa) from MONEY GROUP BY id;
but I have to add new column called account, which has the option like my excel sheet:
accout1st row =income1+account1
accout2nd row =income2+account1
accout3rd row =income3+account2
accout4th row =income4+account3
Is there any code or possibility to create column like account in MySQL or PHP?
CREATE TABLE money (
id INT(11) NOT NULL AUTO_INCREMENT,
date DATE DEFAULT NULL,
income DECIMAL(11,4) DEFAULT NULL,
numer_kluczyka INT(11) DEFAULT NULL,
name CHAR(255) CHARACTER SET utf8 COLLATE utf8_polish_ci DEFAULT NULL,
za_co CHAR(255) CHARACTER SET utf8 COLLATE utf8_polish_ci DEFAULT NULL,
account INT(11) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
You can do it using a user variable -
SELECT #m := 0;
SELECT
id,
name,
date,
income,
#m := #m + income AS balance
FROM money
ORDER BY date ASC, id ASC;
The order by should use the primary key for the table.
Splitting the two queries in your PHP should fix the problem -
<?php
$connection = mysql_connect('host', 'user', 'pass') or die('failed to connect');
mysql_select_db("album", $connection) or die('failed to select db');
mysql_query("SELECT #m:=0;");
$result = mysql_query("SELECT id, imie_i_nazwisko, data_wplaty, kasa, #m := #m + income AS balance FROM money ORDER BY data_wplaty ASC , id ASC");
while($row = mysql_fetch_array($result)) {
echo $row['income'] . " " . $row['balance'];
echo "<br />";
}
If I understand your question correctly, you simply need to store the sum computed so far in your php loop.
Something like:
$account = null;
while(($result = /* read mysql result */)) {
if (is_null($account)) { // Handle first row
$account = $result['income'];
}
// Show first columns
echo $result['id'], $result['income'], $account;
$account += $result['income'];
}
Your question seems a bit unclear, but if I've guessed what you want correctly, you could do this with a subselect that sums the income column over all rows up to the current row:
SELECT id, name, date, income,
( SELECT SUM(income) FROM money AS b
WHERE b.id = a.id
AND b.date <= a.date
) AS balance
FROM money AS a
You could also do the same with a join:
SELECT a.id, a.name, a.date, a.income, SUM(b.income) AS balance
FROM money AS a
JOIN money AS b ON (b.id = a.id AND b.date <= a.date)
GROUP BY a.id, a.name, a.date, a.income
(The GROUP BY is supposed to be on the primary key of the money table. It's not entirely clear from your question what the primary key is supposed to be, since it doesn't seem to be id.)
Also note that both of these answers assume that the date columns are unique for each id and sort in ascending order by time, so that the condition b.date <= a.date can find all rows up to and including the current row. If that's not the case, you should either fix your table so that it is, or adjust the query accordingly.
Edit: S0pra's PHP solution will work too. However, the way I read your question, what you want is to sum the past income for each id separately. If so, you can use S0pra's code with a few modifications:
$balance = array();
while ( $result = mysql_fetch_assoc( $res ) ) { // or equivalent
$id = $result['id'];
if ( array_key_exists( $id, $balance ) ) {
$balance[$id] += $result['income'];
} else {
$balance[$id] = $result['income']; // handle first row for this id
}
$result['balance'] = $balance[$id];
// do something with $result here
}