SQL query how to match 3 out of 4 results - php

I have searched a number of different items but I have not found any answers. Chances are I just don't know how to word this correctly.
Anyway, I have set up a system in PHP/SQL to allow instantaneous scanning of thousands of results. Each data entry has 4 numbers, and it can easily scan for entries that match all 4 of these numbers. What I am trying to achieve is to have the script search the database for entries that match exactly 3 out of the 4 entries with the other being incorrect, kind of like a lottery.
For example, we have the entries:
Steve - 1, 4, 10, 13
Bill - 3, 4, 10, 13
Tom - 1, 17, 20, 39
Jill - 1, 4, 13, 21
Jane - 5, 10, 13, 18
Now, I would scan based on the results 1, 4, 10, 13, and would like to return the following results, as these matched 3 of the 4 entries:
Bill - 3, 4, 10, 13
Jill - 1, 4, 13, 21
How would I achieve this?
Many thanks
EDIT: Sorry yes the table has the structure
Name - Number1 - Number2 - Number3 - Number4
So yes, stored as separate fields

You can do this by counting the matches and setting this equal to 3:
select t.*
from t
where (val1 in (1, 4, 10, 13) +
val2 in (1, 4, 10, 13) +
val3 in (1, 4, 10, 13) +
val4 in (1, 4, 10, 13)
) = 3;
In MySQL a TRUE boolean expression evaluates to 1. You can add these together to get the number of matches.

Related

Extract specific data from a String in PHP

I am capturing some data after taking a user through a number of steps. This data is successfully being captured in the below String format. Note that due to some limitations, I don't have an option to get this data in any other format.
There are two counts that are being captured. One is a Lap and then within each lap, the number of Reps.
Lap 1 Rep 1, 11, Lap 1 Rep 2, 12, Lap 1 Rep 3, 15, Lap 2 Rep 1, 22, Lap 2 Rep 2, 24, Lap 2 Rep 3, 29
I need to get the below values from the above code in a PHP Array.
11
12
15
22
24
29
Please note that since the user selected the number of Laps and Reps in the process, therefore Laps and Reps can go to double-digit as well. But when I am getting the above values, I have the total count of Laps and Reps as well. Like in the above example, I also have the Laps being 2 and Reps of each Lap being 3.
Can anyone help?
Like stated in the comments, you're best to run a regex query (using this as an example: Lap \d Rep \d, (\d+)):
preg_match_all("/Lap \d Rep \d, (\d+)/", $str, $matches);
Now if you look at $matches[1], you'll get the following:
Array (
[0] => 11
[1] => 12
[2] => 15
[3] => 22
[4] => 24
[5] => 29
)
$str being the string you have there as an example.
This will accomplish what you want. I first split the string by using the explode function, then I only keep every other element by using the mod function and checking the value to see if it is 0:
$info = "Lap 1 Rep 1, 11, Lap 1 Rep 2, 12, Lap 1 Rep 3, 15, Lap 2 Rep 1, 22, Lap 2 Rep 2, 24, Lap 2 Rep 3, 29";
$i = 1;
$keepThese = Array();
foreach(explode(", ", $info) as $value) {
if ($i++ % 2 == 0) {
array_push($keepThese, $value);
}
}
var_dump($keepThese);
PSST: You forgot 15 in your example output.

Manipulating/formatting database output

