How to manipulate and collate data in an associate array - php

I have the following array of data
2889 1 1062
2889 8 John Smith
2889 6 0.29
2891 1 1117
2891 8 Jamie Dean
2891 6 2
2892 1 1062
2892 8 John Smith
2892 6 4
The First column is a list of entry IDs relating to form entries from a website, the second column is a list of meta_keys relating to fields in the form, and the final column is the data from those fields.
What I need to be able to do is collate the data in the array so that for each person I have:
ID Number(Meta_key 1)
Name(Meta_key 8)
Sum(Hours Owed(Meta_key 6))
I am lost on how to even start this task, any help would be very much appreciated.
This data has all been pulled from a database with the following query:
select
entry_id,
meta_key,
meta_value
from
staff_gf_entry_meta
where
form_id = 48
and
entry_id in (
select
entry_id
from
staff_gf_entry_meta
where
meta_key = 7
and
form_id = 48
and
meta_value <= '2018-12-18'
and
meta_value >= '2018-12-12'
)
and (
meta_key = 1
or
meta_key = 8
or
meta_key = 6)
If needed the query can be altered.

You might want a GROUP BY statement with the SUM aggregate function. To get all the key/value pairs in a row, you need to JOIN multiple queries.
SELECT
`mk1`.`meta_value` `Number`,
`mk8`.`meta_value` `Name`,
SUM(`mk6`.`meta_value`) `Hours Owed`
FROM
`staff_gf_entry_meta` `mk1`
INNER JOIN
`staff_gf_entry_meta` `mk6`
USING
(`entry_id`, `form_id`)
INNER JOIN
`staff_gf_entry_meta` `mk7`
USING
(`entry_id`, `form_id`)
INNER JOIN
`staff_gf_entry_meta` `mk8`
USING
(`entry_id`, `form_id`)
WHERE
`mk1`.`meta_key` = 1
AND
`mk6`.`meta_key` = 6
AND
`mk7`.`meta_key` = 7
AND
`mk8`.`meta_key` = 8
AND
`mk1`.`form_id` = 48
AND
`mk7`.`meta_value` BETWEEN '2018-12-12' AND '2018-12-18'
GROUP BY `mk1`.`meta_value`,`mk1`.`form_id`
;
I've assumed the following table structure and data:
CREATE TABLE `staff_gf_entry_meta`
(
`form_id` int(11) NOT NULL,
`entry_id` int(11) NOT NULL,
`meta_key` int(11) NOT NULL,
`meta_value` varchar(45) DEFAULT NULL,
PRIMARY KEY (`entry_id`,`form_id`,`meta_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
;
INSERT INTO `staff_gf_entry_meta` (form_id, entry_id, meta_key, meta_value)
VALUES
(48, 2889, 1, 1062),
(48, 2889, 8, 'John Smith'),
(48, 2889, 6, 0.29),
(48, 2891, 1, 1117),
(48, 2891, 8, 'Jamie Dean'),
(48, 2891, 6, 2),
(48, 2892, 1, 1062),
(48, 2892, 8, 'John Smith'),
(48, 2892, 6, 4)
;
INSERT INTO `staff_gf_entry_meta`
SELECT DISTINCT form_id, entry_id, 7 meta_key, '2018-12-17' meta_value
FROM testdb.staff_gf_entry_meta
;
Result:
# Number, Name, Hours Owed
'1062', 'John Smith', '4.29'
'1117', 'Jamie Dean', '2'

Start with something like below, and build from there.
$meta_labels = [
1 => 'ID'
8 => 'name'
6 => 'hours'
];
$output = [];
foreach($results as result) {
$eid = $result['entry_id'];
$label = $meta_labels[$result['meta_key']];
$output[$eid][$label] = $result['meta_value'];
}
And ideally the mapping between metadata IDs and their labels should be stored in the database, not hard-coded in the application.

Related

How to show a SQL virtual/temporary table from associative array

I need a simple SQL query to show a virtual/temporary table without creating it in the database.
I'm using PHP to create that query string with the data.
My current PHP code is,
$array = [
['id' => 1, 'name' => 'one'],
['id' => 2, 'name' => 'two'],
['id' => 3, 'name' => 'three']
];
$subQuery = "SELECT {$array[0]['id']} AS col1, '{$array[0]['name']}' AS col2";
for ($i=1; $i < count($array); $i++) {
$subQuery .= " UNION ALL SELECT {$array[$i]['id']}, '{$array[$i]['name']}'";
}
$sql = "WITH cte AS
(
{$subQuery}
)
SELECT col1, col2 FROM cte;";
echo $sql;
Its output is,
WITH cte AS
(
SELECT 1 AS col1, 'one' AS col2 UNION ALL SELECT 2, 'two' UNION ALL SELECT 3, 'three'
)
SELECT col1, col2 FROM cte;
// Output table from the SQL
col1 col2
1 one
2 two
3 three
I got the idea for this query from here.
But the problem with this query is that,
if I have 100 data in the $array, the UNION ALL part is getting included 100 times in the SQL. I feel like it is not a better SQL because it's like UNION 100 tables at the same time.
I also can create a temporary table (CREATE TEMPORARY TABLE table_name) instead of this WITH clause, but it is not a single query because I need another query to INSERT the records to that temporary table.
Can someone please help me to simplify this query in a better way?
When you use MySQL 8 you can use json_table expression like:
<?php
$array = [
['id' => 1, 'name' => 'one'],
['id' => 2, 'name' => 'two'],
['id' => 3, 'name' => 'three']
];
$data = json_encode($array);
$sql = "SELECT tbl.*
FROM JSON_TABLE(
'{\"data\":$data}',
'$.data[*]' COLUMNS (
id VARCHAR(40) PATH '$.id',
name VARCHAR(100) PATH '$.name')
) tbl";
echo $sql;
PHP online editor
The result of above query in MySQL 8.0 is:
+====+=======+
| id | name |
+====+=======+
| 1 | one |
+----+-------+
| 2 | two |
+----+-------+
| 3 | three |
+----+-------+

Get all rows inside a foreach loop and echo an array result?

I'm trying to get all rows inside a foreach loop but it's not working as it should.
<?php
foreach ($locations_loop as $row):
$lr_id = $row["id"];
$stmtlr = $pdo->prepare("SELECT * FROM locations_rating WHERE l_id = {$lr_id}");
$stmtlr->execute();
$stlr_loop = $stmtlr->fetchAll(PDO::FETCH_ASSOC);
if (empty($stlr_loop)) {
$loc_rate[] = "0";
} else {
foreach($stlr_loop as $rowlr):
$loc_rate[] = $rowlr["stars"];
endforeach;
}
$rating_array = array_values($loc_rate);
$rating_avg = array_sum($rating_array) / count($rating_array);
?>
<?=round($rating_avg, 1);?>
<?php endforeach; ?>
$rating_avg outputs something else every time the script runs. It works fine outside a foreach loop tho. I tried to join the two table but no luck since it only outputs only one row.
I might be thinking too far out of the box, but this is just one technique that occurred to me which will ensure that all location ids will receive an average value in the result set.
Assuming $locations_loop (a poor name for a variable containing array type data, tbh) has the following data:
$locations_loop = [
['id' => 1],
['id' => 2],
['id' => 3],
['id' => 4],
];
And you have a database table with the following schema: (db-fiddle demo)
CREATE TABLE `locations_rating` (
`id` int(11) NOT NULL,
`l_id` int(11) NOT NULL,
`stars` int(11) NOT NULL DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `locations_rating` (`id`, `l_id`, `stars`) VALUES
(1, 3, 4),
(2, 2, 2),
(3, 1, 0),
(4, 2, 5),
(5, 3, 2),
(6, 1, 10);
Then you can get all of your data in one trip to the database by creating a "derived table" from your column of id values, then joining the database data to them. Something like this:
SELECT def.l_id,
ROUND(AVG(COALESCE(stars, 0)), 1) avg
FROM (
(SELECT 1 AS l_id)
UNION (SELECT 2)
UNION (SELECT 3)
UNION (SELECT 4)
) AS def
LEFT JOIN locations_rating AS loc ON def.l_id = loc.l_id
GROUP BY def.l_id
To do this with a prepared statement and bound parameters:
$locationIds = array_column($locations_loop, 'id');
$countIds = count($locationIds);
$fabricatedRows = implode(' UNION ', array_fill(0, $countIds, '(SELECT ? AS l_id)'));
$sql = "SELECT derived.l_id,
ROUND(AVG(COALESCE(stars, 0)), 1) avg
($fabricatedRows) AS derived
LEFT JOIN locations_rating as loc ON derived.l_id = loc.l_id
GROUP BY def.l_id";
$stmt = $pdo->prepare($sql);
$stmt->execute($locationIds);
var_export($stmt->fetchAll(PDO::FETCH_ASSOC));
Should output: (I tested this technique to be successful in my local environment)
[
['l_id' => 1, 'avg' => 5.0],
['l_id' => 2, 'avg' => 3.5],
['l_id' => 3, 'avg' => 3.0],
['l_id' => 4, 'avg' => 0.0],
]

INSERT INTO, Multiple WHERE IN, Check if value exist

I am collecting information about 30 posts with title and content in a multidimensional array which is relatively long and then put all of them into database by one MySQL query. But my checking query is separate from that inserting. I am checking similar post by the new post's title and then insert it. How can I merge the checking query with the inserting one?
This is my multidimensional array about 30 posts :
array(
[0] => array(
[post_title] => $postTitle1,
[post_content] => $contentTitle1,
)
[1] => array(
[post_title] => $postTitle2,
[post_content] => $contentTitle2,
)
[N] => array(
[post_title] => $postTitleN,
[post_content] => $contentTitleN,
)
);
This is my checking query by the new post's title (is done for each post and works fine):
SELECT post_title FROM x_post WHERE post_title=$newPostTitle
This is my inserting query (works fine):
INSERT INTO x_post (`post_title`, `post_content`, `date_created`, `user_id`)
VALUES ((((( My multidimensional array's information will be here after processing )))))
and Finally this is the query what I want to have but it does not work (to merge two queries):
INSERT INTO x_post (`post_title`, `post_content`, `date_created`, `user_id`)
SELECT * FROM (SELECT $postTitle1, $content1, $time, $userId") AS tmp1, (SELECT $postTitle2, $content2, $time, $userId") AS tmp2, .......... and so on ........... , (SELECT $postTitle30, $content30, $time, $userId") AS tmp30 WHERE NOT EXIST (SELECT x_post.post_title FROM x_post WHERE x_post.post_title IN ($newPostTitle1, $newPostTitle2, ... and so on... , $newPostTitleN))
What is the best way to write the query ?
In fact I want to check all the 30 posts if similar is exist in one query and then insert those has not similar.
I know it is complicated, but would be a great query.
Thanks a lot.
You can use UNION ALL to make a set of your individual FROM-less SELECTs which you can then use as just one derived table instead of 30. And instead of using IN in the WHERE clause you can just correlate directly.
INSERT INTO x_post
(`post_title`,
`post_content`,
`date_created`,
`user_id`)
SELECT `post_title`,
`post_content`,
`date_created`,
`user_id`
FROM (SELECT $postTitle1 `post_title`,
$content1 `post_content`,
$time `date_created`,
$userId `user_id`
UNION ALL
...
UNION ALL
SELECT $postTitle30 `post_title`,
$content30 `post_content`,
$time `date_created`,
$userId `user_id`) tmp
WHERE NOT EXISTS (SELECT *
FROM x_news
WHERE x_news.news_title = tmp.`post_title`);

Creating a directory tree with SQL in MySQL

I am using php to make directory tree list. Directory(data) are from a MySQL database table but I do not have the idea to write the sql to get the records with the order I want:
Create sql of the table:
CREATE TABLE section (
section_id tinyint(4) NOT NULL auto_increment,
name varchar(500) default NULL,
parent_id tinyint(4) default NULL,
lineage varchar(45) default NULL,
level tinyint(4) default NULL,
PRIMARY KEY (section_id)
) ENGINE=MyISAM AUTO_INCREMENT=15 DEFAULT CHARSET=utf8;
And the insert SQL of data:
INSERT INTO section (
section_id,name,lineage,parent_id,level)
VALUES
('1', 'Dashboard', '1', '0', '1'),
('2', 'Slider', '2', '0', '1'),
('3', 'Column', '3', '0', '1'),
('4', 'Column list', '3-4', '3', '2'),
('5', 'Add column', '3-5', '3', '2'),
('6', 'Permission', '6', '0', '1'),
('7', 'Permission Group', '6-7', '6', '2'),
('8', 'User List', '6-8', '6', '2'),
('9', 'Section permission', '6-9', '6', '2'),
('10', 'Add permission', '6-7-10', '7', '3'),
('11', 'Add user', '6-8-11', '8', '3'),
('12', 'Add section', '6-9-12', '9', '3');
All records of the table which's created:
section_id name parent_id level
-----------------------------------------------------
1 Dashboard 0 1
2 Slider 0 1
3 Column 0 1
4 Column list 3 2
5 Add column 3 2
6 Permission 0 1
7 Permission Group 6 2
8 User List 6 2
9 Section permission 6 2
10 Add permission 7 3
11 Add user 8 3
12 Add section 9 3
I would like to use a/some SQL get the rows in this order:
section_id name parent_id level
-----------------------------------------------------
1 Dashboard 0 1
2 Slider 0 1
3 Column 0 1
4 Column list 3 2
5 Add column 3 2
6 Permission 0 1
7 Permission Group 6 2
10 Add permission 7 3
8 User List 6 2
11 Add user 8 3
9 Section permission 6 2
12 Add section 9 3
For making this directory tree:
- Dashboard
- Slider
- Column
- Column list
- Add column
- Permission
- Permission Group
- Add permission
- User List
- Add user
- Section permission
- Add Section
I have an idea using many SQL to get the directories and store the data into an php object.
And this is the sql to get sub-directory:
SELECT * FROM tbl_section AS a1 WHERE a1.parent_id = 0;
SELECT * FROM tbl_section AS a1 WHERE a1.parent_id = 1;
SELECT * FROM tbl_section AS a1 WHERE a1.parent_id = 2;
SELECT * FROM tbl_section AS a1 WHERE a1.parent_id = 3;
and so on.
However, it needs run the select sql 100 times if there is 100 directories,
i don't think it's a good method, any others idea?
This method (Lineage Column) may not is the best and most dynamic way to list out the directory tree which get data from MySQL table, but this is quite easy and fast:
Related question in stackoverflow:
What are the options for storing hierarchical data in a relational database?
Referenced article:
Lineage Column (a.k.a. Materialized Path, Path Enumeration)
http://www.ferdychristant.com/blog/articles/DOMM-7QJPM7
This is the Select SQL by using "Lineage Column" method:
SELECT c.section_id, c.name, c.lineage, c.level, c.parent_id,
(SELECT COUNT(*) FROM section
where section.lineage
LIKE (CONCAT(c.lineage,'%')) AND
section.lineage!=c.lineage) as replies
FROM section as c
order by c.lineage;
And it returns the record with the order i want:
section_id name lineage level parent_id replies
----------------------------------------------------------------------
1 Dashboard 1 1 0 0
2 Slider 2 1 0 0
3 Column 3 1 0 2
4 Column list 3-4 2 3 0
5 Add column 3-5 2 3 0
6 Permission 6 1 0 6
7 Permission Group 6-7 2 6 1
10 Add permission 6-7-10 3 7 0
8 User List 6-8 2 6 1
11 Add user 6-8-11 3 8 0
9 Section permission 6-9 2 6 1
12 Add section 6-9-12 3 9 0
This is the php code to echo the directory mentioned at the question:
<?php
CONST SERVERNAME = ""; //your mysql server name
CONST USERNAME = ""; //your mysql user name
CONST PASSWORD = ""; //your mysql password
CONST DATABASE = ""; //your mysql database name
// Create connection
$conn = new mysqli(SERVERNAME, USERNAME, PASSWORD, DATABASE);
// Check connection
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}
$sql = "SELECT c.section_id, c.name, c.lineage, c.level, c.parent_id,
(SELECT COUNT(*) FROM section
where section.lineage
LIKE (CONCAT(c.lineage,'%')) AND
section.lineage!=c.lineage) as replies
FROM section as c
order by c.lineage";
$html = "";
foreach ($conn->query($sql) as $row) {
switch ($row['level']){
case "1": $html.="-".$row['name'] . "<br />"; break;
case "2": $html.=" -".$row['name'] . "<br />"; break;
case "3": $html.=" -".$row['name'] . "<br />"; break;
}
}
print $html;
?>
Result of the php code:
-Dashboard
-Slider
-Column
-Column list
-Add column
-Permission
-Permission Group
-Add permission
-User List
-Add user
-Section permission
-Add section

Struggling adding WHERE clause to INNER JOIN

I have a query which is supposed to select the lowest price_per_pax_after_tax from every backend_hotels_id date_start and package_supplier and this appears to be working until I add a WHERE clause.
Here's the query:
SELECT e.price_per_pax_after_tax, e.hotel_score, e.package_id, e.package_type
FROM packages_sorted_YQU e
INNER JOIN (
SELECT db_id, MIN( price_per_pax_after_tax ) AS lowest_price, package_id, hotel_score
FROM `packages_sorted_YQU`
WHERE `package_type` IN ('9', '10', '18')
AND `package_duration` IN ('6', '8', '12')
GROUP BY
`date_start` , `package_supplier` , `backend_hotels_id`
) AS j
ON j.db_id = e.db_id
AND j.lowest_price= e.price_per_pax_after_tax
AND j.hotel_score = e.hotel_score
AND j.package_id = e.package_id;
The table is huge but all of the fields listed are INT except for date_start which is DATE
The where clause causing the problem is:
WHERE `package_type` IN ('9', '10', '18')
AND `package_duration` IN ('6', '8', '12')
Without the where clause I get over 400 results and with the where clause I get zero results :( Any help will be very much appreciated.
If your columns package_type and package_duration are of type int you don't have to wrap the values inside ' like strings.
SELECT e.price_per_pax_after_tax, e.hotel_score, e.package_id, e.package_type
FROM packages_sorted_YQU e
INNER JOIN (
SELECT db_id, MIN( price_per_pax_after_tax ) AS lowest_price, package_id, hotel_score
FROM `packages_sorted_YQU`
WHERE `package_type` IN (9, 10, 18)
AND `package_duration` IN (6, 8, 12)
GROUP BY
`date_start` , `package_supplier` , `backend_hotels_id`
) AS j
ON j.db_id = e.db_id
AND j.lowest_price= e.price_per_pax_after_tax
AND j.hotel_score = e.hotel_score
AND j.package_id = e.package_id;
The subquery:
SELECT db_id
, MIN( price_per_pax_after_tax ) AS lowest_price
, package_id
, hotel_score
FROM `packages_sorted_YQU`
WHERE `package_type` IN ('9', '10', '18')
AND `package_duration` IN ('6', '8', '12')
GROUP BY
`date_start`
, `package_supplier`
, `backend_hotels_id`
will yield indeterminate results, with or without the WHERE clause. Because you are grouping by date_start, package_supplier, backend_hotels_id and have in the SELECT list columns without any aggregate functions on them: db_id, package_id, hotel_score.
This query should work consistently if the (date_start, package_supplier, backend_hotels_id) is the Primary Key or Unique.
Which is the PRIMARY KEY of the table and are there any other UNIQUE keys?
Hi all and thank you for your valuable input. I've solved the problem without a sub-query and it works a bit faster too.
SELECT MIN
(
concat
(
LPAD(`price_per_pax_after_tax` , 5, '0'),
LPAD(`package_id` , 12, '0'),
LPAD(`hotel_score` , 7, '0')
)
) AS cat
FROM `packages_sorted_YQU`
WHERE `package_type` IN
(
9, 10, 18
)
AND `package_duration` IN
(
6, 7, 8
)
GROUP BY `date_start` , `package_supplier` , `backend_hotels_id`
Then in PHP I break apart the concatenation with:
while($r=mysql_fetch_array($q,MYSQL_ASSOC))
{
$a[lrp][] = intval(substr($r[cat], 0, 5));
$a[package_id][] = intval(substr($r[cat], 5, 12));
$a[hotel_score][] = substr($r[cat], 17, 7);
}
I was lucky that the only FLOAT value was the hotel_score so I put that last - the other two were of type INT

Categories