Check value of last 4 rows - php

I want to check the value of the last 4 rows from DB. If these 4 rows have a specific value, Do something.
So the table looks like this:
______________
| id | value |
|____|_______|
| 1 | a |
|____|_______|
| 2 | a |
|____|_______|
| 3 | a |
|____|_______|
| 4 | a |
|____|_______|
| 5 | a |
|____|_______|
| 6 | a |
|____|_______|
Here is a fiddle to test with: http://sqlfiddle.com/#!9/1064b3/1
I can run the following query SELECT * FROM testing ORDER BY id DESC LIMIT 4, Then check with PHP:
//Count number of `a` from last 4 rows
$count = 0;
foreach($rows as $row){
if($row['value'] == 'a'){
$count++;
}
}
//If last 4 rows value = 'a'
if($count == 4){
//Do something
}
Is there is a better way with SQL to get the rows only if the 4 rows value = a or return true or false maybe?

Try this:
SELECT COUNT(a.value)
FROM(
SELECT value
FROM testing
ORDER BY id DESC LIMIT 4
) AS a
WHERE a.value = 'a'
If 3 is returned, it's TRUE (You have one not equal to A), FALSE otherwise.

A multitude of ways, you could just use simple aggregation to count the rows:
select case when
count(case when value='a' then 1 end)=Count(*)
then 'true' else 'false'
end Last4AreA
from (
select value
from t
order by id desc
limit 4
)x

Related

Repeat query when conditions aren't met

