Select rows from multiple tables based on date - php

So I am trying to select multiple rows from multiple tables based on date. So there are comments, votes and reviews. Normally I would grab 10 votes based on date, then 10 comments based on date, then 10 reviews. However I want to do this all at once so they are sorted.
How can I say grab 30 (votes + reviews + comments) (all separate tables) so that I get a unique mix of comments / votes/ reviews and always the most recent 30. I assume its something like:
SELECT * from votes, comments, reviews ORDERBY 'created_at', 'DESC'

You can do it with UNION
SELECT created_at, votecomment AS comment FROM votes
UNION ALL
SELECT created_at, comment AS comment FROM comments
UNION ALL
SELECT created_at, reviewcomment AS comment FROM reviews
ORDER BY created_at DESC
LIMIT 30;

What you've got is a cartesian query. If each of those tables has (say) 100 records, then you'll end up with 100x100x100 = 1,000,000 returned records.
Try:
SELECT *
FROM (
SELECT 'votes' AS source_table, * FROM votes ORDER BY created_at DESC LIMIT 10
UNION ALL
SELECT 'comments', * FROM comments ORDER BY created_at DESC LIMIT 10
UNION ALL
SELECT 'reviews', * FROM reviews ORDER BY created_at DESC LIMIT 10
) AS required_alias
ORDER BY created_at DESC
Each of the subqueries fetches the 10 most recent records from your three 3 tables. The outer query takes those 30 rows, and re-sorts them so those 30 rows are again in date/time ordering.
The fixed string votes/comments/reviews stuff is simply so you can identify WHICH table the individual records came from afterwards.
And of course, this will only work if the three tables have identical structures. If they have differing numbers of fields and/or the types differ, the the union will fail.

This is one way to return the specified resultset:
SELECT v.created_at, v.fee AS fee, v.fi AS fi FROM votes v
UNION ALL
SELECT c.created_at, c.fo AS fee, '' AS fi FROM comments c
UNION ALL
SELECT r.created_at, r.fum AS fee, r.foo AS fi FROM reviews r
ORDER BY 1 DESC
LIMIT 30
Note that the number of columns returned by each query must match, and the datatypes returned by each query must match.
(To make the columns "line up", we can include extra expressions in the SELECT list, like shown in the query from the comments table above. An expression can also do datatype conversions as well. The aliases assigned to the columns in the second and third queries don't matter, the names of the columns in the resultset are derived from the first select. They are included in the example above just as a demonstration of getting columns "line up"... the aliases aren't important.)

Related

How can I get first 5 and last 1 records from table mysql?

I'm working on php small project, here I need first 5 records from beginning of records and last record 1 from end of the table's record. I don't know how to write a single mysqli query.
Any help will be appreciated. Thanks in advance.
SQL tables represent unordered sets. So, there is no such thing as the first five rows or last row -- unless a column explicitly defines the ordering.
Often, a table has some sort of auto-incremented id column, which can be used for this purpose. If so, you can do:
(select t.*
from t
order by id asc
limit 5
) union all
(select t.*
from t
order by id desc
limit 1
);
Notes:
Sometimes, an insert date/time column is the appropriate column to use.
You want to use union all rather than union -- unless you want to incur the overhead of removing duplicate values.
For this formulation, if there are fewer than 6 rows, then you will get a duplicate.
The UNION operator allows this, carefully toying with the ORDER BY and LIMIT clauses :
(SELECT * FROM table ORDER BY field ASC LIMIT 5)
UNION
(SELECT * FROM table ORDER BY field DESC LIMIT 1)

MySQL ORDER BY "count(*) and 2 other parameters" from another table

I have been struggling with this problem for about month..
Have been searching and reading many posts, but still can't figure out, how to make this work..
Basically: I got 2 database tables fun_posts and fun_post_upvotes And I want to
SELECT *
FROM fun_posts
ORDER BY (HOTTEST POSTS(MOST UPVOTED THIS WEEK))
This is my latest code, that won't work
SELECT *
FROM fun_posts
ORDER BY (SELECT count(*), image_id, date
FROM fun_post_upvotes
GROUP BY image_id
ORDER BY DATE(date) > (NOW() - INTERVAL 7 DAY) DESC,
count(*) DESC,
date DESC)
If I divide this line into 2 different SELECT functions, they work. I can select simple posts and I can select upvotes count ordered like I want.
But If I make them into one line like that, I get following error:
#1241 - Operand should contain 1 column(s)
EDIT NR 1:
fun_posts table
fun_post_upvotes table
Problem with Answer that I checked:
Here, look how posts are ordered in my select function. (It selects like I want) 10->134->132->2->13
And here with given code (It selects image, but not in that order) 10->122->39->8->110
You can use a join to do this
SELECT fp.*, fpu.`cnt`
FROM fun_posts fp
LEFT JOIN ( SELECT image_id, COUNT(*) AS cnt
FROM fun_post_upvotes
WHERE `date` > (NOW() - INTERVAL 7 day)
GROUP BY image_id
) fpu ON ( fpu.image_id = fp.id )
ORDER BY fpu.cnt DESC, fp.`date` DESC, fp.`id` DESC;
It selects a list from fun_post_upvotes grouped by image_id and counts the amount of rows. That list is returned to the main query and matches (LEFT JOIN) on fp.id. The query will first show the item with the most upvotes in the past 7 days, than the least. If no upvotes are found, the result will still return them, but at the bottom in no specific order.
You can edit the order by, to obtain the items in the order you like.
Here a sqlfiddle.com

