Count entries grouped by id and month from denormalized database - php

I have a table (tbl_operations) with rows of where the id column values may be comma-delimited. I want to get the count of each OpId for each month. I am trying to accomplish this through pure sql, but without success.
from this view
OpId
OpDate
3
2022-01-03
5,3
2022-01-15
4
2022-01-27
5
2022-02-01
7
2022-02-09
3,2
2022-01-16
to this
OpId
count
Month
2
1
01
3
3
01
4
1
01
5
1
01
5
1
02
7
1
02
I am stuck here. Can someone enlighten me on how to do this with sql? If not, maybe use php to display the result?
SELECT tbl_operations.OpId,
tbl_operations.OpDate ,
COUNT(tbl_operations.OpId) AS `count`
FROM tbl_operations
WHERE MONTH(OpDate)=1
GROUP BY tbl_operations.OpId

Here’s a quick example. The first part just creates an array of arrays which simulates what you’d get from the database.
The gist is that $counts is an array with a unique OpID for a keys. The values for those arrays are sub-arrays with keys of the month and values of how many times they are found.
Display should just be a simple loop again, however you might want to sort this.
$rows = [
['3', '2022-01-03'],
['5,3', '2022-01-15'],
['4', '2022-01-27'],
['5', '2022-02-01'],
['7', '2022-02-09'],
['3,2', '2022-01-16'],
];
$counts = [];
foreach($rows as $row){
$ids = explode(',', $row[0]);
$month = date('m', strtotime($row[1]));
foreach($ids as $id){
if(!array_key_exists($id, $counts)){
$counts[$id] = [];
}
if(!array_key_exists($month, $counts[$id])){
$counts[$id][$month] = 0;
}
$counts[$id][$month]++;
}
}
Demo here: https://3v4l.org/mVaBB
edit
From #mickmackusa, you can shorten the inner loop by using isset:
if(!isset($counts[$id][$month])){
$counts[$id][$month] = 0;
}
See their comment for a demo link

If you're going to query the data in PHP, you might as well return a better result to work with in the first place:
SQL
SELECT GROUP_CONCAT(OpId), MONTH(OpDate)
FROM tbl_operations
GROUP BY MONTH(OpDate)
PHP
// Result from MySQL query
$rows = [
['3,5,3,4,3,2', 1],
['5,7', 2]
];
And you can perform a count of those grouped results like this:
$results = [];
foreach ($rows as $row) {
$counts = array_count_values(explode(',', $row[0]));
$results[$row[1]] = $counts;
}
Result
Array
(
[1] => Array
(
[3] => 3
[5] => 1
[4] => 1
[2] => 1
)
[2] => Array
(
[5] => 1
[7] => 1
)
)
What you really want to do though is normalise your data, then you can do this easily in SQL alone.

If you are using at least MYSQL8 and you are not going to normalize your table design, then you can actually use the following CTE query to split, group, format, and sort your result set (no PHP processing).
This approach makes recursive calls on the denormalized table and progressively isolates the rightmost id from comma-delimited values and generates new rows for the individual id values. The recursion continues until there are no commas left.
This solution is built on top of the basic technique demonstrated here.
SQL: (Demo)
WITH RECURSIVE norm AS (
SELECT OpId,
OpDate
FROM tbl_operations
UNION ALL
SELECT REGEXP_REPLACE(OpId, '^[^,]*,', '') AS OpId,
OpDate
FROM norm
WHERE OpId LIKE '%,%'
)
SELECT Id,
Mo,
COUNT(*) AS Cnt
FROM (
SELECT REGEXP_REPLACE(norm.OpId, ',.*', '') AS Id,
MONTH(norm.OpDate) AS Mo
FROM norm
) formatted
GROUP BY formatted.Id,
formatted.Mo
Result Set:
Id
Mo
Cnt
2
1
1
3
1
3
4
1
1
5
1
1
5
2
1
7
2
1
That said, this is a lot of unnecessary voodoo mumbo jumbo for a task that is mega-easy once you've normalized your table --- just normalize it A.S.A.P.

