(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,
],
]
Related
I have some data in the database that records the stocks of items and I want to group that data based on month-year for every item.
It looks like this for items :
item_id | item |
1 | item_1 |
2 | item_2 |
3 | item_3 |
4 | item_4 |
And for stocks :
stock_id | item_id | date | amount
1 | 1 | 2022-03-01 | 100
2 | 2 | 2022-03-01 | 120
3 | 3 | 2022-03-01 | 100
4 | 4 | 2022-03-01 | 400
5 | 1 | 2022-04-01 | 100
6 | 2 | 2022-04-01 | 120
7 | 3 | 2022-04-01 | 100
8 | 4 | 2022-04-01 | 400
...
What I need to achieve is something like this :
$data = [
[
'y' => '03',
'1' => 100,
'2' => 120,
'3' => 100,
'4' => 400
],
[
'y' => '04',
'1' => 100,
'2' => 120,
'3' => 100,
'4' => 400
],
];
...
A simple query that I've tried so far :
SELECT MONTH(date) as Month, SUM(amount) as Amount, item
FROM stocks as s
LEFT JOIN items as i
ON s.item_id = i.item_id
GROUP BY s.id_item, MONTH(date)
ORDER BY MONTH(date) ASC
DB Fiddle
How to grouping that data by month and sum the amount of every item in every month?
UPDATED PHP SNIPPET
$StockData = [];
foreach ( $StockModel->stockMonthly()->getResult() as $stock )
{
$StockData[] = [
'y' => $stock->Month
];
for ( $i = 0; $i < count($StockData); $i++ )
{
if ( $StockData[$i]['y'] === $stock->Month)
{
array_push($StockData[$i], [$stock->item => $stock->Amount]);
}
}
}
I don't know how to grouping the data into the same month
Nice to see a reproducible example. For this, you can collect all the item IDs on the month key in an associative array and later add that single entry of month at the top using array_merge like below:
<?php
$StockData = [];
foreach ( $StockModel->stockMonthly()->getResult() as $stock ){
$StockData[ $stock->Month ] = $StockData[ $stock->Month ] ?? [];
$StockData[ $stock->Month ][ $stock->item ] = $stock->Amount;
}
$result = [];
foreach($StockData as $month => $monthlyData){
$result[] = array_merge(['y' => $month], $monthlyData);
}
print_r($result);
You can use the query:
select right(concat('0',month(`date`)),2) as "y",
sum(if (item_id=1,amount, null)) as "1",
sum(if (item_id=2,amount, null)) as "2",
sum(if (item_id=3,amount, null)) as "3",
sum(if (item_id=4,amount, null)) as "4"
from stocks
group by right(concat('0',month(`date`)),2)
In case you have a variable number of item_id's, you are better off by building the array in PHP from a direct data query.
I'm fairly new to sql and am trying to get data from table X when the user is not in table Y with the combination of player id and world id AND the player access is 2.
Let me explain a little furter:
Table X (user table)
+-----------+----------+------------+
| uid | access | more data |
+-----------+----------+------------+
| 1 | 2 | .... |
| 2 | 1 | .... |
| 3 | 2 | .... |
+-----------+----------+------------+
Table Y (worlds)
+-----------+-----------+
| userUuid | worldUuid |
+-----------+-----------+
| 1 | 1 |
| 2 | 2 |
| 3 | 2 |
+-----------+-----------+
When I want to get all users which I can still add to world 1 I want to get the user info from user 3.
User 1 already is in world 1, user 2 does not have access level 2 and user 3 isn't in world 1 yet and does have access level 2.
I'm using medoo and this is my statement at the moment:
$database->select("User", [
"[>]UserInWorld" => ["uid" => "userUid"]
], [
"uid",
"displayname",
"surname",
"email"
], [
"AND" => [
"worldUuid[!]" => $worldUuid,
"access" => 2
]
]);
The worldUuid will be the world I want to get user to add for.
When use the ->debug() the query looks like this:
SELECT "uid","displayname","surname","email"
FROM "User"
LEFT JOIN "UserInWorld" ON "User"."uid" = "UserInWorld"."userUid"
WHERE "worldUuid" != '4dafb8c0-57234ff2-03eb-af7f7a5e'
AND "access" = 2
EDIT: I posted a sollution using medoo below
If I understand you correctly, you should be able to do something like this:
SELECT
uid,
displayname,
surname,
email
FROM
User
LEFT JOIN UserInWorld ON User.uid = UserInWorld.userUid AND worldUuid = 1
INNER JOIN (
SELECT DISTINCT
userUid
from
UserInWorld
WHERE
worldUuid != 1
) AS InOtherWorld ON InOtherWorld.userUid = User.uid
WHERE
access = 2
AND UserInWorld.userUid IS NULL
The left join will connect people in the world where possible and then UserInWorld.userUid IS NULL will effectively strip it down to those that aren't in the world.
you said:
am trying to get data from table X when the user is not in table Y with the combination of player id and world id AND the player access is 2
If I've understood it should be:
select * from X where X.access = 2 and X.uid not in (
select Y.userUuid from Y
)
After a good night sleep I figured out how to do this using the medoo class
$database->select("User", [
"[>]UserInWorld" => ["uid" => "userUid"]
], [
"uid",
"displayname",
"surname",
"email"
], [
"AND" => [
"OR" => [
"worldUuid[!]" => [$worldUuid],
"worldUuid" => NULL
],
"access" => 2
],
"GROUP" => "uid"
]);
Whereby the $worldUuid the world is I want to select users for.
This will make the following sql statement:
SELECT "uid","displayname","surname","email" FROM "User"
LEFT JOIN "UserInWorld" ON "User"."uid" = "UserInWorld"."userUid"
WHERE ("worldUuid" NOT IN ('1') OR "worldUuid" IS NULL)
AND "access" = 2
GROUP BY "uid"
This will select all (unique) user who do not have a world already OR are in the world I'm getting users for AND they have access level 2
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.
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'
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.