Combine two SELECTS from same table - mysql

I have an table with projects in them:
id (int)
ordering (int)
content (string)
My projects-page shows the current project and a preview of the next three projects. For fetching the next three projects, I would like to use a clean MySQL query. Of course, if the person is on one of the last projects, there are not 3 more projects left to show.
So it then needs to show the first one again.
Basically Im trying to combine these two statements:
SELECT *
FROM projects
WHERE ordering > {currentProjectOrdering}
ORDER BY ordering ASC
and
SELECT *
FROM projects
WHERE ordering > 0
ORDER BY ordering ASC
and also
LIMIT 0,3
In a nutshell: get the next three records with higher ordering than the current project, if (some of) these do not exist, start from ordering = 1.
Assuming there are 10 projects:
Project 1 shows 2,3 and 4
Project 2 shows 3,4 and 5
...
Project 9 shows 10, 1 and 2

If you want data from first condition AND second condition use
SELECT *
FROM projects
WHERE ordering > {currentProjectOrdering} AND ordering > 0
ORDER BY ordering ASC
LIMIT 0,3
If you want data from first condition OR second condition use
SELECT *
FROM projects
WHERE ordering > {currentProjectOrdering} OR ordering > 0
ORDER BY ordering ASC
LIMIT 0,3

I'm curious about this. I don't have mysql and can't test, but I don't think you are going to get the 3 records you want in these examples - if you are at the end of the list and need to grab 1 or 2 from the beginning. I'm playing with TSQL - so there is no LIMIT Hence the TOP.
I'm also curious if anyone can do it better.
SELECT TOP 3
CASE
WHEN ORDERING >= {currentProjectOrdering} THEN ORDERING
ELSE {currentProjectOrdering} - ORDERING END AS DISPLAYORDER,*
FROM SO_projects
WHERE (ordering >= {currentProjectOrdering} AND ordering < {currentProjectOrdering}+ 3 ) OR ordering < 3
ORDER BY DISPLAYORDER DESC
Can't promise as I have no idea about MYSQL, but here is my try. In T-SQL TOP returns the specified number of recs (3 in this case) depending on the ORDER BY. There is no equivocation to the 0 in the 0,3 part (I wish) of LIMIT. I googled and it looks like the CASE stuff should be pretty similar. You may be able to get rid of the > and < because of how LIMIT works. The logic works on SqlServer:
SELECT
CASE
WHEN ORDERING >= {currentProjectOrdering} THEN ORDERING
ELSE {currentProjectOrdering} - ORDERING END AS DISPLAYORDER,*
FROM SO_projects
WHERE (ordering >= {currentProjectOrdering} AND ordering < {currentProjectOrdering} + 3 ) OR ordering < 3
ORDER BY DISPLAYORDER DESC
LIMIT 0,3

Something Like this should work (assuming you limiting by just projects that person is working on):
SELECT * FROM projects WHERE person = person_id && (ordering > 0 && COUNT(id) < 4)
|| (ordering >= currentProjectOrdering && COUNT(id) >= 4)) ORDER BY ordering LIMIT 0,3;

Related

MySQL : ORDER BY expression?

On a website, I have got a list of Items I am struggling to sort with MySQL.
Some of the items are "expired" (their end_at is in the past), and I'd like to retrieve them, but after the "active" items (their end_at can be either NULL or in the future).
I would like to be able to further sort items according to another field, let's say "views" (to get the most popular ones first)
Basically, this is what I'd like to get :
item 1 (active - most popular)
item 2 (active - 2nd most popular)
item 3 (active - 3rd most popular)
item 4 (expired)
item 5 (expired)
(and so on)
So far, this is what I have tried:
SELECT name, end_at
FROM items
ORDER BY (end_at IS NULL OR end_at > CURDATE()), views DESC;
Doesn't work : even the first returned item is expired.
You almost got it. A condition in MySQL returns 0 or 1. And since 0 comes before 1 you have to either sort desc
SELECT name,end_at FROM items
ORDER BY (end_at IS NULL OR end_at > CURDATE()) DESC,
views DESC;
or negate the condition to get the desired result
SELECT name,end_at FROM items
ORDER BY end_at IS NOT NULL AND end_at < CURDATE() ASC,
views DESC;
I just found a way :
SELECT *
FROM items
ORDER BY CASE
WHEN (end_at IS NULL OR end_at > CURDATE())
THEN 0
ELSE 1
END, views DESC;
The CASE WHEN affects a value on each row, according to an expression. The sort is done as usual, using this computed value.

SQL comparison within max function