How to structure this SQL query?

So basically I'm getting notifications of new content on my website. I have 4 tables -
articles
media
updates
comments
Each table has a set of its own columns (I can include these if anyone wants). There is one distinct column every table has, this is the timestamp column (a big int formatted column with data from the PHP time() function). My solution to getting the last 30 modifications is to select the first 30 rows from these 4 tables ordered by timestamp descending.
Here is the query I have so far, it doesn't work and I'm wondering if someone could help me. -
SELECT * FROM `articles`
UNION SELECT * FROM `media`
UNION SELECT * FROM `updates`
UNION SELECT * FROM `comments`
ORDER BY `timestamp` DESC
LIMIT 30
EDIT:
I was also using another query before -
SELECT * FROM `articles` ,`media` ,`updates` ,`comments`
ORDER BY `timestamp` DESC
LIMIT 30
and kept getting this error -
Column 'timestamp' in order clause is ambiguous
EDIT 2
I realise now I have to use the AS clause in my statement to combine these results into one table.
SELECT a.*,m.*,u.*,c.* from articles AS a
LEFT JOIN media AS m ON (m.timestamp = a.timestamp)
LEFT JOIN updates AS u ON (u.timestamp = a.timestamp)
LEFT JOIN comments AS c ON (c.timestamp = a.timestamp)
ORDER BY timestamp desc LIMIT 30
Your union can work, but only if you can create some sort of common field list. For example, lets say you have a description field in each table, with different names. Something like this will work...
SELECT TimeStamp,'Articles',Art_desc AS Description FROM articles
UNION ALL
SELECT TimeStamp,'Media',Media_Desc FROM Media
UNION ALL
SELECT TimeStamp,'Updates',Update_Desc FROM Updates
UNION ALL
SELECT TimeStamp,'Comments',Comment FROM Comments
ORDER BY timeStamp DESC LIMIT 30
In essence, you are creating result sets of 3 consistent columns, so UNION will work in this case.

want to fetch first 9 records and make one more record which will be as “Others” in mysql

