MySQL: How to SELECT only three of each differing x-column-name - php

I want to display my blogs in a very future proof way. So I need to make a query that will reference new blog types if they are created. Hence the x-column-name. In this case that's blogType. This select query should contain the blog information for all blog types, but for each blog type get 3 Blogs. It's kind of confusing so this is why I am reiterating!
I have done a bit of work already in googling how to limit the results. Shown below:
$query = 'SELECT * FROM blogs
ORDER BY dateWritten
ASC LIMIT 3';
I'll be outputting the results to blog_rss.php using an array and a foreach loop. I get the array from a function like this:
function get_Recent_Blogs() {
global $db;
$query = 'SELECT * FROM blogs
ORDER BY dateWritten
ASC LIMIT 3';
try {
$statement = $db->prepare($query);
$statement->execute();
$result = $statement->fetchAll(PDO::FETCH_ASSOC);
$statement->closeCursor();
return $result;
} catch (PDOException $e) {
$error_message = $e->getMessage();
display_db_error($error_message);
}
}
It's not too important but at least it gives you some context.
SELECT *
FROM (
SELECT b.*, ROW_NUMBER() OVER (PARTITION BY b.blogType ORDER BY b.blogID DESC) as rn
FROM blogs b
) x
WHERE x.rn <= 3
ORDER BY x.blogType, x.blogID DESC';
So I've done my best to implement the solution but I'm getting some errors. I'm not sure if I should start a new post for this one or not but the code above is what I used and this is the error I'm getting:
You have an error in your SQL syntax; it seems the error is around: '( PARTITION BY b.blogType ORDER BY b.dateWritten DESC ' at line 7

Since your MySQL version supports window functions, you can use ROW_NUMBER(), which will enumerate your entries per blog type. Then you just need to pick the first three rows per type.
SELECT *
FROM (
SELECT b.*, ROW_NUMBER() OVER (PARTITION BY b.type ORDER BY b.dateWritten DESC) as rn
FROM blogs b
) x
WHERE x.rn <= 3
ORDER BY x.type, x.dateWritten DESC -- adjust as needed
Notes:
I assume that the blog type is determined by the type column. Adjust it if needed.
You should use DESC instead of ASC, since you want to get the most recent entries.
I would rather use an AUTO_INCEMENT id column instead of dateWritten for sorting. That is more reliable, since a DATE or even a TIMESTAMP can have duplicates.
For older versions which don't support window functions I would first fetch all types and generate a UNION ALL query. With PDO it could be something like the following:
$types = $db
->query('SELECT DISTINCT type from blogs ORDER BY type')
->fetchAll(PDO::FETCH_COLUMN);
$subqueries = array_map(function($type){
return '(SELECT * FROM blogs WHERE type = ? ORDER by dateWritten DESC LIMIT 3)';
}, $types);
$query = implode(' UNION ALL ', $subqueries) . ' ORDER BY type, dateWritten DESC';
$statement = $db->prepare($query);
$statement->execute($types);
$result = $statement->fetchAll(PDO::FETCH_ASSOC);
This will generate the following query:
(SELECT * FROM blogs WHERE type = ? ORDER by dateWritten DESC LIMIT 3)
UNION ALL
...
UNION ALL
(SELECT * FROM blogs WHERE type = ? ORDER by dateWritten DESC LIMIT 3)
ORDER BY type, dateWritten DESC
Note 1: Even though two queries are executed, given a composite index on (type, dateWritten) this can still be faster than other solutions. When you have a couple of blog types and many articles per type, this can even be faster than the ROW_NUMBER() solution.
Note 2: I would usually have a separate table for types, and the blogs table would reference the primary key type_id column. In that case the first query would be SELECT type_id from blog_types, and the subqueries would have the condition WHERE type_id = ?.

Related

Select a fixed number of records from a particular user in a sql result

