How to structure this SQL query? - php

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.

Related

Pagination query mysql+php take 20-40 seconds

Hi I have pagination in angular js in my app, I send the data to my big query that includes the filters that the user set
UPDATE
SQL_CALC_FOUND_ROWS this is my problem. How do I count the rows of specific filters . It is took me 2 second for 100,000 rows I need the number for the pagination as a total number
UPDATE:
I have the following inner query that I missed here :
(select count(*) from students as inner_st where st.name = inner_st.name) as names,
when I remove above inner query is much faster
rows: 50,000
Users table : 4 rows
Classes table : 4 rows
indexes: only id as primary key
query time 20-40 seconds
tables: students.
columns : id, date ,class, name,image,status,user_id,active
table user
coloumn: id,full_name,is_admin
query
SELECT SQL_CALC_FOUND_ROWS st.id,
st.date,
st.image,
st.user_id,
st.status,
st,
ck.name AS class_name,
users.full_name,
(select count(*) from students AS inner_st where st.name = inner_st.name) AS names,
FROM students AS st
LEFT JOIN users ON st.user_id = users.user_id
LEFT JOIN classes AS ck ON st.class = ck.id
WHERE date BETWEEN '2018-01-17' AND DATE_ADD('2018-01-17', INTERVAL 1 DAY)
AND DATE_FORMAT(date,'%H:%i') >= '00:00'
AND DATE_FORMAT(date,'%H:%i') <= '23:59'
AND st.active=1
-- here I can concat filters from web like "and class= 1"
ORDER BY st.date DESC
LIMIT 0, 10
How can I make it faster? when I delete the order by and SQL_CALC_FOUND_ROWS it faster but i need them
I heard about indexes but only primary key is index
Few comments before recommending a different approach to this query:
Did you consider removing SQL_CALC_FOUND_ROWS and instead running two queries (one that counts and one that selects the data)? In some cases it might be quicker than joining them both to one query.
What is the goal of these conditions? What are you trying to achieve? Can we remove them (as it seems they might always return true?) - AND DATE_FORMAT(st.date, '%H:%i') >= '00:00' AND DATE_FORMAT(st.date, '%H:%i') <= '23:59'
You only need 10 results, but the database will have to run the "names" subquery for each of the results before the LIMIT (which might be a lot?). Therefore, I would recommend to extract the subquery from the SELECT clause to a temporary table, index it and join to it (see fixed query below).
To optimize the query, let's begin with adding these indexes:
ALTER TABLE `classes` ADD INDEX `classes_index_1` (`id`, `name`);
ALTER TABLE `students` ADD INDEX `students_index_1` (`active`, `user_id`, `class`, `name`, `date`);
ALTER TABLE `users` ADD INDEX `users_index_1` (`user_id`, `full_name`);
Now create the temporary table (originally this was a subquery in the SELECT clause) and index it:
-- Transformed subquery to a temp table to improve performance
CREATE TEMPORARY TABLE IF NOT EXISTS temp1 AS SELECT
count(*) AS names,
name
FROM
students AS inner_st
WHERE
1 = 1
GROUP BY
name
ORDER BY
NULL
-- This index is required for optimal temp tables performance
ALTER TABLE
`temp1`
ADD
INDEX `temp1_index_1` (`name`, `names`);
And the modified query:
SELECT
SQL_CALC_FOUND_ROWS st.id,
st.date,
st.image,
st.user_id,
st.status,
ck.name AS class_name,
users.full_name,
temp1.names
FROM
students AS st
LEFT JOIN
users
ON st.user_id = users.user_id
LEFT JOIN
classes AS ck
ON st.class = ck.id
LEFT JOIN
temp1
ON st.name = temp1.name
WHERE
st.date BETWEEN '2018-01-17' AND DATE_ADD('2018-01-17', INTERVAL 1 DAY)
AND st.active = 1
ORDER BY
st.date DESC LIMIT 0,
10
Give this a try first:
INDEX(active, date)
Is user_id the PK for users? Is class_id the PK for classes? If not, then they should be INDEXed.
Why are you testing the times separate?
Fix the test so it is obvious which table each column is in.
Do you really need LEFT JOIN? Or would JOIN suffice? In the latter case, there are more optimization options.
Give some realistic examples of other SELECTs; different index(es) may be needed.
Is the "first" page slow? Or only later pages? See this for pagination optimization -- by not using OFFSET.

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

Pulling Last Two Dates PHP/MySQL

I have a table in my database which is updated randomly. I'm trying to pull entries by the latest date. This part is simply and I can do it with ease. However, I want to pull the two latest dates.
Example; If my last update was 2015-06-22 and the one before than was 2015-06-12 and the one before then was 2015-06-02. I would want to pull 2015-06-22 and 2015-06-15.
I would use a LIMIT 2, however, there are an unknown amount of items that may have the same date attached.
I haven't tried anything other than the LIMIT 2. After some research, I wasn't able to find anything to reference.
Update
I used SELECT DISTINCT to get the desired results.
SELECT DISTINCT dates FROM table ORDER BY dates DESC LIMIT 2
Will give you the latest 2 dates in the table.
I would have a column set to id, that is auto incremented, and do my query like this:
SELECT * FROM tbl_name ORDER BY `id` DESC LIMIT 2
Crap McAdam you beat me to it!
You can get the latest two dates using LIMIT, like you mentioned:
SELECT latestDates
FROM myTable
ORDER BY dateColumn DESC
LIMIT 2;
And you can join that to your original table to only select rows that occur on those two dates:
SELECT m.*
FROM myTable m
JOIN(
SELECT latestDates
FROM myTable
ORDER BY dateColumn DESC
LIMIT 2) tmp ON tmp.latestDates = m.dateColumn;

Select rows from multiple tables based on date

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.)

PHP MySQL newest records from multiple tables

I'm trying to select the latest 10 records from multiple tables (ORDER BY date). For example, 8 of the newest records might be in one table and 2 in another (10 rows in total). Is there a way to select those 10 records?
SELECT *
FROM
( SELECT * FROM x
UNION ALL
SELECT * FROM y
) n
ORDER
BY date DESC
LIMIT 10;
You can maybe use:
SELECT column_name(s)
FROM table1
ORDER BY date LIMIT 0,8
UNION ALL
SELECT column_name(s)
FROM table2
ORDER BY date LIMIT 0,2;
SELECT * FROM (
SELECT some_data AS alias1, date_field AS mydate
FROM table1
UNION ALL
SELECT datazzz AS alias1, another_datefield AS mydate
FROM table2
)
ORDER BY mydate DESC LIMIT 10
Syntax might need a little bit of tweaking, but that's the gist of it.
Specifically, you need to select whatever data you want out of each of the tables and then use aliases to make sure they have the same column names (otherwise they can't be returned in the same result set). Then after that you need to order by the common date field.

Categories