I've got a database query that outputs the data I need, but I'm not sure how to get it into the format I need (also need to get it into csv format, but need to get the output right first).
SELECT `user_id`, `assessment_name`, `question_id`, `question_type_id`, `answer`, `created_at` FROM answer WHERE `assessment_id` = 1
The output is like this (although with 30 rows per submission - just put three each here as an example):
11 Three Intelligences 31 6 4 7/22/08 11:30
11 Three Intelligences 40 4 4 7/22/08 11:30
11 Three Intelligences 41 6 5 7/22/08 11:30
10 Three Intelligences 31 6 3 7/22/08 14:54
10 Three Intelligences 40 4 4 7/22/08 14:54
10 Three Intelligences 41 6 4 7/22/08 14:54
12 Three Intelligences 31 6 4 7/29/08 10:31
12 Three Intelligences 40 4 4 7/29/08 10:31
12 Three Intelligences 41 6 4 7/29/08 10:31
What I need is to reformat this so that it can be used for data analysis, which means getting the data into a single row for each user_id and assessment_name and created_at
assessment_name, user_id, answer(for question 31), question_type_id(for question 31), answer(for question 40), question_type_id(for question 40), answer(for question 41), question_type_id(for question 41), created_at
Three Intelligences, 11, 6, 4, 4, 4, 6, 5, 7/22/08 11:30
Three Intelligences, 10, 6, 3, 4, 4, 6, 4, 7/22/08 14:54
Three Intelligences, 12, 6, 4, 4, 4, 6, 4, 7/29/08 10:31
Is this possible? If so, I also assume I can do it in php, but I don't know enough to figure out the 'while' loops necessary. It probably doesn't even need to produce a csv file output... if I can just get it properly formatted on the page I can copy/paste for this particular project.
Thanks
--EDIT--
Here's what I've got so far -- this will generate the semi-raw output of the data... I think what I need is just to figure out what loops within loops are required within the 'while' to get the desired output.
<?php
require_once '../connection.php';
// Create connection
$conn = new mysqli(localhost, $dbuser, $dbpass, $db);
// Check connection
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}
$query_responses = "SELECT `user_id`, `assessment_name`, `question_id`, `question_type_id`, `answer`, `created_at` FROM answer WHERE `assessment_id` = 1";
$result_responses = $conn->query($query_responses);
if ($result_responses->num_rows > 0) {
// output data of each row
while($row = $result_responses->fetch_assoc()){
echo $row["created_at"]. ", " .$row["assessment_name"]. ", " .$row["user_id"]. "," .$row["question_id"]. "," .$row["question_type_id"]. "," .$row["answer"]. "<br />";
}
} else {
echo "0 results";
}
$conn->close();
?>
is it this what you looking for:
SELECT
`user_id`,
`assessment_name`,
CONCAT(
GROUP_CONCAT(
CONCAT(`question_type_id`, ', ', `answer`)
SEPARATOR ', ' ), ', ', `created_at`) AS RESULT
FROM answer
WHERE `assessment_id` = 1
GROUP BY `user_id`
ORDER BY `user_id`;
Sample
MariaDB []> SELECT
-> `user_id`,
-> `assessment_name`,
-> CONCAT(
-> GROUP_CONCAT(
-> CONCAT(`question_type_id`, ', ', `answer`)
-> SEPARATOR ', ' ), ', ', `created_at`) AS RESULT
-> FROM answer
-> -- WHERE `assessment_id` = 1
-> GROUP BY `user_id`
-> ORDER BY `user_id`;
+---------+---------------------+---------------------------------------------+
| user_id | assessment_name | RESULT |
+---------+---------------------+---------------------------------------------+
| 11 | Three Intelligences | 6, 4, 4, 4, 6, 5, 2016-01-01 00:00:00 |
| 12 | Three Intelligences | 6, 4, 6, 4, 6, 4, 6, 4, 2016-01-01 00:00:00 |
+---------+---------------------+---------------------------------------------+
2 rows in set (0.00 sec)
MariaDB []>
Please let me know if it works for you

Rank array values with potential duplicate values and skipping some positions if there is a tie