Related

How to add not existing record and return it with zero value in Mysqli

QUERY:
SELECT month(date_created), count(a.ticket_num)
FROM ticket as a
LEFT JOIN user_management as b on b.engineer_id = a.ticket_engineer
WHERE b.tl_id = 'sample_id'
AND year(date_created) = '2019'
GROUP BY extract(year from date_created), extract(month from date_created)
SAMPLE OUTPUT:
month | ticket_num
----------------------
2 | 12
4 | 24
6 | 78
EXPECTED SAMPLE OUTPUT:
month | ticket_num
----------------------
1 | 0
2 | 12
3 | 0
4 | 24
5 | 0
6 | 78
As you can see the above expected output, i'm trying to place all existing month in the first column and set all the count to zero if not existed in the second column. As of now, i only have the query for sorting the ticket count by month that is existed when the ticket is created.
There are different approaches to this problem. One is pure SQL for example.
But I would say a PHP based solution is simpler. Basically you need to get your data into array, then create a loop that outputs the desired months order, and have a condition that sees whether we have a corresponding row in our array and outputs ether the actual data or a zero accordingly.
The only tricky part is to have such an array that would let us check the data availability. For this we have to index it with month numbers. Not a big deal actually
$sql = "SELECT month(date_created), count(a.ticket_num) ...";
$res = $mysqli($sql);
$data = [];
while($row = mysqli_fetch_row($res)) {
$data[$row[0]] = $row[1];
}
Now $data is an array indexed by the month number. The rest is a primitive loop
foreach (range(1,12) as $month) {
echo $data[$month] ?: 0;
}
On a side note I would like to advertise using PDO as opposed to mysqli for your database interactions as this case clearly displays the superiority of the former. Using PDO we can get the indexed array right away, without an explicit loop, thanks to a special fetch mode:
$sql = "SELECT month(date_created), count(a.ticket_num) ...";
$data = $data = $pdo->query($sql)->fetchAll(PDO::FETCH_KEY_PAIR);
That's all!

How to use week number to select unique record ID's MYSQL PHP