I am making a kindof quiz. The quiz has 15 questions, for the quiz I need 5 questions of quiztype '1', 5 of quizType '2' and 5 of quizType '3'. Right now I'm counting quiztype '1'and quiztype '2' trough a loop and if conditions outside the loop aren't met, I get 15 new entry's and repeat the loop. I'm wondering, is there a better way to do this inside my query instead of using 2 objects?
This Is my code:
public function checkVariety($quizType, $data)
{
$i=0;
$i2=0;
foreach($quizType as $type) {
if ($type=='1') {
$i++;
}
if ($type=='2') {
$i2++;
}
}
if($i=='5' AND $i2=='5') {
$this->startQuiz($data);
return true;
} else {
$this->getRandom();
return false;
}
}
public function getRandom()
{
$stmt = $this->db->prepare("
SELECT id, quiz_type
FROM quiz
ORDER BY rand()
LIMIT 15
");
$stmt->execute();
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$quizType[] = $row['quiz_type'];
$data[] = $row['id'];
}
$this->checkVariety($quizType, $data);
return true;
}
You could also combine this way.
The UNION was easily avoided by noting the difference in the SELECT statements was just to pick form values 1, 2, and 3. In SQL, this is easily done with form IN (1, 2, 3).
The problem with this is we can't easily use LIMIT 5, as you originally did, since all 15 rows are now in the same result.
This is where window functions comes into play. We can now process these rows using window specifications to isolate and operate on groups (by partition) of rows.
The example below is ROW_NUMBER() OVER (PARTITION BY form ORDER BY rand()) AS seq.
In short, this derives a new column (see: derived column), the contents of which is the position (row number) of this row within the group of rows with a matching form value (indicated in the PARTITION BY terms) and in the order specified by the ORDER BY terms of the OVER clause.
Your requirement is complicated slightly by the needed random order. It's not as easy to see how this window function use provides this nice row number ordering. You can test this by replacing the rand() term with something more recognizable ORDER BY exercise, which is the column I chose to represent some exercise identifier.
The WITH clause or Common Table Expression - CTE term is like a derived table or view, but provides more capability, like recursion. We can access it similar to any VIEW, Derived Table, base table, etc.
In the following CTE term, we select all the rows matching the 3 forms, and assign / generate a new seq column containing a row number (1 to n, within each partition), so that later we can just take seq <= 5 to limit the result to just the first 5 rows in each partition (form).
WITH cte AS (
SELECT *
, ROW_NUMBER() OVER (PARTITION BY form ORDER BY rand()) AS seq
FROM exercises
WHERE form IN (1, 2, 3)
)
SELECT * FROM cte
WHERE seq <= 5
ORDER BY form, seq
;
Result with test data:
+----------+------+-----+
| exercise | form | seq |
+----------+------+-----+
| 15 | 1 | 1 |
| 8 | 1 | 2 |
| 10 | 1 | 3 |
| 16 | 1 | 4 |
| 6 | 1 | 5 |
| 29 | 2 | 1 |
| 24 | 2 | 2 |
| 26 | 2 | 3 |
| 20 | 2 | 4 |
| 25 | 2 | 5 |
| 41 | 3 | 1 |
| 46 | 3 | 2 |
| 47 | 3 | 3 |
| 40 | 3 | 4 |
| 51 | 3 | 5 |
+----------+------+-----+
I got it partially working thanks to the UNION method.
$stmt = $this->db->prepare("
SELECT *
FROM (SELECT * FROM exercises as e1 WHERE e1.form='1' ORDER BY rand() LIMIT 5) as f
UNION
SELECT *
FROM (SELECT * FROM exercises as e1 WHERE e1.form='2' ORDER BY rand() LIMIT 5) as f2
UNION
SELECT *
FROM (SELECT * FROM exercises as e1 WHERE e1.form='3' ORDER BY rand() LIMIT 5) as f3
ORDER BY rand()
");
$stmt->execute();
still having some problems though, but I will try to figure that out on my own first, and if I eventually need to, open another question.

Mysql : How to Select SUM of a column based of a condition value from another table?

So, I have this scenario of a quiz where questions are attached to groups and so when someone do a quiz, first an attempt is stored in a table and then as person does quiz e.g of 3 questions, its points are recorded with points, question, group and quiz attempt id in a table. Here are my table structure:
Table attempts
id | status ( 0 for not complete, 1 for complete )
1 | 1
2 | 1
3 | 0
Table groups
id | title
1 | Group 1
2 | Group 2
3 | Group 3
Table points
id | attempt_id | group_id | points
1 | 1 | 2 | 5
2 | 1 | 2 | 5
3 | 1 | 1 | 5
4 | 2 | 3 | 5
5 | 2 | 2 | 5
6 | 2 | 3 | 5
7 | 3 | 1 | 5
8 | 3 | 3 | 5
I need to show the group list with sum of each group from points table infront of them but only from the completed attempts from attempts table:
Here's how I am trying:
$query = mysqli_query($conn, "SELECT * FROM `groups` order BY `id` ASC");
while ( $row = mysqli_fetch_array($query) ) {
$group_id = $row["id"];
$gpc = mysqli_query($conn, "SELECT SUM(`points`) as TotalPoints FROM `points` WHERE `attempt_id` IN (SELECT `id` FROM `attempts` WHERE `status`=1) AND `group_id`=".$group_id);
$gpcrow = mysqli_fetch_assoc($gpc);
echo $row["title"]." : ".$gpcrow["TotalPoints"];
}
Result should be:
Group 1 : 5
Group 2 : 15
Group 3 : 10
But its not giving me exact results as its giving me results from all complete and incomplete attempts. But I need results only from completed attempts in group listing shape.
You don't need the loop at all, or in this case at least prepared statements
<?php
if (
$result = mysqli_query(
$mysqli,
"SELECT MIN(g.title) title,SUM(points)
FROM points p INNER JOIN `groups` g ON p.group_id = g.id
WHERE `attempt_id`IN (SELECT id FROM attempts WHERE status = 1) GROUP BY `group_id`"
)
) {
// Fetch all
$gpcrow = mysqli_fetch_all($result, MYSQLI_ASSOC);
print_r($gpcrow);
// Free result set
// Free result set
mysqli_free_result($result);
}
?>
See example

How do I get distinct rows by a column?

I have a huge number of rows that I'd like to get say, last 5 records inserted in that database from 10 different users. If the same user inserted the last 3 rows into database, we must get one row, skip the others two and move to get a row per user, until it count up to 5.
A database like that:
user_id | news_id | title
1 | 1 | foo-1
2 | 2 | foo-2
3 | 3 | foo-3
1 | 4 | baa
4 | 5 | baa0
5 | 6 | baa1
5 | 7 | baa2
6 | 8 | baa3
7 | 9 | baa4
Should return:
user_id | news_id | title
1 | 1 | foo-1
2 | 2 | foo-2
3 | 3 | foo-3
4 | 5 | baa0
5 | 6 | baa1
The current filter was done by PHP, like this:
$used = array();
while ($data = mysql_fetch_array($query)) {
$uid = $data['user_id'];
if(in_array($uid, $used))
continue;
array_push($used, $uid);
// do something with data
}
But I want to refactor it, and do the filter purely by mysql, if possible. I don't know much MySql and that's why I'm having problem to archive this...
Here's what I've tried
select DISTINCT(user_id), news_id, title from XXX
WHERE GROUP BY (news_id) DESC
LIMIT 0,5
How can I do that?
1 way you can do it is to generate a partitioned row number per user and then select 5 records where RowNumber = 1.
SELECT *
FROM
(
SELECT
d.user_id
,d.news_id
,d.title
,(#rn:= if(#uid = user_id, #rn + 1,
if(#uid:=user_id,1,1)
)
) as RowNumber
FROM
Data d
CROSS JOIN (SELECT #uid:=-1, #rn:=0) vars
ORDER BY
user_id
,news_id
) t
WHERE
t.RowNumber = 1
ORDER BY news_id
LIMIT 5;
http://rextester.com/JRIZI7402 - example to show it working
Note you can change the row order by simply changing the ORDER BY statement of the derived table so if you have a column that will signify the latest record e.g. an identity column or a datetime column you can use that, but user_id must be the first criteria to be partitioned correctly.
Do it from your query.
"SELECT * FROM table GROUP BY user_id ORDER BY news_id DESC LIMIT 5"
well, i think this will achieve what you are after.
select user_id, news_id, title from tableName
GROUP BY user_id
ORDER BY news_id DESC
LIMIT 0,5
Hope this helps!

circular/round loop based on ID (start with specific ID add the rest of the ID, break when specific ID reached)

Consider I have array of IDs:
1
2
3
4
5
6
7
8
Let's say I can fetch an array starting with an ID of specific number, let's say 5.
mysql_query("SELECT * FROM farmlist WHERE id > 4 ORDER BY id")
it should give me array of form:
5
6
7
8
Question: Is there a way after reaching the last ID, it would add to array the IDs from the smallest until the ID of where we start, so that we have:
5
6
7
8
1 <~ Add
2 <~ Add
3 <~ Add
4 <~ Add
Try this query:
mysql_query("SELECT * FROM farmlist WHERE id > 4 UNION ALL SELECT * FROM farmlist WHERE id <= 4");
select id from farmlist
order by
(select if( id > 4, id - 4, id + (select max(id) + 1 from farmlist)))
pseudo-code:
res1 = mysql_query("SELECT * FROM farmlist WHERE id > 4 ORDER BY id");
res2 = mysql_query("SELECT * FROM farmlist WHERE id <= 4 ORDER BY id");
array_merge(res1, res2);
and the final res would be in res1
Consider this:
$ids = array(1,2,3,4,5,6,7,8);
$arr = array(5,6,7,8);
sort($ids);
sort($arr);
$min = $arr[0];
foreach($ids as $id){
if($id>=$min) break;
$arr[]=$i;
}
//array will now be (5,6,7,8,1,2,3,4)
That what you wanted?
With mysql this could be done as
mysql> create table test (id int);
Query OK, 0 rows affected (0.10 sec)
mysql> insert into test values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10);
Query OK, 10 rows affected (0.03 sec)
Records: 10 Duplicates: 0 Warnings: 0
mysql> select * from test order by case when id >=5 then 0 else 1 end , id ;
+------+
| id |
+------+
| 5 |
| 6 |
| 7 |
| 8 |
| 9 |
| 10 |
| 1 |
| 2 |
| 3 |
| 4 |
+------+
The above query could be even expanded in a range, lets say I want data from 5 till 7 on the top and rest all after that and this could be done as
mysql> select * from test order by case when id >=5 and id<8 then 0 else 1 end , id ;
+------+
| id |
+------+
| 5 |
| 6 |
| 7 |
| 1 |
| 2 |
| 3 |
| 4 |
| 8 |
| 9 |
| 10 |
+------+

MySQL+PHP array_count_values

I need to find the most common value (not 0) from arrays.
My code:
include ("db.php");
$query = "SELECT poll1 FROM names";
$res = mysql_query($query) or die(mysql_error());
while ($row = mysql_fetch_array($res)) {
echo $row['poll1'];
}
And echo results (minimum value 0 (default) and maximum 3):
1
1
0
0
0
2
3
The most common value is "1". I cant use array_count_values, because there are 7 arrays of numbers.
This is a problem you should solve with SQL.
Firstly, you want to get the number of people selecting each option:
SELECT poll1, COUNT(*) AS count FROM names GROUP BY poll1;
+-------+-------+
| poll1 | count |
+-------+-------+
| 0 | 3 |
| 1 | 2 |
| 2 | 1 |
| 3 | 1 |
+-------+-------+
4 rows in set (0.00 sec)
Ok, but you're not interested in zeros, and you only care about the row with the largest value of count so you should sort by descending count, and limit it to 1 result:
SELECT poll1, count(*) AS count FROM names
WHERE poll1 != 0
GROUP BY poll1
ORDER BY count DESC
LIMIT 1;
+-------+-------+
| poll1 | count |
+-------+-------+
| 1 | 2 |
+-------+-------+
1 row in set (0.00 sec)

Categories