I am fetching records from one table with count of one field with other field as name. I want only 10 records. out of which 9 records give me field and its count. but i want to show 10 record as "Others" with all remaining fields with count. This is something like wrapping records.
Something like below will be table contents.
emp_id | designation
1 | software Engg.
2 | software Engg.
3 | Project Manager
not less than 10 designation.
And I want to show first 10 records as
Software Engineers 20
Project Manager 5
....
....
....
Others 50
Is there any way to make SQL Query for mysql db. So that it will be fast and save time in application level where I am adding up record counts for "Others". Or Suggest me how I can make it possible in effective way.
Try this:
SELECT IF(rowNum <= 9, designation, 'Other') designation, SUM(cnt)
FROM (SELECT designation, COUNT(*) cnt, (#row := #row + 1) rowNum
FROM contents, (SELECT #row := 0) dm
GROUP BY designation
ORDER BY designation
) d
GROUP BY IF(rowNum <= 9, designation, 'Other')
Change the GROUP BY and ORDER BY however you decide what 9 to show.
you can use SQL_CALC_FOUND_ROWS option in your query which will tell MySQL to count total number of rows disregarding LIMIT clause. You still need to execute a second query in order to retrieve row count, but it’s a simple query and not as complex as your query which retrieved the data.
Usage is pretty simple. In you main query you need to add SQL_CALC_FOUND_ROWS option just after SELECT and in second query you need to use FOUND_ROWS() function to get total number of rows. Queries would look like this:
SELECT SQL_CALC_FOUND_ROWS name, email FROM users WHERE name LIKE 'a%' LIMIT 10;
SELECT FOUND_ROWS();

Get multiple GROUP BY results per group, or use separate concatenated table

I am working on an auction web application. Now i have a table with bids, and from this table i want to select the last 10 bids per auction.
Now I know I can get the last bid by using something like:
SELECT bids.id FROM bids WHERE * GROUP BY bids.id ORDER BY bids.created
Now I have read that setting an amount for the GROUP BY results is not an easy thing to do, actually I have found no easy solution, if there is i would like to hear that.
But i have come up with some solutions to tackle this problem, but I am not sure if i am doing this well.
Alternative
The first thing is creating a new table, calling this bids_history. In this table i store a string of the last items.
example:
bids_history
================================================================
auction_id bid_id bidders times
1 20,25,40 user1,user2,user1 time1,time2,time3
I have to store the names and the times too, because I have found no easy way of taking the string used in bid_id(20,25,40), and just using this in a join.
This way i can just just join on auction id, and i have the latest result.
Now when there is placed a new bid, these are the steps:
insert bid into bids get the lastinserteid
get the bids_history string for this
auction product
explode the string
insert new values
check if there are more than 3
implode the array, and insert the string again
This all seems to me not a very well solution.
I really don't know which way to go. Please keep in mind this is a website with a lot of bidding's, they can g up to 15.000 bidding's per auction item. Maybe because of this amount is GROUPING and ORDERING not a good way to go. Please correct me if I am wrong.
After the auction is over i do clean up the bids table, removing all the bids, and store them in a separate table.
Can someone please help me tackle this problem!
And if you have been, thanks for reading..
EDIT
The tables i use are:
bids
======================
id (prim_key)
aid (auction id)
uid (user id)
cbid (current bid)
created (time created)
======================
auction_products
====================
id (prim_key)
pid (product id)
closetime (time the auction closses)
What i want as the result of the query:
result
===============================================
auction_products.id bids.uid bids.created
2 6 time1
2 8 time2
2 10 time3
5 3 time1
5 4 time2
5 9 time3
7 3 time1
7 2 time2
7 1 time3
So that is per auction the latest bids, to choose by number, 3 or 10
Using user variable, and control flow, i end up with that (just replace the <=3 with <=10 if you want the ten auctions) :
SELECT a.*
FROM
(SELECT aid, uid, created FROM bids ORDER BY aid, created DESC) a,
(SELECT #prev:=-1, #count:=1) b
WHERE
CASE WHEN #prev<>a.aid THEN
CASE WHEN #prev:=a.aid THEN
#count:=1
END
ELSE
#count:=#count+1
END <= 3
Why do this in one query?
$sql = "SELECT id FROM auctions ORDER BY created DESC LIMIT 10";
$auctions = array();
while($row = mysql_fetch_assoc(mysql_query($sql)))
$auctions[] = $row['id'];
$auctions = implode(', ', $auctions);
$sql = "SELECT id FROM bids WHERE auction_id IN ($auctions) ORDER BY created LIMIT 10";
// ...
You should obviously handle the case where, e.g. $auctions is empty, but I think this should work.
EDIT: This is wrong :-)
You will need to use a subquery:
SELECT bids1.id
FROM ( SELECT *
FROM bids AS bids1 LEFT JOIN
bids AS bids2 ON bids1.created < bids2.created
AND bids1.AuctionId = bids2.AuctionId
WHERE bid2.id IS NULL)
ORDER BY bids.created DESC
LIMIT 10
So the subquery performs a left join from bids to itself, pairing each record with all records that have the same auctionId and and a created date that is after its own created date. For the most recent record, there will be no other record with a greater created date, and so that record would not be included in the join, but since we use a Left join, it will be included, with all the bids2 fields being null, hence the WHERE bid2.id IS NULL statement.
So the sub query has one row per auction, contianing the data from the most recent bid. Then simply select off the top ten using orderby and limit.
If your database engine doesn't support subqueries, you can use a view just as well.
Ok, this one should work:
SELECT bids1.id
FROM bids AS bids1 LEFT JOIN
bids AS bids2 ON bids1.created < bids2.created
AND bids1.AuctionId = bids2.AuctionId
GROUP BY bids1.auctionId, bids1.created
HAVING COUNT(bids2.created) < 9
So, like before, left join bids with itself so we can compare each bid with all the others. Then, group it first by auction (we want the last ten bids per auction) and then by created. Because the left join pairs each bid with all previous bids, we can then count the number of bids2.created per group, which will give us the number of bids occurring before that bid. If this count is < 9 (because the first will have count == 0, it is zero indexed) it is one of the ten most recent bids, and we want to select it.
To select last 10 bids for a given auction, just create a normalized bids table (1 record per bid) and issue this query:
SELECT bids.id
FROM bids
WHERE auction = ?
ORDER BY
bids.created DESC
LIMIT 10
To select last 10 bids per multiple auctions, use this:
SELECT bo.*
FROM (
SELECT a.id,
COALESCE(
(
SELECT bi.created
FROM bids bi
WHERE bi.auction = a.id
ORDER BY
bi.auction DESC, bi.created DESC, bi.id DESC
LIMIT 1 OFFSET 9
), '01.01.1900'
) AS mcreated
COALESCE(
(
SELECT bi.id
FROM bids bi
WHERE bi.auction = a.id
ORDER BY
bi.auction DESC, bi.created DESC, bi.id DESC
LIMIT 1 OFFSET 9
), 0)
AS mid
FROM auctions a
) q
JOIN bids bo
ON bo.auction >= q.auction
AND bo.auction <= q.auction
AND (bo.created, bo.id) >= (q.mcreated, q.mid)
Create a composite index on bids (auction, created, id) for this to work fast.

Categories