I have a web app in which I show a series of posts based on this table schema (there are thousands of rows like this and other columns too (removed as not required for this question)) :-
+---------+----------+----------+
| ID | COL1 | COL2 |
+---------+----------+----------+
| 1 | NULL | ---- |
| 2 | --- | NULL |
| 3 | NULL | ---- |
| 4 | --- | NULL |
| 5 | NULL | NULL |
| 6 | --- | NULL |
| 7 | NULL | ---- |
| 8 | --- | NULL |
+---------+----------+----------+
And I use this query :-
SELECT * from `TABLE` WHERE `COL1` IS NOT NULL AND `COL2` IS NULL ORDER BY `COL1`;
And the resultant result set I get is like:-
+---------+----------+----------+
| ID | COL1 | COL2 |
+---------+----------+----------+
| 12 | --- | NULL |
| 1 | --- | NULL |
| 6 | --- | NULL |
| 8 | --- | NULL |
| 11 | --- | NULL |
| 13 | --- | NULL |
| 5 | --- | NULL |
| 9 | --- | NULL |
| 17 | --- | NULL |
| 21 | --- | NULL |
| 23 | --- | NULL |
| 4 | --- | NULL |
| 32 | --- | NULL |
| 58 | --- | NULL |
| 61 | --- | NULL |
| 43 | --- | NULL |
+---------+----------+----------+
Notice that the IDs column is jumbled thanks to the order by clause.
I have proper indexes to optimize these queries.
Now, let me explain the real problem. I have a lazy-load kind of functionality in my web-app. So, I display around 10 posts per page by using a LIMIT 10 after the query for the first page.
We are good till here. But, the real problem comes when I have to load the second page. What do I query now? I do not want the posts to be repeated. And there are new posts coming up almost every 15 seconds which make them go on top(by top I literally mean the first row) of the resultset(I do not want to display these latest posts in the second or third pages but they alter the resultset size so I cannot use LIMIT 10,10 for the 2nd page and so on as the posts will be repeated.).
Now, all I know is the last ID of the post that I displayed. Say 21 here. So, I want to display the posts of IDs 23,4,32,58,61,43 (refer to the resultset table above). Now, do I load all the rows without using the LIMIT clause and display 10 ids occurring after the id 21. But for that I will have to interate over thousands of useless rows.But, I cannot use a LIMIT clause for the 2nd,3rd... pages that is for sure. Also, the IDs are jumbled, so I can definitely not use WHERE ID>.... So, where do we go now?
I'm not sure if I've understood your question correctly, but here's how I think I would do it:
Add a timestamp column to your table, let's call it date_added
When displaying the first page, use your query as-is (with LIMIT 10) and hang on to the timestamp of the most recent record; let's call it last_date_added.
For the 2nd, 3rd and subsequent pages, modify your query to filter out all records with date_added > last_date_added, and use LIMIT 10, 10, LIMIT 20, 10, LIMIT 30, 10 and so on.
This will have the effect of freezing your resultset in time, and resetting it every time the first page is accessed.
Notes:
Depending on the ordering of your resultset, you might need a separate query to obtain the last_date_added. Alternatively, you could just cut off at the current time, i.e. the time when the first page was accessed.
If your IDs are sequential, you could use the same trick with the ID.
Hmm..
I thought for a while and came up with 2 solutions. :-
To store the Ids of the post already displayed and query WHERE ID NOT IN(id1,id2,...). But, that would cost you extra memory. And if the user loads 100 pages and the ids are in 100000s then a single GET request would not be able to handle it. At least not in all browsers. A POST request can be used.
Alter the way you display posts from COL1. I don't know if this would be a good way for you. But, it can save you bandwith and make your code cleaner. It may also be a better way. I would suggest this :- SELECT * from TABLE where COL1 IS NOT NULL AND COL2 IS NULL AND Id>.. ORDER BY ID DESC LIMIT 10,10. This can affect the way you display your posts by leaps and bounds. But, as you said in your comments that you check if a post meets a criteria and change the COL1 from NULL to the current timestammp, I guess that the newer the posts the, the more above you want to display them. It's just an idea.
I assume new posts will be added with a higher ID than the current max ID right? So couldn't you just run your query and grab the current max ID. Then when you query for page 2 do the same query but with "ID < max_id". This should give you the same result set as your page 1 query because any new rows will have ID > max_id. Hope that helps?
How about?
ORDER BY `COL1`,`ID`;
This would always put IDs in order. This will let you use:
LIMIT 10,10
for your second page.
Related
I have a problem that I can't figure out, I'm not experienced enough (or it can't be done!) I've trawled Google for the answer with no luck.
I have a system where I need to assign an ID to each row, with the ID from another table. The catch is that the ID must be unique for each row created in this batch.
Basically I'm selling links on my Tumblr accounts, I need to assign a Tumblr account to each link that a customer purchases but I want to assign all possible Tumblr accounts so that duplicates are kept to the minimum possible.
The URLs - each link that a customer buys is stored in this table (urls_anchors):
+----------+--------------------+------------+-----------+------+
| clientID | URL | Anchor | tumblrID | paid |
+----------+--------------------+------------+-----------+------+
| 1234 | http://example.com | Click here | 67 | Yes |
| 1234 | http://example.com | Click here | 66 | Yes |
| 1234 | http://example.com | Click here | 65 | Yes |
| 1234 | http://example.com | Click here | 64 | Yes |
+----------+--------------------+------------+-----------+------+
All of the Tumblr accounts available for allocation are stored in this table (tumblrs):
+----------+-------------------+------------+
| tumblrID | tumblrURL | spacesLeft |
+----------+-------------------+------------+
| 64 | http://tumblr.com | 9 |
| 65 | http://tumblr.com | 9 |
| 66 | http://tumblr.com | 9 |
| 67 | http://tumblr.com | 9 |
+----------+-------------------+------------+
My best attempt at this has been the following query:
INSERT INTO `urls_anchors` (`clientID`, `URL`,`Anchor`, `tumblrID`, `paid`) VALUES ('$clientID','$url','$line', (SELECT #rank:=#rank+1 AS tumblrID FROM tumblrs WHERE #rank < 68 LIMIT 1), 'No')
Which works but keeps adding incrementally indefinitely, when there are only X number of Tumblrs to assign. I need the query to loop back around when it reaches the last row of Tumblrs and run through the list again.
Also i'm using this in a PHP script, I'm not sure if that's of any significance.
Any help would be MASSIVELY appreciated!
Thanks for looking :)
You can use a SELECT query as the source of data to insert.
INSERT INTO urls_anchors (`clientID`, `URL`,`Anchor`, `tumblrID`, `paid`)
SELECT '$clientID','$url','$line', tumblrID, 'No'
FROM tumblrs
LIMIT $number_of_rows
DEMO
This will assign $number_of_rows different tumblrID values to the rows.
If you need to assign more tumbler IDs than are available, you'll need to do this in a loop, subtracting the number of rows inserted from $number_of_rows each time. You can use mysqli_affected_rows() to find out how many rows were inserted each time.
Right now I have a PHP script that is fetching the first three results from a MYSQL database using:
SELECT * FROM table Order by DATE DESC LIMIT 3;
After that command I wanted PHP to fetch the next three results, initially I was going to use:
SELECT * FROM table Order by DATE DESC LIMIT 3,3;
However there will be a delay between the two commands which means that it is very possible that a new row will be inserted into the table during the delay. My first thought was to store the DATE value of the last result and then include a WHERE DATE > $stored_date but if entry 3 and 4 have the same date it will skip entry 4 and return results from 5 onward. This could be avoided using the primary key field which is an integer which increments automatically.
I am not sure which the best approach is, but I feel like there should be a more elegant and robust solution to this problem, however I am struggling to think of it.
Example table:
-------------------------------------------
| PrimaryKey | Data | Date |
-------------------------------------------
| 0 | abc | 2014-06-17 11:43:00 |
| 1 | def | 2014-06-17 12:43:00 |
| 2 | ghi | 2014-06-17 13:43:00 |
| 3 | jkl | 2014-06-17 13:56:00 |
| 4 | mno | 2014-06-17 14:23:00 |
| 5 | pqr | 2014-06-17 14:43:00 |
| 6 | stu | 2014-06-17 15:43:00 |
-------------------------------------------
Where Data is the column that I want.
Best will be using primary key and select like
SELECT * FROM table WHERE pk < $stored_pk Order by DATE DESC LIMIT 3;
And if you have automatically generated PK you should use ORDER BY pk it will be faster
Two options I can think of depending on what your script does:
You could either use transactions: performing these queries inside a transaction will give you a consistent view of the data.
Alternatively you could just use:
SELECT * FROM table Order by DATE DESC;
And only fetch the results as you need them.
This question already has answers here:
Can we limit the number of rows in a table in MySQL?
(2 answers)
Closed 8 years ago.
I have a table which holds notices for a university website.I want it to hold a maximum of 1,000 notices.I am using the id field (which is auto increment) to fetch 10 most recent notices (by counting the total entries that denotes current most recent id and then traversing in backward direction by 10)and then next 10 and so on.
Now when the notice reaches the 1000 limit the notice should start getting uploaded from id 1 by overwriting existing data.
Now the problem is how can I modify the sql query to identify the most recent notice?Because suppose I uploaded 17 notices after the table was full,then the notices 1 to 17 are the recent notices ,17 being the most recent and next to it i.e 18 is the least recent in the table .
Or is there any tutorial or something specific to such case or any optimal method?
Here is my table structure-
+-----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| title | varchar(160) | NO | | NULL | |
| body | mediumtext | NO | | NULL | |
| posted_by | varchar(30) | NO | | NULL | |
| semester | int(2) | NO | | NULL | |
| branch | varchar(30) | NO | | NULL | |
| posted_on | date | NO | | NULL | |
+-----------+--------------+------+-----+---------+----------------+
You would use a delete query after your insertion query. That way you wouldn't need to pull another query to see which record was last updated.
insert into table notices...;
delete from notices where id not in (select top 1000 id from notices);
If you wanted to keep the same 1000 ids for some reason, then you could switch auto-increment off in the table, then you would have to pull two different queries:
select id,min(posted_on) from notices;
update notices set .... where id=$id;
Really because MySQL is designed to handle millions of rows, then you don't actually have to do this at all. You can pull a query with a limit of 1000.
select * from notices limit 1000 order by posted_on DESC;
I'm attempting to add Breadcrumbs to my website using a MySQL table, and I'm having difficulty at the moment.
I have a table named 'includes' created that stores information about the category, page, subpage, the title, and the ref (url) of the page. Category, Page, and Subpage are all php parameters passed from the page the user is on
My table is laid out like this:
|----------------------------------------------------------------|
| ID | Category | Page | Subpage | Title | Ref |
|----------------------------------------------------------------|
| 0 | | | | Home | ... |
| 1 | Software | | | Software | ... |
| 2 | Software | Desktop | | Desktop Software | ... |
| 3 | Software | Mobile | | Mobile Software | ... |
| 4 | Software | Desktop | Blah | Blah Blah | ... |
| ...
|----------------------------------------------------------------|
What I'm trying to do is make a query that will return only the required steps back to home for the breadcrumbs.
In other words, if the user is on "example.com/software/desktop/blah", the query will return rows 0,1,2, and 4. Or if I was on /software/mobile, it would only return rows 0,1, and 3.
My current attempts have been things like the following:
SELECT * FROM `includes` WHERE
`category` IS NULL AND `page` IS NULL AND `subpage` IS NULL OR
`category`='$category' AND `page` IS NULL AND `subpage` IS NULL OR
`category`='$category' AND `page`='$page' AND `subpage` IS NULL OR
`category`='$category' AND `page`='$page' AND `subpage`='$subpage'
Which not only don't work, but also seem more complex than it should have to be.
I'm probably overcomplicating this, or possibly just doing an entirely wrong method, which is why I've turned here.
Does anyone have a possible solution to this? Should I be looking at a more complex query? (admittedly, SQL is not my forte) Or should I be looking at a new SQL table, or possibly an entirely different method?
What you have is a hierarchical structure. The data is set up with parent-child relationships. There is a good description on how to work with hierarchical data here: http://explainextended.com/2009/03/17/hierarchical-queries-in-mysql/
You can make a self relation table like this
id | parent_it | title | Ref
1 | 0 | Home | ...
2 | 1 | Software | ...
3 | 2 | Desktop | ...
4 | 2 | Mobile | ...
5 | 3 | Blah | ...
So your query should get the last element
SELECT * FROM includes WHERE
tilte = 'Blah'
And then get the parent ID title and so on , like this the table structure will be better from my point of view & experience
OR
Generate your query based on the values you get , with simple loop count the arguments and based on that generate the query string then execute it
I hope this can help :)
I have this table structure,
| id | name | level |
---------------------
| 1 | a | 1 |
| 2 | b | 2 |
| 3 | c | 3 |
| 5 | d | 4 |
| 6 | e | 1 |
| 7 | f | 2 |
| 8 | g | 1 |
| 9 | g | 4 |
I want to order my fetch result to level, so I execute this query:
$sql = "SELECT * FROM section_tb WHERE id = ? ORDER BY level";
$stmt = $db->prepare($sql);
$stmt->execute(array($id));
$result = $stmt->fetch(PDO::FETCH_ASSOC);
However when I print_r($result) the order seems like it was sorted by id. I am confused why.
My db details:
id - PRIMARY, AUTO INCREMENT
name - UNIQUE
INNODB
Your query is ordering correctly.
This is irrelevant since it's only returning one row each time its called anyway.
Your foreach is calling into it multiple times, and the ordering only affects the actual database call. Therefore the overall order of the results is the order of that foreach.
If the foreach had passed a parameter that identified more than one row, then within each of those calls the order would be by level (e.g. if you'd done queries to match on name, then the two that match "g" would be in the order requested).
You want to change the query to something like SELECT * FROM section_tb WHERE id in (1,2,3,4,5,6,7,8,9) ORDER BY level (or perhaps just SELECT * FROM section_tb ORDER BY level), call it once, and loop through the results.
Your WHERE clause is seeking an id which you've identified as the primary key, so your query should only return one row.
You can't do that. You can either use a place holder or bind the parameter.