How properly SELECT several one-to-many rows? - php

We have 3 tables.
1) Order
PK
order_id
2) Order_Products
PK | FK | Column | Column
order_product_id | order_id | product_id | name | quantity
3) Order_Product_Components
PK | FK | Column | Column | Column
order_product_component_id | order_product_id | stock_id | name | quantity
Tables 2 and 3 have one-to-many relationship.
So if we want to get an array of order products with their components what is the best query we can use?
Finally the result should be this format (if we have just 1 product with 2 components):
$products = array(
array(
' 'product_id' => x,
'name' => x,
'quantity' => x,
'stocks' => array(
array(
'stock_id' => x,
'name' => x,
'quantity' => x),
array(
'stock_id' => x,
'name' => x,
'quantity' => x)));

Since order is a reserved word I have changed the order table name to 'orders'. I am using join to get the expected results. I think group by would not be as useful to get the result the way you want it.
$sql= "select * from orders o join order_products op on o.order_id=op.order_id";
$stmt = $db->prepare($sql);
$stmt->execute();
$orders =$stmt->fetchAll();
foreach($orders as $o){
$sql= "select stock_id,name,quantity from order_products op
join order_product_components opc on opc.order_product_id=op.order_product_id where op.order_product_id=:orderprodid";
$stmt = $db->prepare($sql);
$stmt->execute(array('orderprodid'=>$o['order_product_id']));
$comps =$stmt->fetchAll();
$o['stocks']=$comps;
}

You may not want to do a loop of queries, which can be very expensive.
Just do a query joining all tables and order by product id,product name, ... stock id, stock name,... and loop through the results and build your array in one pass.

Related

Parse through a nested array and save it in mysql db with parent id

I have a nested array like the following:-
array(
array(
'id' => 45cfeteuid536hjj929,
'name' = 'Cuisines',
'children' => array(
array(
'id' => 45gcfeteuid536hjj929,
'name' = 'Itlaian',
),
array(
'id' => 45bjfe78ng5d536hjj92,
'name' = 'Indian',
'children' => array(
array(
'id' => 457hfe78ng5d53ghy56j,
'name' = 'Punjabi'
)
)
)
)
)
);
I have a table like this:-
|--------------------------------|
| id | name | parent_id |
|--------------------------------|
I want data to be inserted like this:-
|---------------------------------------------------------------|
| id | name | parent_id | api_id |
|---------------------------------------------------------------|
| 1 | Cuisines | 0 | 45cfeteuid536hjj929 |
|---------------------------------------------------------------|
| 2 | Italian | 1 | 45gcfeteuid536hjj929 |
|---------------------------------------------------------------|
| 3 | Indian | 1 | 45bjfe78ng5d536hjj92 |
|---------------------------------------------------------------|
| 4 | Punjabi | 3 | 457hfe78ng5d53ghy56j |
|---------------------------------------------------------------|
The parent_id of the child is the id of the object to which it belongs. The id of the table are autoincrement value generated automatically by the mysql db.
For example:-
Step 1: While saving Cuisine, the id (autoincrement in nature) saved
is 1. Since it is the root, hence parent_id = 0.
Step 2: While saving Italian, the id (autoincrement in nature)
saved is 1. Since it is a child of Cuisines, the parent_id = 1
How can I save the nested array in such way?
One way would be to generate insert statements that look like this (assuming your table is called tab):
insert into tab(name, parent_id, app_id)
select 'Cuisines', coalesce(min(id), 0), '45cfeteuid536hjj929' from tab
where app_id = '';
insert into tab(name, parent_id, app_id)
select 'Itlaian', coalesce(min(id), 0), '45gcfeteuid536hjj929' from tab
where app_id = '45cfeteuid536hjj929';
insert into tab(name, parent_id, app_id)
select 'Indian', coalesce(min(id), 0), '45bjfe78ng5d536hjj92' from tab
where app_id = '45cfeteuid536hjj929';
insert into tab(name, parent_id, app_id)
select 'Punjabi', coalesce(min(id), 0), '457hfe78ng5d53ghy56j' from tab
where app_id = '45bjfe78ng5d536hjj92';
These queries will find the parent ID via the app_id that parent should have. No value for the id column is inserted, as it is assumed that the database will generate it upon insertion.
Here is a recursive PHP function which generates those statements. You'll have to adapt it to actually execute those statements with whatever API you use (PDO, mysqli, ...):
function insertRecords($data, $parent = "") {
foreach($data as $row) {
// Replace next echo statement with the code to actually execute this SQL:
echo "insert into tab(name, parent_id, app_id) select '$row[name]', coalesce(min(id), 0), '$row[id]' from tab where app_id = '$parent';\n";
if (isset($row["children"])) insertRecords($row["children"], $row["id"]);
}
}
Assuming your nested data structure is stored in variable $data, call the above function like this:
insertRecords($data);

How to write a dynamic number of Eloquent queries in Laravel 5?

(This question is building on my other question here)
There are four tables in my database:
Users (columns id, dept_id)
Departments (columns id, deptStringName)
Absences (columns id, user_id, type_id)
Absence_types (columns id, stringName)
At the moment there are 10 rows in the Departments table and 12 rows in the Absence_types table.
I'm trying to get a query that outputs ten tables, one for each department, with the types of absences and their counts next to the name, e.g., for the IT department:
+-----------+---+
| Sickness | 4 |
| Paternity | 7 |
| Maternity | 3 |
| ... | 6 |
+-----------+---+
I know the query to get these results. However, I'm wondering what is the best practice from a Software Engineering standpoint: do I hard-code the values in the WHERE clause as I've done (dept.id = 1) in the query below?
SELECT COUNT(abs.id) as AbsenceCount , absence_types.description FROM Absences abs JOIN Users u on u.id = abs.user_id JOIN Departments dept on dept.id = u.dept_id JOIN Absence_types on Absence_types.id = abs.type_id WHERE dept.id = 1 group by dept.description
Or do I use some other way to get the ID of a department? I can't think of a way in which I could write a Laravel script that would know how many departments there are and then run one query each per department.
EDIT: Example result could be like the one below (ideally), with A, B, C Absence Types for X, Y, Z Departments
+---+---+---+---+
| | A | B | C |
+---+---+---+---+
| X | 4 | 8 | 5 |
| Y | 7 | 9 | 4 |
| Z | 5 | | |
+---+---+---+---+
Try this way..
Initialize the table by getting all departments and all absence types:
$departments = DB::table('Departments')->pluck('deptStringName');
$absenceTypes = DB::table('Absence_types')->pluck('stringName');
$row = [];
foreach ($absenceTypes as $absenceType) {
$row[$absenceType] = 0;
}
$table = [];
foreach ($departments as $department) {
$table[$department] = $row;
}
This should create a 2D array like:
[
'X' => [
'A' => 0,
'B' => 0,
'C' => 0,
],
// ...
]
You can use NULL or an empty string instead of 0 if you like.
Now modify your query a bit. You need to group by departments and absence types.
$data = DB::select('
SELECT
d.deptStringName as department,
t.stringName as absenceType,
COUNT(a.id) as absenceCount
FROM Absences a
JOIN Users u ON u.id = a.user_id
JOIN Departments d ON d.id = u.dept_id
JOIN Absence_types t ON t.id = a.type_id
GROUP BY d.deptStringName, t.stringName
');
And fill the $table with values fron the query:
foreach ($data as $row) {
$table[$row->department][$row->absenceType] = $row->absenceCount
}
Now the $table shlould look like
[
'X' => [
'A' => 4,
'B' => 8,
'C' => 5,
],
'Y' => [
'A' => 7,
'B' => 9,
'C' => 4,
],
'Z' => [
'A' => 5,
'B' => 0,
'C' => 0,
],
]

Trying to group up results of a SQL multi table query

alright best way for me to explain is by giving an example
Table 1
ID | name | class
-------------------------
1 | first name | a
2 | second name | b
Table 2
ID | name_id | meta_key | meta_value |
-------------------------------------------------------
1 | 1 | image | someImage.jpg |
2 | 1 | description | very long description |
3 | 2 | image | someImage_2.jpg |
4 | 2 | description | very long description 2 |
I am trying to select name and class from Table 1, and also all the records from Table 2 that has the same name_id as ID (in Table 1).
Here is the query i have now:
SELECT
table1.name,
table1.class,
table2.name_id,
table2.meta_key,
table2.meta_value
FROM
table1
LEFT JOIN
table2 ON ( table1.ID = table2.name_id )
WHERE
table1.ID = '2'
This works and return an array with as many elements as the Table 2 entries that has name_id = 2
Is there a way to return an array with 1 the result from first table, and another array with results from the second table ?
Update:
The ideal results will be:
$result['table1'][0] = array('name' => 'second name',
'class' => 'b')
$result['table2'][0] = array('name_id' => 2,
'meta_key' => 'image',
'meta_value' => 'someImage_2.jpg')
$result['table2'][1] = array('name_id' => 2,
'meta_key' => 'description',
'meta_value' => 'very long description 2')
hopes that makes sense.
I'd split the result array AFTER fetching it from the database...
//Assuming your result-set is assigned to $result:
$table1_keys = array('id', 'name', 'class');
$table2_keys = array('id', 'name_id', 'meta_key', 'meta_value');
$table1_results = array();
$table2_results = array();
foreach($result as $key => $val) {
if(in_array($key, $table1_keys)) {
$table1_results[] = $val;
}
if(in_array($key, $table2_keys)) {
$table2_results[] = $val;
}
}
After this, your result-set should be split into two arrays: $table1_results and $table2_results.
Hope this helps you.
I don't believe there's a way to do it in SQL but I'd label each table so it can be done generically in PHP.
SELECT
':table1',
table1.name,
table1.class,
':table2',
table2.name_id,
table2.meta_key,
table2.meta_value
FROM ...
Although personally I'd just do this.
SELECT 'table1', table1.*, ':table2', table2.*
FROM ...
That makes it easier to duplicate entire rows between client and server and reduces maintenance when you decide to add a new field.
However in your query you are duplicating table1 for each row returned so perhaps this would be better.
SELECT
table1.*,
table2a.meta_value AS image,
table2b.meta_value AS description
FROM
table1
LEFT JOIN
table2 AS table2a ON ( table1.ID = table2a.name_id AND table2a.meta_key = 'image' )
LEFT JOIN
table2 AS table2b ON ( table1.ID = table2b.name_id AND table2b.meta_key = 'description' )
WHERE
table1.ID = '2'

How can you count matches from a join action in CakePHP?

I want to count to an extra field created, the matches from a join. Is this possible in CakePHP?
I have an example of my data that I curently have.
And how would a query look in mySQL for this type of result?
Table:goal
id | name
-----------
1 Goal X
2 Goal Y
Table: tasks
id | name | goal_id
-------------------
1 task1 1
2 task2 1
3 task3 2
4 task4 2
5 task5 2
Result
id | name | matches
-------------------
1 goal1 2
2 goal2 3
MySQL query :
SELECT goal.id, goal.name, Count( * ) AS matches
FROM goal
RIGHT JOIN task ON goal.id = task.goal_id
GROUP BY goal.id
CakePHP : [if you have model with name Goal and Task]
$options['fields'] = array(
'Goal.id',
'Goal.name',
'count(*) AS matches'
);
$options['joins'] = array(
array(
'table' => 'tasks',
'alias' => 'Task',
'type' => 'Right',
'conditions' => array(
'Goal.id = Task.goal_id'
)
)
);
$options['group'] = array('Goal.id');
$result = $this->Goal->find('all', $options);
mysql should be that:
Select goal.id,goal.name,Count(*) From goal RIGHT JOIN tasks on goal.id=tasks.goal_id Group by goal.id
CakePHP, can't tell you without testing...

Use array as table in MySql JOIN

I would like to use data from an array to add a column and make a join on a MySql table.
Let's say, on one hand, we have an array ($relevance):
$relevance = array(
array('product_id' => 1, 'relevance' => 2),
array('product_id' => 2, 'relevance' => 5),
array('product_id' => 3, 'relevance' => 1),
);
And on the other hand, we have this table (products):
product_id | product_name
--------------------------
1 | Product 1
2 | Product 2
3 | Product 3
Now, I want to select data from the products table and joining them with $relevance based on their product_id in order to get something like this:
product_id | product_name | relevance
---------------------------------------
1 | Product 1 | 2
2 | Product 2 | 5
3 | Product 3 | 1
In other words, how can I make a SELECT with LEFT JOIN using data from both the MySql database and an array which would "mean" something like this:
SELECT `p`.*, `{{$relevance}}`.* FROM `products` AS `p`
LEFT JOIN `{{$relevance}}`
ON p.product_id = {{$relevance}}.product_id
pure sql solution, not efficient though for big recordsets:
$relevances = array()
foreach ($relevance as $v){
$relevances[] = "SELECT {$v['product_id']} as product_id, {$v['relevance']} as relevance"
}
$sql = implode(' UNION ', $relevances);
$sql = "SELECT p.product_id, p.product_name, r.relevance
FROM products p
JOIN ($sql) r ON p.product_id=r.product_id";
Well, you can make another table relevance and then you could just use JOIN. Or you can use loop to get those data. Something like
$relevance = array(
array(1, 'relevance' => 2),
array(2, 'relevance' => 5),
array(3, 'relevance' => 1),
);
$q = mysql_query("SELECT * FROM products")
while($r = mysql_fetch_assoc($q))
{
$productRelevance = $relevance[$r['prod_id']-1];
}
Hoewever this code may fail if you delete some product and those ids wouldn' be in order, e.g.: 1,2,5,6,7,10. I recommend you to use another table.

Categories