I am working with database data that manipulates college students exam results. Basically, I am pulling the records from a MySQL database and pulling one class at any given time. I want to rank the students with the highest performer given the rank of 1.
Here is an illustration;
Marks: 37, 92, 84, 83, 84, 65, 41, 38, 38, 84.
I want to capture MySQL data as a single array. Once I have the data in an array, I should then assign each student a position in the class such as 1/10 (number 1, the 92 score), 4/10 etc. Now the problem is that if there is a tie, then the next score skips a position and if there are 3 scores at one position then the next score skips 2 positions. So the scores above would be ranked as follows;
92 - 1
84 - 2,
84 - 2,
84 - 2,
83 - 5,
65 - 6,
41 - 7,
38 - 8,
38 - 8 ,
37 - 10
The grading system requires that the number of positions (ranks, if you will) will be maintained, so we ended up with 10 positions in this class since positions 3, 4, 5 and 9 did not have any occupants. (The alternative of filling every number will have given us only 8 positions!)
Is it possible (humanly/programmatically possible) to use PHP to rank the scores above in such a way that it can handle possible ties such as 4 scores at one position? Sadly, I could not come up with a function to do this. I need a PHP function (or something in PHP) that will take an array and produce a ranking as above.
If it's possible to do this with MySQL query data without having it in an array, then that will also be helpful!
I assume the grades are already sorted by the database, otherwise use sort($grades);.
Code:
$grades = array(92, 84, 84, 84, 83, 65, 41, 38, 38, 37);
$occurrences = array_count_values($grades);
$grades = array_unique($grades);
foreach($grades as $grade) {
echo str_repeat($grade .' - '.($i+1).'<br>',$occurrences[$grade]);
$i += $occurrences[$grade];
}
Result:
92 - 1
84 - 2
84 - 2
84 - 2
83 - 5
65 - 6
41 - 7
38 - 8
38 - 8
37 - 10
EDIT (Response to discussion below)
Apparently, in case the tie occurs at the lowest score,
the rank of all lowest scores should be equal to the total count of scores.
Code:
$grades = array(92, 84, 84, 84, 83, 65, 41, 38, 37, 37);
$occurrences = array_count_values($grades);
$grades = array_unique($grades);
foreach($grades as $grade) {
if($grade == end($grades))$i += $occurrences[$grade]-1;
echo str_repeat($grade .' - '.($i+1).'<br>',$occurrences[$grade]);
$i += $occurrences[$grade];
}
Result:
92 - 1
84 - 2
84 - 2
84 - 2
83 - 5
65 - 6
41 - 7
38 - 8
37 - 10
37 - 10
$scores = array(92, 84, 84, 84, 83, 65, 41, 38, 38, 37);
$ranks = array(1);
for ($i = 1; $i < count($scores); $i++)
{
if ($scores[$i] != $scores[$i-1])
$ranks[$i] = $i + 1;
else
$ranks[$i] = $ranks[$i-1];
}
print_r($ranks);
I needed to end up with a map of values to rank. This method may be more efficient for the original question too.
public static function getGrades($grades)
{
$occurrences = array_count_values($grades);
krsort($occurrences);
$position = 1;
foreach ($occurrences as $score => $count) {
$occurrences[$score] = $position;
$position += $count;
}
return $occurrences;
}
If you print_r on $occurrences you get
Array
(
[92] => 1
[84] => 2
[83] => 5
[65] => 6
[41] => 7
[38] => 8
[37] => 10
)
Based on the original answer, so thanks!
Using array_count_values() followed by a foreach() is doing 2 loops over the input array, but this task can be done will one loop (minimizing/optimizing the time complexity).
Code: (Demo)
// assumed already rsort()ed.
$scores = [92, 84, 84, 84, 83, 65, 41, 38, 38, 37];
$gappedRank = 0;
$result = [];
foreach ($scores as $score) {
++$gappedRank;
$gappedRanks[$score] ??= $gappedRank;
$result[] = [$score => $gappedRanks[$score]];
}
var_export($result);
For a flat, associative lookup array of scores and their rank, unconditionally increment the counter and only push a new element into the lookup array if the key will be new. (Demo)
$gappedRank = 0;
$lookup = [];
foreach ($scores as $score) {
++$gappedRank;
$lookup[$score] ??= $gappedRank;
}
var_export($lookup);
The first snippet provides "gapped ranking". I have another answer which implements a similar approach but with a different input data structure and with the intent of modifying row data while looping.
Get dense rank and gapped rank for all items in array
In the realm of ranking, there is also "dense ranking". See my time complexity optimized answers at:
Populate multidimensional array's rank column with dense rank number
Add order column to array to indicate rank from oldest to youngest

Creating matches with 3 arrays

