change the mysql order of the result with union - mysql

So i get 10 results from my first select and 1 from the other one after union like this:
(SELECT a.*,
b.*
FROM all a,
names b
WHERE b.name_id = a.name_id
ORDER BY name_id DESC
LIMIT 10)
UNION
(SELECT a.*,
b.*
FROM all a,
names b
WHERE b.name_id = a.name_id
ORDER BY request_id ASC
LIMIT 1)
i would like to get the result of the second select as the second last result like this
********
name_id 100
name_id 99
name_id 98
name_id 97
name_id 96
name_id 95
name_id 94
name_id 93
name_id 92
name_id 1 <- second select result as second last result
name_id 91
********
Can someone help pls?

Synthesize a row number column for the query as it stands and shuffle positions as needed.
SELECT x.name
, x.name_id
FROM (
SELECT #rownum:=#rownum + 1 as row_number,
t.name,
t.name_id
FROM (
-- original query from the question starts here
(SELECT b.name,
a.name_id
FROM allx a,
names b
WHERE b.name_id = a.name_id
ORDER BY name_id DESC
LIMIT 10)
UNION
(SELECT b.name,
a.name_id
FROM allx a,
names b
WHERE b.name_id = a.name_id
ORDER BY request_id ASC
LIMIT 1)
) t,
(SELECT #rownum := 0) r
) x
ORDER BY CASE row_number
WHEN 10 THEN 11
WHEN 11 THEN 10
ELSE row_number
END
;
(Note that the query has been sightly modified to avoid syntax errors / support the demo: table all has been named allx, explicit projections of the union's subqueries).
That gets complicated quickly thus next to ad hoc reporting it is preferable to synthesize an attribute in the subqueries of the union that reflects a global order.
Demo here (SQL fiddle)
Credits
Row number synthesizing taken from this SO answer

Interesting question given
+----+--------+
| id | sname |
+----+--------+
| 1 | sname1 |
| 2 | sname2 |
| 3 | sname3 |
| 4 | sname4 |
| 5 | sname5 |
| 6 | sname6 |
+----+--------+
6 rows in set (0.001 sec)
(select id,sname,#r:=#r+1 rn
from users
cross join(select #r:=0) r
order by sname desc limit 3
)
union
(
select u.id,u.sname,
#r:=#r - .9
from users u
left join (select id from users order by sname desc limit 3) u1 on u1.id = u.id
where u1.id is null
order by u.id asc limit 0,1
)
order by rn;
Where a variable is used to calculate a row number in the first sub query, since this variable is not reset in the second query a simple piece of arithmetic works out where to position the second sub query result. Note the second sub query uses a left join to check that the result has not already appeared in the first sub query,

I would suggest union all and three selects:
SELECT an.*
FROM ((SELECT a.*, n.*, 1 as ord
FROM all a JOIN
names n
ON n.name_id = a.name_id
ORDER BY n.name_id DESC
LIMIT 9
) UNION ALL
(SELECT a.*, n.*, 3 as ord
FROM all a JOIN
names n
ON n.name_id = a.name_id
ORDER BY n.name_id DESC
LIMIT 9 OFFSET 9
) UNION ALL
(SELECT a.*, b.*
FROM all a JOIN
names n
WHERE n.name_id = a.name_id
ORDER BY request_id ASC
LIMIT 1
)
) an
ORDER BY ord, name_id;

Related

How to retrieve number of records from mysql database in a group?

I have a table with multiple records in it for full name and surname.
---------------------------
id | name | lastname
---------------------------
1 | A smith | smith
2 | B smith | smith
3 | c smith | smith
4 | A josh | josh
5 | B josh | josh
6 | C josh | josh
7 | D josh | josh
8 | A white | white
9 | D white | white
10| z white | white
And so so....more than 100k records. Now what i want to do is to retrieve latest 7 records for each surname up to 9 surnames. I have 500 surnames but i just want latest 9 surnames.. In my application "latest" means "largest value of id column."
This is the command that i tried to make but when i execute it. i am not getting any response from server. this is happening because of database size and my command is taking a lot of time. its just keep me waiting:
SELECT * FROM `queue` s WHERE ( SELECT COUNT(*) FROM `queue` f WHERE f.lastname = s.lastname AND f.id >= s.id LIMIT 0 , 7) <=7
Can someone suggest me better way of retrieving my goal.
Let's build this up from the basics.
Your first step is to create a subquery to get the latest nine surnames (http://sqlfiddle.com/#!9/aee62e/19/0). By that I mean the surnames with the highest id values.
SELECT lastname, MAX(id) namerank
FROM t
GROUP BY lastname
ORDER BY MAX(id) DESC
LIMIT 9
And, in MySQL, that was the easy part. Now you need to retrieve the seven highest ranked (largest id) rows for each selected surname. As a start, you could do this to get all records for the selected surnames, in descending order by id. (http://sqlfiddle.com/#!9/aee62e/18/0).
SELECT t.*, namerank
FROM t
JOIN (
SELECT lastname, MAX(id) namerank
FROM t
GROUP BY lastname
ORDER BY MAX(id) DESC
LIMIT 9
) h ON t.lastname = h.lastname
ORDER BY t.lastname, t.id DESC
This is correct, but contains too many rows. Next we need to get the ranking for each lastname's rows. A lower ranking means a higher id value. This is the nasty hack in MySQL. (Nasty because it mixes procedural operations on local variables with the inherently declarative nature of SQL.) (http://sqlfiddle.com/#!9/aee62e/17/0)
SELECT IF(detail.lastname = #prev_lastname, #rank := #rank+1, #rank :=1) rank,
namerank,
#prev_lastname := detail.lastname lastname,
id,
name
FROM (
SELECT t.*, namerank
FROM t
JOIN (
SELECT lastname, MAX(id) namerank
FROM t
GROUP BY lastname
ORDER BY MAX(id) DESC
LIMIT 9
) h ON t.lastname = h.lastname
ORDER BY t.lastname, t.id DESC
) detail
JOIN (SELECT #rank := 0, #prev_lastname := '') initializer
Finally we need to wrap that whole mess in an outer query to pick off the seven highest ranked rows for each lastname value. (http://sqlfiddle.com/#!9/aee62e/16/0)
SELECT *
FROM (
SELECT IF(detail.lastname = #prev_lastname, #rank := #rank+1, #rank :=1) rank,
namerank,
#prev_lastname := detail.lastname lastname,
id,
name
FROM (
SELECT t.*, namerank
FROM t
JOIN (
SELECT lastname, MAX(id) namerank
FROM t
GROUP BY lastname
ORDER BY MAX(id) DESC
LIMIT 9
) h ON t.lastname = h.lastname
ORDER BY t.lastname, t.id DESC
) detail
JOIN (SELECT #rank := 0, #prev_lastname := '') initializer
) ranked
WHERE rank <= 7
ORDER BY namerank DESC, rank
I believe the technical term for the complexity of your requirement and this solution is "hairball." It definitely puts the structured in Structured Query Language.

MySQL select upto first occurrence of condition matching

Id | Price
----------------
1 | 10
2 | 20
3 | 40
4 | 10
I need to select ids where first occurrence of summation of price is greater than or equal 55 matching from the bottom. At this case --
I will have 4,3,2 ids selected.
Well, this is kinda tricky for MySQL since it doesn't support any window fuctions and becuase you want to include the first occurrence as well. You can try this:
SELECT * FROM (
SELECT t.id,
(SELECT sum(s.price) FROM YourTable s
WHERE s.id <= t.id) as cuml_sum
FROM YourTable t) ss
WHERE ss.cuml_sum < 55
--Will select all the record will the sum < 55
UNION ALL
SELECT * FROM (
SELECT t.id,
(SELECT sum(s.price) FROM YourTable s
WHERE s.id <= t.id) as cuml_sum
FROM YourTable t) tt
WHERE tt.cuml_sum >= 55
ORDER BY tt.cuml_sum
LIMIT 1
--Will select the first record that have sum >= 55

finding second position in mysql

I need to pull the name of the students who stood second positions from grade 1 to grade 12. each grade has separate databases with similar table structure
I have the following data:
Set 1
uid marks
1 10
2 20
3 17
4 17
5 20
6 20
Set 2
uid marks
1 10
2 20
3 17
4 17
5 20
6 17
7 20
I need a query which can say uid 3,4 are second in set 1 and 3,4,6 are second in set 2.
i need it in a single query because there are several set of databases
what could be the possible way?
I tried:
SELECT * FROM TBL WHERE marks ! = SELECT MAX(marks) from tbl
but it fetched all marks except the highest
Try this out:
SELECT uid, marks FROM (
SELECT uid, marks, #rank := #rank + (#prevMarks != marks) rank, #prevMarks := marks
FROM t, (SELECT #rank := 0, #prevMarks := 0) init
ORDER BY marks
) s
WHERE rank = 2
Fiddle here.
Another alternative without User Defined Variables:
SELECT t.uid, t.marks FROM t
JOIN (
SELECT DISTINCT marks FROM t
ORDER BY marks
LIMIT 1, 1
) s
ON t.marks = s.marks
Output:
| UID | MARKS |
|-----|-------|
| 3 | 17 |
| 4 | 17 |
Use LIMIT and ORDER BY
SELECT * FROM TBL ORDER BY marks DESC LIMIT 1,1
There you ordered all students by marks fro hi to low. And then limit return from second (0 is first record) and return only one record.
If need all students with second mark, the use subquery
SELECT * FROM TBL WHERE marks = (
SELECT marks FROM TBL ORDER BY marks DESC GROUP BY marks LIMIT 1,1
)
SELECT *
FROM table
WHERE mark = (
SELECT MAX(mark)
FROM table
WHERE mark <
(
SELECT MAX(mark)
FROM table
)
)
Try this
SELECT t.marks, t.uid, (
SELECT COUNT( marks ) +1
FROM tbl t1
WHERE t.marks < t1.marks
) AS rank
FROM tbl t
LIMIT 0 , 30
now you can use rank column with bit modification below
SELECT * from (
SELECT t.marks, t.uid, (
SELECT COUNT( marks ) +1
FROM tbl t1
WHERE t.marks < t1.marks
) AS rank
FROM tbl t
) alias where rank=n (2 here)

Unknown column in mysql subquery

I am trying to get the avg of an item so I am using a subquery.
Update: I should have been clearer initially, but i want the avg to be for the last 5 items only
First I started with
SELECT
y.id
FROM (
SELECT *
FROM (
SELECT *
FROM products
WHERE itemid=1
) x
ORDER BY id DESC
LIMIT 15
) y;
Which runs but is fairly useless as it just shows me the ids.
I then added in the below
SELECT
y.id,
(SELECT AVG(deposit) FROM (SELECT deposit FROM products WHERE id < y.id ORDER BY id DESC LIMIT 5)z) AVGDEPOSIT
FROM (
SELECT *
FROM (
SELECT *
FROM products
WHERE itemid=1
) x
ORDER BY id DESC
LIMIT 15
) y;
When I do this I get the error Unknown column 'y.id' in 'where clause', upon further reading here I believe this is because when the queries go down to the next level they need to be joined?
So I tried the below ** removed un needed suquery
SELECT
y.id,
(SELECT AVG(deposit) FROM (
SELECT deposit
FROM products
INNER JOIN y as yy ON products.id = yy.id
WHERE id < yy.id
ORDER BY id DESC
LIMIT 5)z
) AVGDEPOSIT
FROM (
SELECT *
FROM products
WHERE itemid=1
ORDER BY id DESC
LIMIT 15
) y;
But I get Table 'test.y' doesn't exist. Am I on the right track here? What do I need to change to get what I am after here?
The example can be found here in sqlfiddle.
CREATE TABLE products
(`id` int, `itemid` int, `deposit` int);
INSERT INTO products
(`id`, `itemid`, `deposit`)
VALUES
(1, 1, 50),
(2, 1, 75),
(3, 1, 90),
(4, 1, 80),
(5, 1, 100),
(6, 1, 75),
(7, 1, 75),
(8, 1, 90),
(9, 1, 90),
(10, 1, 100);
Given my data in this example, my expected result is below, where there is a column next to each ID that has the avg of the previous 5 deposits.
id | AVGDEPOSIT
10 | 86 (deposit value of (id9+id8+id7+id6+id5)/5) to get the AVG
9 | 84
8 | 84
7 | 84
6 | 79
5 | 73.75
I'm not an MySQL expert (in MS SQL it could be done easier), and your question looks a bit unclear for me, but it looks like you're trying to get average of previous 5 items.
If you have Id without gaps, it's easy:
select
p.id,
(
select avg(t.deposit)
from products as t
where t.itemid = 1 and t.id >= p.id - 5 and t.id < p.id
) as avgdeposit
from products as p
where p.itemid = 1
order by p.id desc
limit 15
If not, then I've tri tried to do this query like this
select
p.id,
(
select avg(t.deposit)
from (
select tt.deposit
from products as tt
where tt.itemid = 1 and tt.id < p.id
order by tt.id desc
limit 5
) as t
) as avgdeposit
from products as p
where p.itemid = 1
order by p.id desc
limit 15
But I've got exception Unknown column 'p.id' in 'where clause'. Looks like MySQL cannot handle 2 levels of nesting of subqueries.
But you can get 5 previous items with offset, like this:
select
p.id,
(
select avg(t.deposit)
from products as t
where t.itemid = 1 and t.id > coalesce(p.prev_id, -1) and t.id < p.id
) as avgdeposit
from
(
select
p.id,
(
select tt.id
from products as tt
where tt.itemid = 1 and tt.id <= p.id
order by tt.id desc
limit 1 offset 6
) as prev_id
from products as p
where p.itemid = 1
order by p.id desc
limit 15
) as p
sql fiddle demo
This is my solution. It is easy to understand how it works, but at the same time it can't be optimized much since I'm using some string functions, and it's far from standard SQL. If you only need to return a few records, it could be still fine.
This query will return, for every ID, a comma separated list of previous ID, ordered in ascending order:
SELECT p1.id, p1.itemid, GROUP_CONCAT(p2.id ORDER BY p2.id DESC) previous_ids
FROM
products p1 LEFT JOIN products p2
ON p1.itemid=p2.itemid AND p1.id>p2.id
GROUP BY
p1.id, p1.itemid
ORDER BY
p1.itemid ASC, p1.id DESC
and it will return something like this:
| ID | ITEMID | PREVIOUS_IDS |
|----|--------|-------------------|
| 10 | 1 | 9,8,7,6,5,4,3,2,1 |
| 9 | 1 | 8,7,6,5,4,3,2,1 |
| 8 | 1 | 7,6,5,4,3,2,1 |
| 7 | 1 | 6,5,4,3,2,1 |
| 6 | 1 | 5,4,3,2,1 |
| 5 | 1 | 4,3,2,1 |
| 4 | 1 | 3,2,1 |
| 3 | 1 | 2,1 |
| 2 | 1 | 1 |
| 1 | 1 | (null) |
then we can join the result of this query with the products table itself, and on the join condition we can use FIND_IN_SET(src, csvalues) that return the position of the src string inside the comma separated values:
ON FIND_IN_SET(id, previous_ids) BETWEEN 1 AND 5
and the final query looks like this:
SELECT
list_previous.id,
AVG(products.deposit)
FROM (
SELECT p1.id, p1.itemid, GROUP_CONCAT(p2.id ORDER BY p2.id DESC) previous_ids
FROM
products p1 INNER JOIN products p2
ON p1.itemid=p2.itemid AND p1.id>p2.id
GROUP BY
p1.id, p1.itemid
) list_previous LEFT JOIN products
ON list_previous.itemid=products.itemid
AND FIND_IN_SET(products.id, previous_ids) BETWEEN 1 AND 5
GROUP BY
list_previous.id
ORDER BY
id DESC
Please see fiddle here. I won't recommend using this trick for big tables, but for small sets of data it is fine.
This is maybe not the simplest solution, but it does do the job and is an interesting variation and in my opinion transparent. I simulate the analytical functions that I know from Oracle.
As we do not assume the id to be consecutive the counting of the rows is simulated by increasing #rn each row. Next products table including the rownum is joint with itself and only the rows 2-6 are used to build the average.
select p2id, avg(deposit), group_concat(p1id order by p1id desc), group_concat(deposit order by p1id desc)
from ( select p2.id p2id, p1.rn p1rn, p1.deposit, p2.rn p2rn, p1.id p1id
from (select p.*,#rn1:=#rn1+1 as rn from products p,(select #rn1 := 0) r) p1
, (select p.*,#rn2:=#rn2+1 as rn from products p,(select #rn2 := 0) r) p2 ) r
where p2rn-p1rn between 1 and 5
group by p2id
order by p2id desc
;
Result:
+------+--------------+---------------------------------------+------------------------------------------+
| p2id | avg(deposit) | group_concat(p1id order by p1id desc) | group_concat(deposit order by p1id desc) |
+------+--------------+---------------------------------------+------------------------------------------+
| 10 | 86.0000 | 9,8,7,6,5 | 90,90,75,75,100 |
| 9 | 84.0000 | 8,7,6,5,4 | 90,75,75,100,80 |
| 8 | 84.0000 | 7,6,5,4,3 | 75,75,100,80,90 |
| 7 | 84.0000 | 6,5,4,3,2 | 75,100,80,90,75 |
| 6 | 79.0000 | 5,4,3,2,1 | 100,80,90,75,50 |
| 5 | 73.7500 | 4,3,2,1 | 80,90,75,50 |
| 4 | 71.6667 | 3,2,1 | 90,75,50 |
| 3 | 62.5000 | 2,1 | 75,50 |
| 2 | 50.0000 | 1 | 50 |
+------+--------------+---------------------------------------+------------------------------------------+
SQL Fiddle Demo: http://sqlfiddle.com/#!2/c13bc/129
I want to thank this answer on how to simulate analytical functions in mysql: MySQL get row position in ORDER BY
It looks like you just want:
SELECT
id,
(SELECT AVG(deposit)
FROM (
SELECT deposit
FROM products
ORDER BY id DESC
LIMIT 5) last5
) avgdeposit
FROM products
The inner query gets the last 5 rows added to product, the query that wraps that gets the average for their deposits.
I'm going to simplify your query a bit so I can explain it.
SELECT
y.id,
(
SELECT AVG(deposit) FROM
(
SELECT deposit
FROM products
LIMIT 5
) z
) AVGDEPOSIT
FROM
(
SELECT *
FROM
(
SELECT *
FROM products
) x
LIMIT 15
) y;
My guess would be that you just need to insert some AS keywords in there. I'm sure someone else will come up with something more elegant, but for now you can try it out.
SELECT
y.id,
(
SELECT AVG(deposit) FROM
(
SELECT deposit
FROM products
LIMIT 5
) z
) AS AVGDEPOSIT
FROM
(
SELECT *
FROM
(
SELECT *
FROM products
) AS x
LIMIT 15
) y;
Here's one way to do it in MySQL:
SELECT p.id
, ( SELECT AVG(deposit)
FROM ( SELECT #rownum:=#rownum+1 rn, deposit, id
FROM ( SELECT #rownum:=0 ) r
, products
ORDER BY id ) t
WHERE rn BETWEEN p.rn-5 AND p.rn-1 ) avgdeposit
FROM ( SELECT #rownum1:=#rownum1+1 rn, id
FROM ( SELECT #rownum1:=0 ) r
, products
ORDER BY id ) p
WHERE p.rn >= 5
ORDER BY p.rn DESC;
It's a shame MySQL doesn't support the WITH clause or windowing functions. Having both would greatly simplify the query to the following:
WITH tbl AS (
SELECT id, deposit, ROW_NUMBER() OVER(ORDER BY id) rn
FROM products
)
SELECT id
, ( SELECT AVG(deposit)
FROM tbl
WHERE rn BETWEEN t.rn-5 AND t.rn-1 )
FROM tbl t
WHERE rn >= 5
ORDER BY rn DESC;
The latter query runs fine in Postgres.
2 possible solutions here
Firstly using user variables to add a sequence number. Do this twice, and join the second set to the first where the sequence number is between the id - 1 and the id - 5. Then just use AVG. No correlated sub queries.
SELECT Sub3.id, Sub3.itemid, Sub3.deposit, AVG(Sub4.deposit)
FROM
(
SELECT Sub1.id, Sub1.itemid, Sub1.deposit, #Seq:=#Seq+1 AS Sequence
FROM
(
SELECT id, itemid, deposit
FROM products
ORDER BY id DESC
) Sub1
CROSS JOIN
(
SELECT #Seq:=0
) Sub2
) Sub3
LEFT OUTER JOIN
(
SELECT Sub1.id, Sub1.itemid, Sub1.deposit, #Seq1:=#Seq1+1 AS Sequence
FROM
(
SELECT id, itemid, deposit
FROM products
ORDER BY id DESC
) Sub1
CROSS JOIN
(
SELECT #Seq1:=0
) Sub2
) Sub4
ON Sub4.Sequence BETWEEN Sub3.Sequence + 1 AND Sub3.Sequence + 5
GROUP BY Sub3.id, Sub3.itemid, Sub3.deposit
ORDER BY Sub3.id DESC
Second one is cruder, and uses a correlated sub query (which is likely to perform poorly as the amount of data increases). Does a normal select but for the last column it has a sub query that refers to the id in the main select.
SELECT id, itemid, deposit, (SELECT AVG(P2.deposit) FROM products P2 WHERE P2.id BETWEEN P1.id - 5 AND p1.id - 1 ORDER BY id DESC LIMIT 5)
FROM products P1
ORDER BY id DESC
Is this what you are after?
SELECT m.id
, AVG(d.deposit)
FROM products m
, products d
WHERE d.id < m.id
AND d.id >= m.id - 5
GROUP BY m.id
ORDER BY m.id DESC
;
But can't be that simple. Firstly, the table cannot just contain one itemid (hence your WHERE clause); Second, the id cannot be sequential/without gaps within an itemid. Thirdly, you probably want to produce something that runs across itemid and not one itemid at a time. So here it is.
SELECT itemid
, m_id as id
, AVG(d.deposit) as deposit
FROM (
SELECT itemid
, m_id
, d_id
, d.deposit
, #seq := (CASE WHEN m_id = d_id THEN 0 ELSE #seq + 1 END) seq
FROM (
SELECT m.itemid
, m.id m_id
, d.id d_id
, d.deposit
FROM products m
, products d
WHERE m.itemid = d.itemid
AND d.id <= m.id
ORDER BY m.id DESC
, d.id DESC) d
, (SELECT #seq := 0) s
) d
WHERE seq BETWEEN 1 AND 5
GROUP BY itemid
, m_id
ORDER BY itemid
, m_id DESC
;

Order by two columns

Imagine the following MySQL table of orders:
id | name
1 | Mike
2 | Steve
3 | Janet
4 | Juliet
5 | Mike
6 | Jane
This is my current query:
SELECT * FROM table ORDER BY id DESC
However, I'd like to "group" those by name, so that I have orders from the same person listed after one another, however, I cannot do ORDER BY name.
This is my desired output:
id | name
6 | Jane
5 | Mike
1 | Mike
4 | Juliet
3 | Janet
2 | Steve
What's the query for this output?
E.g.:
SELECT y.id
, y.name
FROM my_table x
JOIN my_table y
ON y.name = x.name
GROUP
BY name
, id
ORDER
BY MAX(x.id) DESC
, id DESC;
You need to have special calculation to get their row position.
SELECT a.*
FROM tableName a
INNER JOIN
(
SELECT Name,
#ord := #ord + 1 ord
FROM
(
SELECT MAX(ID) ID, NAME
FROM TableName
GROUP BY Name
) a, (SELECT #ord := 0) b
ORDER BY ID DESC
) b ON a.Name = b.Name
ORDER BY b.ord, a.ID DESC
SQLFiddle Demo
You can do it via double ORDER BY:
SELECT * FROM t ORDER BY name ASC, id DESC
SELECT * FROM table1
ORDER BY field(NAME,'Mike','Jane') desc,
`ID` desc;
exactly as you asked
You could also try this query if you want to have something which is more generic SQL.
SELECT id, name
FROM ( SELECT id, name, (SELECT MAX(id) from Table1 where name=t.name) AS max_id
FROM Table1 AS t
ORDER BY max_id DESC, id DESC) as x
What about to use group by?
You can group it by name and then order...