Not sure if my title accurately explains what I'm trying to do, but here is my use case:
I have a database of Id's 1 = n, let's call it 500 total records.
I want to first get the week number in PHP $week = date('W') so this week is 48.
Then I want to return 3 unique Id's that would always return the same Id's for that week. So for example.
Week 1 = Id's 1,2,3
Week 2 = Id's 4,5,6
Week 3 = Id's 7,8,9
Week 4 = Id's 10,11,12
Week 5 = Id's 13,14,15
Week 6 = Id's 16,17,18
And so on.
My approach was to start with week 1, specifying the 3 Id's, then multiply the week by 2 for the 2nd week, then each subsequent week continue to multiply by 2 and add $i = 1 each week and incrementing $i++ each week and selecting the next 3 Id's from the starting number. My solution below seems to work, but it feels kinda hokey.
$weeks = array(1,2,3,4,5,6,7);
$output_arr = array();
$ids_arr = array();
$id1 = 1;
$id2 = $id1 + 1;
$id3 = $id1 + 2;
$i = -1;
foreach ($weeks as $week) {
if ($week == 1) {
$ids = array($id1, $id2, $id3);
$ids = implode(', ', $ids);
} else {
$id1 = $week * 2 + $i;
$id2 = $id1 + 1;
$id3 = $id1 + 2;
$ids = array($id1, $id2, $id3);
$ids = implode(', ', $ids);
}
$output_arr[$week] = $ids;
$i++;
}
So my end result is:
$output_arr = Array ( [1] => 1, 2, 3 [2] => 4, 5, 6 [3] => 7, 8, 9 [4] => 10, 11, 12 [5] => 13, 14, 15 [6] => 16, 17, 18 [7] => 19, 20, 21 )
Which is what I want, but is there a simpler cleaner way? And, BONUS HELP NEEDED it would be a lot better if it could handle a table with non-sequential Id's. In my case, my ID's need to be sequential, if I have a break in Id's EX: 1,2,3,4,6,7,8,10 it would mess up the output array.
Thanks for the help.
No need to store all weeks in array, a simple function can do the job:
$ids = array(1,2,3,4,6,7,8,10);
function weekIds($pWeek){
$wids = array();
for($i=1;$i<=3;$i++)
$wids[] = ($pWeek-1)*3+$i;
return $wids;
}
$seq = weekIds(2);
foreach($seq as $s)
$nonSeq[] = $ids[$s-1];
echo 'Sequential: ' .print_r($seq,true).PHP_EOL;
echo 'Non-Sequential: ' .print_r($nonSeq,true);
Output:
Sequential: Array
(
[0] => 4
[1] => 5
[2] => 6
)
Non-Sequential: Array
(
[0] => 4
[1] => 6
[2] => 7
)
If you are pulling all of the data out of your table, ORDER BY id and just call array_chunk($array, 3) on your resultset array and access the desired set-of-3 by subtracting 1 from the week number.
If you only need to pull the desired three rows from the table, then you can ORDER BY id and set the LIMIT "offset" to (week minus 1) times 3 then the "limit" to 3.
Alternatively, if you are not scared off by a bit of SQL magic, you can write a user-defined variable in your query so that all of the info is grouped readily available in your resultset.
SQL (SQL Demo)
SELECT
CEIL(r / 3) AS WeekId, GROUP_CONCAT(id) AS Ids
FROM (
SELECT
#rowid := #rowid + 1 AS r, id
FROM tableA
CROSS JOIN (
SELECT #rowid := 0
) cjoin
) sub
GROUP BY WeekId
Output:
| WeekId | Ids |
| ------ | ----- |
| 1 | 1,2,3 |
| 2 | 4,6,7 |
| 3 | 8,10 |
Now, if you don't actually intend to use every row of your database table, then best practice indicates that you should only retrieve the data that you intend to use.
To access a single set of 3 ids, use php to determine the current week number, divide it by three, round that number up to the next integer, then subtract one so that the LIMIT clause in the query uses 0 (for week 1) to target ids 1, 2, and 3; and 1 (for week 2) to target ids 4, 6, 7; etc.
Code: (SQL Demo)
$limit_offset = ceil(date('W') / 3) - 1;
$query = "SELECT id FROM your_table ORDER BY id LIMIT $limit_offset, 3";
p.s. The reason that I am not using pure SQL to perform this task is because: Can I use MySQL functions in the LIMIT offset .

SQL Query result with contains values from an array