I have an issue with an application that I'm developing for a car pooling program in my company (the process is kind of complex). What I want to do is the follwoing:
I have 3 teams of 3 people, each team has a unique member id for instance:
Group 1 = (1,2,3,4)
Group 2 = (5,6,7,8)
Group 3 = (9,10,11,12)
The idea is to make as many combinations as possible of 2 members (I think it is at least 8 per member) without matching with someone from the same group.
For example
1-5
1-6
1-7
1-8
1-9
1-10
1-11
1-12
2-5
2-6
2-7
2-8
2-9
...
and so on
This is a code snippet (it might not have sense with what I want to achieve but I'm a junior programmer)
<?php
$numberSet = array( range(1,4),
range(5,8),
range(9,12)
);
$sizeofArray=count($numberSet);
for ($i=0; $i<$sizeofArray; $i++){
for ($j=0; $j<count($numberSet[$i]); $j++){
for ($k=0; $k<count($numberSet[$i]); $k++){
echo $numberSet[$i][$j] . "<br>";
}
}
}
?>
If you clear up what it is you actually want to achieve, it maybe a little more help, but to be going on with, here is one way to get all the matches for a member of one group, without matching it to anyone from its own group - I will assume you plan on having multiple ID's and not a simple 1234, 5678, 9 10 11 12 in your working set:
// Build an example array:
$numberSet = array( range(1,4),
range(5,8),
range(9,12) );
// The function will return an array of matches when passed the array and the ID:
function findCombos($id, $set)
{
// Store the matches found:
$matches = array();
// Loop through each array in the multidimensional array which was passed:
foreach ($set as $group)
{
// Make sure the ID passed isn't a member of the current array, don't want its matches:
if (!in_array($id, $group))
{
// Loop through each array as the ID isn't a member of this group:
foreach ($group as $member)
{
// Add the match the the matches array:
$matches[] = $member;
}
}
}
// Pass the matches back:
return $matches;
}
Finally looking for a single users matches:
// Find all the matches for ID 2 from the multidimensional array:
$matches = findCombos("2", $numberSet);
// Display the nubmer of matches:
echo "Found ".count($matches)." matches for 2.<br/>";
// Loop through each match found:
foreach ($matches as $match)
{
// Display the results:
echo "2 - ".$match."<br/>";
}
Results:
Found 8 matches for 2.
2 - 5
2 - 6
2 - 7
2 - 8
2 - 9
2 - 10
2 - 11
2 - 12
If you wanted to show all possibilities you could do something like this:
$count = 0;
foreach ($numberSet as $group)
{
foreach ($group as $member)
{
$matches = findCombos($member, $numberSet);
$count = $count+count($matches);
foreach ($matches as $match)
{
echo $member." - ".$match.", ";
}
}
}
echo "<br/>Found ".$count." possible combinations.";
Results:
1 - 5, 1 - 6, 1 - 7, 1 - 8, 1 - 9, 1 -
10, 1 - 11, 1 - 12, 2 - 5, 2 - 6, 2 -
7, 2 - 8, 2 - 9, 2 - 10, 2 - 11, 2 -
12, 3 - 5, 3 - 6, 3 - 7, 3 - 8, 3 - 9,
3 - 10, 3 - 11, 3 - 12, 4 - 5, 4 - 6,
4 - 7, 4 - 8, 4 - 9, 4 - 10, 4 - 11, 4
- 12, 5 - 1, 5 - 2, 5 - 3, 5 - 4, 5 - 9, 5 - 10, 5 - 11, 5 - 12, 6 - 1, 6 -
2, 6 - 3, 6 - 4, 6 - 9, 6 - 10, 6 -
11, 6 - 12, 7 - 1, 7 - 2, 7 - 3, 7 -
4, 7 - 9, 7 - 10, 7 - 11, 7 - 12, 8 -
1, 8 - 2, 8 - 3, 8 - 4, 8 - 9, 8 - 10,
8 - 11, 8 - 12, 9 - 1, 9 - 2, 9 - 3, 9
- 4, 9 - 5, 9 - 6, 9 - 7, 9 - 8, 10 - 1, 10 - 2, 10 - 3, 10 - 4, 10 - 5, 10
- 6, 10 - 7, 10 - 8, 11 - 1, 11 - 2, 11 - 3, 11 - 4, 11 - 5, 11 - 6, 11 -
7, 11 - 8, 12 - 1, 12 - 2, 12 - 3, 12
- 4, 12 - 5, 12 - 6, 12 - 7, 12 - 8,
Found 96 possible combinations.
If you chenage $numberSet to:
$numberSet = array( array("a","b"),
array("c", "d", "e", "f"),
array("joe", "tom", "same")
);
The result:
a - c, a - d, a - e, a - f, a - joe, a
- tom, a - same, b - c, b - d, b - e, b - f, b - joe, b - tom, b - same, c -
a, c - b, c - joe, c - tom, c - same,
d - a, d - b, d - joe, d - tom, d -
same, e - a, e - b, e - joe, e - tom,
e - same, f - a, f - b, f - joe, f -
tom, f - same, joe - a, joe - b, joe -
c, joe - d, joe - e, joe - f, tom - a,
tom - b, tom - c, tom - d, tom - e,
tom - f, same - a, same - b, same - c,
same - d, same - e, same - f,
You might want to take a look at array_diff(). I could see something like this working:
$everyone=range(1,12);
$groups=array(range(1,4), range(5,8), range(9,12));
$cnt=count($groups);
for($i=0;$i<$cnt;$i++) {
// this will give you all the people who aren't in your group
$diff=array_diff($everyone,$groups[$i]);
// loop and compare here
}
What wasn't clear to me is if the pair "1-5" and "5-1" are the same or not i.e. you need them to be unique pairs.
if it's only calculating pairs of 2 (and no higher), you can simply count the other two arrays.
for anyone in array1, simply count(array2) + count(array3) = number of pairs

Seasonal Pricing for an Entity.

I am trying to manage seasonal prices for hotel rooms.
The only way that I can think of doing it would be to use:
A = Room Rate
B = Service Charge for room
Imagine that the table has a roomId column which is omited from below.
| DayDate |EndDate | A | B
-----------------------------------------------
| 2010/07/1 |2010/07/2 | 200 | 40
| 2010/07/3 |2010/07/4 | 150 | 40
| 2010/07/5 |2010/07/5 | 150 | 50
| 2010/07/6 |2010/07/7 | 200 | 50
| 2010/07/8 |2010/07/9 | 100 | 60
etc.. (table taken from another question).
The problem is: I don't want my seasons to be year specific.
Seasons for rooms shouldn't change year on year. I don't want my users to have to enter the seasonal information several times.
I am also going to have thousands of rooms, so I don't know a way to make this easily manageable.
I'm using mysql and php.
Start with a season table that defines the date ranges for the seasons. It should have a primary key field, say season_id. Then have another table to store room, price and season_id. The season_id is a foreign key to the season table.
Create Table Prices
(
MonthStart int not null
, DayStart int not null
, MonthEnd int not null
, DayEnd int not null
, A int not null
, B int not null
)
Insert Prices( MonthStart, DayStart, MonthEnd, DayEnd, A, B )
Select 7, 1, 7, 2, 200, 40
Union All Select 7, 3, 7, 4, 150, 40
Union All Select 7, 5, 7, 5, 150, 50
Union All Select 7, 6, 7, 7, 200, 50
Union All Select 7, 8, 7, 9, 100, 60
It should be noted that this approach presumes that the boundaries of the seasons are specific to the month and day regardless of year or circumstance. In addition, you'll have to decide how to handle leap year. Another approach which might be simpler is to simply enumerate every day of the year:
Create Table Prices
(
MonthStart int not null
, DayStart int not null
, A int not null
, B int not null
, Constraint PK_Prices Primary Key ( MonthStart, DayStart )
)
Insert Prices( MonthStart, DayStart, A, B )
Select 7, 1, 200, 40
Union All Select 7, 2, 200, 40
Union All Select 7, 3, 150, 40
Union All Select 7, 4, 150, 40
Union All Select 7, 5, 150, 50
Union All Select 7, 6, 200, 50
Union All Select 7, 7, 200, 50
Union All Select 7, 8, 100, 60
Union All Select 7, 9, 100, 60

Categories