I'm trying to get a list of 20 events grouped by their Ids and sorted by whether they are in progress, pending, or already finished. The problem is that there are events with the same id that include finished, pending, and in progress events and I want to have 20 distinct Ids in the end. What I want to do is group these events together but if one of them is in progress then sort that group by that event. So basically I want to sort by the latest end time that is also before now().
What I have so far is something like this where end and start are end/start times. I'm not sure if what is inside max() is behaving how I should expect.
select * from event_schedule as t1
JOIN (
SELECT DISTINCT(event_id) as e
from event_schedule
GROUP BY event_id
order by MAX(end < unix_timestamp(now())) asc,
MIN(start >= unix_timestamp(now())) asc,
MAX(start) desc
limit 0, 20
)
as t2 on (t1.event_id = t2.e)
This results in some running / pending events to be mixed around in order when I want them to be in the order running -> pending -> Ended.
I would suggest to first create a view in order to not get an overcomplicated SELECT statement:
CREATE VIEW v_event_schedule AS
SELECT *,
CASE
WHEN end < unix_timestamp(now())
THEN 1
WHEN start > unix_timestamp(now())
THEN 2
ELSE 3
END AS category
FROM event_schedule;
This view v_event_schedule returns an extra column, in addition to the columns of event_schedule, which represents the priority of the category (running, pending, past):
running (in progress)
pending (future)
past
Then the following will do what you want:
SELECT a.*
FROM v_event_schedule a
INNER JOIN (
SELECT id,
MIN(category) category
FROM v_event_schedule b
GROUP BY id
) b
ON a.id = b.id
AND a.category = b.category
ORDER BY category,
start DESC
LIMIT 20;
The ORDER BY can be further adapted to your needs as to how you want to sort within the same category. I added start DESC as that seemed what you were doing in your attempt.
About the original ORDER BY
You had this:
order by MAX(end < unix_timestamp(now())) asc,
MIN(start >= unix_timestamp(now())) asc,
The expressions you have there evaluate to boolean values, and both elements in the ORDER BY each divide the groups into two sections, one for false and one for true, so in total 4 groups.
The first of the two will order IDs first that have no record with an end value in the past, because only then the boolean expression is always false which is the only way to make the MAX of them false as well.
Now let's say for the same ID you have both records that have an end date in the future as well as records with an end date in the past. In that case the MAX aggregates to true, and so the id will be sorted secondary. This is not intended, as this ID might have a "running" record.
I did not look into making your query work based on such aggregates on boolean expressions. It requires some time to understand what they are doing. A CASE WHEN to determine the category with a number really makes the SQL a lot easier to understand, at least to me.

SQL: Skip entries in an order without knowing total entry amount

The title is a bit confusing, but I'm wondering if there is a way to do a query like this:
SELECT * FROM table ORDER BY timestamp LIMIT 10
and then only take the ones after the 10th one (or none if there are less than or equal to 10 entries).
EDIT I guess another way to do this would be to order them by timestamp, descending, and then somehow limit to 0, (total-someNumber).
By specifying an OFFSET you can get the rows after a specified number. You combine this with limit.
In MySQL you achieve this with LIMIT [offset], limit.
Example - get 10 records after the oldest 10 records:
SELECT * FROM table ORDER BY timestamp LIMIT 10, 10; # Retrieve rows 11-20
Example - get 20 records after the newest 5 records:
SELECT * FROM table ORDER BY timestamp DESC LIMIT 5, 20; # Retrieve rows 6-25
If you want to get ALL rows after a certain number (eg. 10) then you pass an arbitrarily big number for the limit since it is required by the clause:
SELECT * FROM table ORDER BY timestamp LIMIT 10,18446744073709551615; # Retrieve rows 11-BIGINT
Note: 18446744073709551615 is the maximum of an unsigned BIGINT and is provided as the solution within the MySQL documentation.
See:
http://dev.mysql.com/doc/refman/5.5/en/select.html
I'd try something like this and then just add a where clause that skips the first n (n=10 in this case) rows.
i.e. using the linked example:
SELECT
*
FROM
(select #n := #n + 1 RowNumber, t.* from (select #n:=0) initvars, tbl t)
WHERE
RowNumber > 10

Sqlite Query select statement with sorted result respecting the OFFSET

I want to make a sqlite query in such a way that the result should be sorted which has a LIMIT and the OFFSET. But the OFFSET should work in synch a manner that it should discard the last records from the result.
SELECT * FROM TempTable WHERE CLASS = 1 ORDER BY Date ASC LIMIT 100 OFFSET 5;
The above query just ignores the first 5 records from the table and give the remaining records. But instead I want it to ignore the first 5 latest entries.
Note:- the first 5 latest entries means since I am sorting it by date it should IGNORE the latest record inserted in the table respecting the date.
Sort backwards, with OFFSET 5 and resort again:
SELECT * FROM (
SELECT * FROM TempTable WHERE CLASS = 1 ORDER BY Date DESC LIMIT 100 OFFSET 5
) ORDER BY Date ASC;

mysql select with this query then pad out with this query?

I am trying to do a mysql query to select some news stories from a table, now the key is I always need 5 results.
so I was hoping to be able to pad out my results with another where clause ie
select * from here where this = 1
if there is < 5 results then select * from here where this = 2
limit [how ever many we are short say the first query brings back 3 results this should bring back 2]
Now I've looked at using a union to do this but without outside help ie from php and another query to count the results I don't think it is possible, I know I could simply use php to do this, and will probably end up doing that, but I was just wondering if what I am trying to do is possible with one mysql query?
EDIT:
also it needs to order by date but they are not really posted in order so
order by date get upto 5 where this = 1 and if there isn't 5 pad it out with the remainder of where this = 2 also ordered by date.
Another Shameful Edit:
ask a silly question lol... it was my sleep deprivation I just assumed there was data in the table and the previous coder was using unions to do all sorts of stuff, making me think it was more complex than it should be
SELECT *
FROM
news
WHERE
( this = 45 || this= 0 )
AND
active = '1'
ORDER BY
this ASC,
date_added DESC
LIMIT 5
How about -
SELECT *
FROM here
WHERE this < 5 -- added this WHERE clause based on the idea that there will be at least one item per this
ORDER BY this ASC, `date` ASC
LIMIT 5;
Or are you after the five results then being sorted by date again -
SELECT *
FROM (
SELECT *
FROM here
WHERE this < 5 -- added this WHERE clause based on the idea that there will be at least one item per this
ORDER BY this ASC, `date` ASC
LIMIT 5
) AS tmp
ORDER BY `date` ASC
You could combine the where clauses and use limit :
select * FROM here WHERE this = 1 OR this = 2 ORDER BY this LIMIT 5
Even in there were 15 records where this is equal to 1 this would only bring back 5 records ...