I search a MySQL solution to collect queries, which contains some ints (ids in an array).
Example-Table:
id1 id2 id3
1 2 2
2 3 2
3 3 5
4 4 2
5 4 5
6 4 7
I have an array like
$id3array = (2,5);
I need id2 = 3 (not 4, because id2 "4" has one more id3 argument (7)
If I have an array like
$id3array = (2,5,6); // array can contains 20 and more arguments
In this example Table I need id2 = 0 (because no match)
My Tests with IN(2,5) or Group by was not successful - Is this possible to solve with MySQL?
http://www.sqlfiddle.com/#!9/b71306/2
Try following query. Put inner query which will match records which is not in desired array. Then group and count exact element
SELECT `id2` FROM `exampletable` WHERE `id3` IN (2,5) AND `id2` NOT IN (
SELECT e.id2 from exampletable e WHERE e.id3 not in (2,5)) GROUP BY `id2`
HAVING count(`id2`) = 2;
Here I have added count(id2) = 2 because you have 2 elements in array
DEMO

PHP/MySQL Sort Multi-dimensional array

I'm trying to sort an array in to a three-deep array. This is my current query:
SELECT * FROM question
INNER JOIN category ON question.category_id = category.id
INNER JOIN difficulty ON question.difficulty_id = difficulty.id
Expected result is something like:
array(
'1' => array( // category id 1
'1' => array( // difficulty id 1
'1' => array('...'), // question id 1
'2' => array('...') // question id 2
),
'2' => array(
'3' => array('...'),
'4' => array('...')
)
)
)
I did have the following:
foreach($categories as $category) {
foreach($difficulties as $difficulty) {
foreach($questions as $question) {
if ($question['category_id'] == $category['id'] && $question['difficulty_id'] == $difficulty['id']) {
$feed[$category['id']][$difficulty['id']][$question['id']] = $question;
}
}
}
}
But there will be 10,000+ questions and performance will be bad so is there a way I can do this with one query and fewer loops?
Basically you could just return your query and order by the ids like so:
Category_ID Difficulty_ID Question_ID
0 0 0
0 0 1
1 0 2
1 3 3
1 3 4
2 0 5
2 1 6
Then parse everything in a while:
each time the category_ID changes add a new category with empty difficulty and reset previous difficulty
each time the difficulty changes add new difficulty to category with empty question
each time add the question to current difficulty.
To store this structure performantly in local storage:
define a unique delimiter (note: IE doesn't support control characters, this also means you can't store binary data without encoding it before, e.g. base64)
load each row of each table like this:
key: unique table prefix + id
value: columns (delimited with the delimiter defined before)
The easiest way to return a whole table at once is to define a second delimiter and then have some slightly ugly query in the form of:
SELECT id||delimiter||col1||delimiter||...||colN FROM ...
And then put it all together with a list aggregation using the second delimiter (group_concat() in mysql).
Sometimes you need maps (for N to M relations or also if you want to search questions by difficulty or category), but because each question only has one category and difficulty you are already done.
Alternative
If the data is not too big and doesn't change after login, then you can just use the application cache and echo your stuff in script tags.

Iterative appending of array with arrays from sql results

am new here, but have enjoyed reading others' questions and answers. I'm fairly new to PHP and am working on a project - basic table look-ups in MySQL, etc.
What I want to end up with is an array of arrays, the form of which is shown below with condiments (not my actual project). The content is coming from two different tables. For each of the condiment names (from table 1) I look up in table 2 the types of condiments, linked by ID. The searching and grabbing stuff is fine, but I'm having trouble looping and building my final $Condiments array.
The first part of the loop, I grab the condiment name from the $row and append it to the array. But I need each of these condiment names to be an empty array to put something in, in the next step. I've looked around but couldn't find a good way to iteratively append new placeholder arrays into an array. Is there an elegant solution? Some cool function I'm not taking advantage of? Thanks!
// SQL search for condiment words, blah blah, leading to...
$rowsnumber = mysql_num_rows($result);
for ($j = 0 ; $j < $rowsnumber ; ++$j)
{
$row = mysql_fetch_row($result); // $row is an array featuring a condiment name and other stuff.
$Condiments[] = $row[1]; // condiment name goes in array.
$CondimentType = searchTable2($row[0]);
// using the condiment name's ID, I look up its matching types via a function.
// $CondimentType is now an array of IDs and types from Table2 that I want to append to the condiment name I just added above.
$Condiments[$row[1]] = $CondimentType;
// I repeat the process for the next name
}
// Final desired result...
$Condiments=
Array
(
[Pickles] => Array
(
[34] => Dill
[23] => Butter
)
[Mustard] => Array
(
[22] => Hot
)
[Relish] => Array
(
[3] => Pickle
)
)
so like i said , you need to use join to perform the needed task.
you can find more explanation here about joins
http://dev.mysql.com/doc/refman/5.0/en/join.html
in your case , this query should do the job
select t1.word,t3.word
from table1 as t1 join table2 as t2 on t1.id =t2.id
left join table1 as t3 on t3.id = t2.linkid
i ran the query in my machine and these are the results
+---------+--------+
| word | word |
+---------+--------+
| Pickles | Dill |
| Pickles | Butter |
| Mustard | Hot |
| Relish | Pickle |
+---------+--------+
so instead of looping through each row, just perform a join and get the results. in php then you can do the needed format of the array.
hopefully this will help you

Categories