How do I get distinct rows by a column? - php

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!

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.

SELECT Query by avoiding a specific row

I have a table with many columns. Each row contains a unique ID field. How can I select 5 rows without selecting a row with id stored in variable $doNot.
Currently I am using the following query.
$qry="SELECT title, link FROM posts WHERE id > (SELECT MAX(id) - 5 FROM news)";
Consider this table:
id | title | link
_____________________________________
1 | title_1 | link_1
2 | title_2 | link_2
3 | title_3 | link_3
4 | title_4 | link_4
5 | title_5 | link_5
6 | title_6 | link_6
7 | title_7 | link_7
8 | title_8 | link_8
I want to select 5 rows except row with id '3' using the above query from the above table
SELECT title, link FROM posts WHERE NOT (id = $doNot) LIMIT 5
Use LIMIT to specify the number of rows to be returned:
SELECT title, link
FROM posts
ORDER BY ID DESC
LIMIT 5
If $doNot is a table variable, you should be able to simply sub-query on posts.id:
SELECT title, link FROM posts WHERE id NOT IN (SELECT id FROM $doNot) LIMIT 5;

PHP MySQLi how to get the most repeated value from a table

There's a little thing I'm stuck with. I've a table in my database like:
id | AuthorId | Book
---------------------
1 | 2 | book name
2 | 2 | book name
3 | 2 | book name
4 | 2 | book name
5 | 5 | book name
6 | 5 | book name
7 | 8 | book name
8 | 9 | book name
9 | 9 | book name
10 | 6 | book name
As you can see, Author ID "2" is repeated the highest times in this table ( 4 times ) while the author Author IDs appeared less then 4 times. How can I get the ID "2" from this table using php MySQLi, which is the most frequently occurring value in this data? I've no idea how can I do this.
Thank you very much.
Try the following query. You count rows grouped by AuthorID DESC , and limiting the result to the top value.
SELECT AuthorId, COUNT(AuthorId) as c from table
GROUP BY AuthorId
ORDER BY COUNT(AuthorId) DESC
LIMIT 1;
Try this
select AuthorId , count(AuthorId ) as max
from table_name
group by AuthorId order by max desc limit 1;
The order by max desc is for ordering the max value at the first row. The limit 1 is from getting only the first row

how to display mysql latest entries from each category

we re using CodeIgniter, we want to get 6 categories latest 10 entries by (published_date), then we will display different categories results in view. here we are using 6 colourful boxes to display 10 latest entries from each category. our SQL tables are look like this ...
-> ci_categories
cat_id | cat_name | cate_slug | cate_title
-> ci_pages
page_id | cat_id | page_title | published_date
--------------------------------------
1 | 1 | Ttl 1 | 2014-02-22 10:22:20
2 | 2 | Ttl 2 | 2014-02-24 11:42:30
3 | 1 | Ttl 3 | 2014-02-26 10:37:21
4 | 3 | Ttl 3 | 2014-02-28 12:40:30
SELECT `ci_pages`.`cat_id` AS CAT_ID,
(SELECT `cat_name` FROM `ci_categories` WHERE `ci_categories`.`cat_id` = `ci_pages`.`cat_id`) AS CAT_NAME,
(SELECT `cat_slug` FROM `ci_categories` WHERE `ci_categories`.`cat_id` = `ci_pages`.`cat_id`) AS CAT_SLUG,
(SELECT `cat_title` FROM `ci_categories` WHERE `ci_categories`.`cat_id` = `ci_pages`.`cat_id`) AS CAT_TITLE
FROM `ci_pages`
ORDER BY `ci_pages`.`published_date` DESC
LIMIT 10;
This will return the 10 last inserted values into "ci_pages" Table getting each Category's details from "ci_pages" table. Check now...
try this query..
select category.*,pages.* from ci_categories category join ci_pages pages on (category.cat_id = pages.cat_id) group by pages.cat_id order_by pages.published_date DESC limit 0,10

SQL query to select only the maximum items?

I have this table: I want to search by UID
ID | VID | UID
1 | 1 | 5
1 | 1 | 6
1 | 2 | 6
2 | 3 | 5
2 | 3 | 6
2 | 4 | 6
I want to end up with this result:
ID | VID | UID
1 | 2 | 6
2 | 4 | 6
In other words, only select the entries where the VID is MAX of the UID but keeping in min NID could differ. Something like this I suppose:
select * from TABLE where uid = 6 and max(vid)
???
But this doesn't work?
One way is to order by the value in descending order (so the max is at the top), then just select the first result.
SELECT t.ID,
t.VID,
t.UID
FROM table t
WHERE t.ID = 1
ORDER BY t.VID DESC
LIMIT 1
Or do you mean you want all rows where t.VID is the highest value? In which case you could do something like this,
SELECT t.ID,
t.VID,
t.UID
FROM table t
WHERE t.ID = 1
AND t.VID = (SELECT MAX(VID) FROM table);
EDIT: Based on the edit to your question, it looks like you just want the max VID value for each ID? If I'm understanding you correctly, then this should give you what you need.
SELECT t.ID,
max(t.VID) as VID,
t.UID
FROM table t
WHERE t.UID = 6
GROUP BY t.ID
You need to have a subquery. This should work:
select * from TABLE where ID='1' AND VID=(select max(VID) from TABLE)
I expect your real-life example is more complicated (at least has more data).
This query will give you the row you want.
SELECT id,vid, uid
FROM TABLE
where id = 1
and vid in (select max(vid) from TABLE where id = 1)

Categories