I have 2 tables - users and articles.
users:
user_id (int)
name (varchar)
articles:
article_id (int)
user_id (int)
title (varchar)
description (text)
In my application I need to display 20 RANDOM articles on a page.
My query is like this:
SELECT a.title
, a.description
, u.name
FROM articles a
JOIN users u
USING (user_id)
ORDER
BY RAND()
LIMIT 20
A user can have any number of articles in the database.
Now the problem is sometimes out of 20 results, there are like 9-10 articles from one single user.
I want those 20 records on the page to not contain more than 3 (or say 4) articles from a particular user.
Can I achieve this through SQL query. I am using PHP and MySQL.
Thanks for your help.
You could try this?
SELECT * FROM
(
SELECT B.* FROM
(
SELECT A.*, ROW_NUMBER() OVER (PARTITION BY A.USER_ID ORDER BY A.R) USER_ROW_NUMBER
FROM
(
SELECT a.title, a.description, u.name, RND() r FROM articles a
INNER JOIN users u USING (user_id)
) A
) B
WHERE B.USER_ROW_NUMBER<=4
) C
ORDER BY RAND() LIMIT 20
Mmm, intresting I don't think this is possible through a pure sql query.
My best idea would be to have an array of the articles that you'll eventually display query the database and use the standard SELECT * FROM Articles ORDER BY RAND() LIMIT 20
The go through them, making sure that you have indeed got 20 articles and no one has breached the rules of 3/4 per user.
Have another array of users to exclude, perhaps using their user id as an index and value of a count.
As you go through add them to your final array, if you find any user that hits you rule add them to the array.
Keep running the random query, excluding users and articles until you hit your desired amount.
Let me try some code (it's been a while since I did php)
$finalArray = [];
$userArray = [];
while(count($finalArray) < 20) {
$query = "SELECT * FROM Articles ";
if(count($finalArray) > 0) {
$query = $query . " WHERE articleID NOT IN(".$finalArray.")";
$query = $query . " AND userID NOT IN (".$userArray.filter(>4).")";
}
$query = $query . " ORDER BY Rand()";
$result = mysql_query($query);
foreach($row = mysql_fetch_array($result)) {
if(in_array($finalArray,$row) == false) {
$finalArray[] = $row;
}
if(in_array($userArray,$row[userId]) == false) {
$userArray[$row[userId]] = 1;
}
else {
$userArray[$row[userId]] = $userArray[$row[userId]] + 1;
}
}

Combine 2 SQL "Complex" Queries

I don't know if these are "complex queries" by defn, but they look very complex to a noob like me.
So I have a query here that will get the latest chart of customer_id=5:
$query = "SELECT c.Chart_ID, c.Chart_Notes
FROM tblchart AS c WHERE c.Customer_ID=5
ORDER BY c.Last_Edited ASC LIMIT 1";
But I have to relate it to another table that uses the Chart_ID as foreign key. How can I get the data from the tblcontent using tblchart.Chart_ID=tblcontent.Chart_ID? I couldn't just add that as:
$query = "SELECT c.Chart_ID, c.Chart_Notes, d.Content_Desc, d.Content_Title
FROM tblchart AS c, tblcontent AS d
WHERE c.Customer_ID=5 AND c.Chart_ID=d.Chart_ID
ORDER BY c.Last_Edited DESC LIMIT 1";
can I? As that would limit the search to just one...the use of LIMIT 1 is just to get the latest, but for the subsequent query (extended query), I am expecting multiple results extracted from tblcontent in addition to the first query I posted. A join, maybe, or union, or a complex query, but how? Please, can anyone help me? Thanks.
SELECT a.Chart_ID, a.Chart_Notes, c.Content_Desc, c.Content_Title
FROM tblChart a
INNER JOIN
(
SELECT Chart_ID, MAX(Last_edited) maxEdited
FROM tblChart
GROUP BY Chart_ID
) b ON a.Chart_ID = b.Chart_ID AND
a.Last_Edited = b.maxEdited
INNER JOIN tblcontent c
ON a.Chart_ID = c.Chart_ID
WHERE a.Customer_ID=5

Order by a SUM with multiple joins in codeigniter

Solution:
$query = $this->db->query("
SELECT *, SUM(views.times) AS sum
FROM channel
RIGHT JOIN video
ON channel.channel_id = video.channel_id
LEFT JOIN user
ON channel.user_id = user.user_id
LEFT JOIN views
ON channel.channel_id = views.channel_id
GROUP BY channel.channel_name
ORDER BY sum DESC
");
I have a query that returns a list of items.
function getfrontpage(){
$this->db->select('*');
$this->db->from('channel');
$this->db->join('video', 'channel.channel_id = video.channel_id' , 'right');
$this->db->join('user', 'channel.user_id = user.user_id');
$this->db->group_by("channel.channel_name");
$query = $this->db->get();
return $query->result();
}
Now i'm trying to order these with the SUM from another table, how can i achieve this?
Here is a picture from that table:
I want the results to be sorted by the SUM of the total of "times" for each "channel_id"
thanks in advance
I would suggest to run this through $this->db->query() instead.
It's nice to fetch simple values through CodeIgniters AR functions. But at some situations it's simply easier to build query strings instead.
In your case:
$query = $this->db->query("
SELECT channel_id, SUM(times) AS sum
FROM channel
GROUP BY channel_id
ORDER BY sum DESC
");
You can also escape most values through db->query()!
$this->db->query("
SELECT name
FROM table_name
WHERE id = ?
", array(1));
Isn't it as simple as $this->db->order_by("channel_id", "desc");? this orders the results by channel_id in descending order.
Assuming the table displayed in your question is called times_table, and has a key of user_id, channel_id, you can use the following code to join the times_table into your query so the "times" column is available to sort by.
$this->db->join("times_table", "times.user_id=channel.user_id, times.channel_id=channel.channel_id", "left");
// You've already grouped by channel_name, so grouping by channel_id is probably not necessary.
$this->db->order_by("SUM(times_table.times) DESC");
N.B. I just guessed the name of your displayed table is times_table.

mysql: group results, limit them and join to other tables in one query

i have a online application for wich i require a sort of dashboard (to use the white-space).
There are three tables used for the operation:
1.) categories: id, name
2.) entries: id, name, description, category_id, created, modified
3.) entryimages: id, filename, description, entry_id
on the dashboard i want to show 4-5 entries (with thumbnail images, so i require joins to the entryimages table and the categories table) for each category.
I read through some articles (and threads on s.o.) like this one:
http://www.xaprb.com/blog/2006/12/07/how-to-select-the-firstleastmax-row-per-group-in-sql/
But am still not getting it right, i've tried to first extract all categories and for each and every category build a query and with "all union" attach them to one, but that is not working.
The last version of code i used:
foreach($categories as $id => $name)
{
$query .= "SELECT `entry`.`id`,
`entry`.`name`,
`entry`.`description`,
`entry`.`category_id`,
`entry`.`created`,
`entry`.`modified`,
`entryimages`.`filename`,
`entryimages`.`description`
FROM `entries` as `entry` LEFT JOIN `entryimages` ON `entryimages`.`entry_id` = `entry`.`id`
WHERE `entry`.`category_id` = $id ";
if($i < count($groups))
{
$query .= 'UNION ALL ';
}
$i++;
}
$result = mysql_query($query);
Does anybody know what is the best right to accomplish this operation?
Thanks 1000
On the dashboard if you want to show three entries, the way you are doing is wrong. If my understanding is right, the entire query will be something like
"SELECT `entry`.`id`,
`entry`.`name`,
`entry`.`description`,
`entry`.`category_id`,
`entry`.`created`,
`entry`.`modified`,
`entryimages`.`filename`,
`entryimages`.`description`
FROM `entries` as `entry`
INNER JOIN categories
ON (entry.category_id = categories.id)
LEFT JOIN (SELECT * FROM `entryimages` WHERE `entry_id` = `entry`.`id` LIMIT 1) AS `entryimages`
ON `entryimages`.`entry_id` =`entry`.`id`
ORDER BY `entry`.`created` DESC LIMIT 5";
Your code looks ok to me you should just add a LIMIT clause so that you get just five of them and an ORDER BY clause to get the latest
$query .= "SELECT `entry`.`id`,
`entry`.`name`,
`entry`.`description`,
`entry`.`category_id`,
`entry`.`created`,
`entry`.`modified`,
`entryimages`.`filename`,
`entryimages`.`description`
FROM `entries` as `entry` LEFT JOIN `entryimages` ON `entryimages`.`entry_id` = `entry`.`id`
WHERE `entry`.`category_id` = $id ORDER BY `entry`.`created` DESC LIMIT 5";

Get total rows count of table

I want to get all rows count in my sql.
Table's first 2 columns look like that
My function looks like that
$limit=2;
$sql = "SELECT id,COUNT(*),dt,title,content FROM news ORDER BY dt DESC LIMIT " . $limit;
$stmt = $this->db->prepare($sql);
$stmt->execute();
$stmt->bind_result($id, $total, $datetime, $title, $content);
$stmt->store_result();
$count = $stmt->num_rows;
if ($count > 0) {
while ($stmt->fetch()) {
Inside loop, I'm getting exact value of $total, but MySQL selects only 1 row - row with id number 1. (and $count is 1 too)
Tried this sql
SELECT id,dt,title,content FROM news ORDER BY dt DESC LIMIT 2
All goes well.
Why in first case it selects only 1 row? How can I fix this issue?
for ex my table has 5 rows. I want to get 2 of them with all fields, and get all rows count (5 in this case) by one query.
Remove COUNT(*). You will only ever get 1 row if you leave it in there.
Try adding GROUP BY dt if you want to use COUNT(*) (not sure why you're using it though).
EDIT
Fine, if you insist on doing it in a single call, here:
$sql = "SELECT id,(SELECT COUNT(id) FROM news) as total,dt,title,content FROM news ORDER BY dt DESC LIMIT " . $limit;
This is likely cause by the variable $limit being set to 1, or not being set and mysql defaulting to 1. Try changing your first line to
$sql = "SELECT id,COUNT(*),dt,title,content FROM news ORDER BY dt DESC";
EDIT
Change to:
$sql = "SELECT SQL_CALC_FOUND_ROWS,id,dt,title,content FROM news ORDER BY dt DESC LIMIT " . $limit;
And then use a second query with
SELECT FOUND_ROWS( )
to get the number of rows that match the query
This totally wreaks of a HW problem... why else besides a professor's retarded method to add complexity to a simple problem would you not want to run two queries?
anyways.... here:
SELECT id, (SELECT COUNT(*) FROM news) AS row_count, dt, title, content FROM news ORDER BY dt DESC LIMIT

Categories