Exclude top and bottom n rows in SQL - mysql

I'm trying to query a database but excluding the first and last rows from the table. Here's a sample table:
id | val
--------
1 1
2 9
3 3
4 1
5 2
6 6
7 4
In the above example, I'd first like to order it by val and then exclude the first and last rows for the query.
id | val
--------
4 1
5 2
3 3
7 4
6 6
This is the resulting set I would like. Note row 1 and 2 were excluded as they had the lowest and highest val respectively.
I've considered LIMIT, TOP, and a couple of other things but can't get my desired result. If there's a method to do it (even better with first/last % rather than first/last n), I can't figure it out.

You can try this mate:
SELECT * FROM numbers
WHERE id NOT IN (
SELECT id FROM numbers
WHERE val IN (
SELECT MAX(val) FROM numbers
) OR val IN (
SELECT MIN(val) FROM numbers
)
);

You can try this:
Select *
from table
where
val!=(select val from table order by val asc LIMIT 1)
and
val!=(select val from table order by val desc LIMIT 1)
order by val asc;
You can also use UNION and avoid the 2 val!=(query)

;WITH cte (id, val, rnum, qty) AS (
SELECT id
, val
, ROW_NUMBER() OVER(ORDER BY val, id)
, COUNT(*) OVER ()
FROM t
)
SELECT id
, val
FROM cte
WHERE rnum BETWEEN 2 AND qty - 1

What if you use UNION and exclude the val you don't want. Something like below
select * from your_table
where val not in (
select top 1 val from your_table order by val
union
select top 1 val from your_table order by val desc)

Related

Sorting by frequency in MySQL/SQL

Is there way of sorting by frequency that a value occurs? If a value appears in multiple rows, would we just use the WHERE clause? Is it just about making the query more specific?
As a simple example:
CREATE TABLE mytable
( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY
, val VARCHAR(15) NOT NULL
);
INSERT INTO mytable (id, val) VALUES
(1,'one')
,(2,'prime')
,(3,'prime')
,(4,'square')
,(5,'prime')
,(6,'six')
,(7,'prime')
,(8,'cube')
,(9,'square')
;
We can write a simple query to return the rows
SELECT t.val
, t.id
FROM mytable t
ORDER BY t.val
But what query do we use to get the most frequently occurring values listed first? To return a result like this:
freq val id
---- ------ --
4 prime 2
4 prime 3
4 prime 5
4 prime 7
2 square 4
2 square 9
1 cube 8
1 one 1
1 six 6
where freq is the frequency (the count of the number of rows) that a value appears in the val column. The value 'prime' appears in four rows, so freq has a value of 4.
What MySQL SELECT query would I use to return a result like this?
Try this:
SELECT A.Freq , A.val , A.id
FROM ( SELECT COUNT(*) AS Freq , val , id
FROM mytable
GROUP BY val , id ) A
ORDER BY Freq DESC ;
EDIT:
As suggested by spencer7593, the id is defined as auto-increment in the table and hence the GROUP BY should not include it. Still, if that would be the case, it is not clear how the result could be as shown. I'm adding here an alternative SELECT that, supposedly, should yield the shown output:
SELECT B.Freq , A.val , A.id
FROM mytable A
INNER JOINT ( SELECT val , COUNT(*) AS Freq
FROM mytable
GROUP BY val) B
ON A.val = B.val
ORDER BY B.Freq DESC ;
[NOTE: This was NOT tested!!!!]

SELECT 2 rows of the same column value

I have data that looks like
id val
--------
1 1
2 1
3 1
4 2
5 2
6 2
7 3
8 3
9 3
I'd like to fetch the id (and value) of two randomly chosen rows for each value, something like:
id val
--------
2 1
3 1
4 2
6 2
8 3
9 3
[EDIT]
I use MySQL v5.7
[SOLUTION]
I finally found another solution to my problem which is more compact and more useful if for example I want to limit the results to 25 id per value
SELECT val, SUBSTRING_INDEX(GROUP_CONCAT(id ORDER BY RAND() SEPARATOR ','), ',', 2)
FROM my_table
GROUP BY val
The idea is to group by val, concatenate the corresponding id in a string and taking a substring of the desired length.
try this
SELECT t.val,
#id := (SELECT t2.id FROM tbl_values t2 WHERE t2.val=t.val ORDER BY RAND() LIMIT 1) id1,
(SELECT t2.id FROM tbl_values t2 WHERE t2.val=t.val and t2.id<>#id ORDER BY RAND() LIMIT 1) id2
FROM tbl_values t
GROUP by 1
db fiddle demo
With MySQL 8.0 you could use ROW_NUMBER():
SELECT id, val
FROM (SELECT *, ROW_NUMBER() OVER(PARTITION BY val ORDER BY RAND()) AS rn
FROM tab) sub
WHERE rn <=2;
DB-Fiddle.com Demo
How about something like:
SELECT val, MIN(id)
FROM data
GROUP BY val

how to group by first letter of text

I have the following query in MySQL:
select val,count(val)
from ....
where ...
group by val
It gives:
val count
CE3 4
CE5 1
A3 12
BRICK4 5
BRICK2 2
I want to show only the row with the highest count per first letter.
Which means all val starting with A are one group, all val starting with B are another group etc...
The expected result is:
val count
CE3 4 / CE3 CE5 are in group C , CE3 has higher count
A3 12 / A3 is the only one in group A
BRICK4 5 / BRICK4 BRICK2 are in group B, BRICK4 has higher count
How can I do that?
Edit:
what I thought to do is to create a temp column in a query that will represent the group something like:
val count group
CE3 4 C
CE5 1 C
A3 12 A
BRICK4 5 B
BRICK2 2 B
and then search for the row with the highest count value per group.
But i'm not sure this is the best approach
Try something like this:
select
val
,MAX(count) as count
,left(val,1) as first_letter
from (
select
val
,count(val) as count
from tbl
group by val
) a
group by left(val, 1);
First get count per val and from this result get the MAXcount grouping by first letter
UPDATE: (thx to Vamsi Prabhala for pointing it out that my first solution wasn't the best one)
After get the count per val, I used a variable to redo the ROW_NUMBER() functionality (from MS-SQL) and select the first row from result, ordered by first_letter and count desc
select val, count, first_letter from (
select
#i:=CASE
WHEN #first_letter = first_letter THEN #i + 1
ELSE 1
END as rn
,#first_letter:= a.first_letter as First_letter
,a.val
,a.count
from (
select
val
,count(val) as count
,left(val,1) as first_letter
from tbl
group by val
)a, (select #i:=0) b
order by First_letter, count desc
) c
where rn = 1
This can be done with variables to rank the rows based on counts.
select val,val_cnt
from (
select val,val_cnt,#rn:=case when #prev=left(val,1) then #rn+1 else 1 end as rnum,
#prev:=left(val,1)
from (select val,count(val) as val_cnt
from ....
where ...
group by val
) t
cross join (select #rn:=0,#prev:='') r
order by left(val,1),val_cnt desc,val --added val to order by to break ties
) t
where rnum=1
Not sure it's what you want :
Let's get the max from each starting letter :
SELECT LEFT(val, 1) as l_val, MAX(count_val)
FROM (
select val,count(val) as count_val
from ....
where ...
group by val
) t
GROUP BY l_val
THEN as you want the val to appear :
SELECT t2.val, t1.max_val
FROM (
SELECT LEFT(val, 1) as l_val, MAX(count_val) as max_val
FROM (
select val,count(val) as count_val
from ....
where ...
group by val
) t
GROUP BY l_val) t1
INNER JOIN `YOUR_table` t2 ON LEFT(t2.val,1) = t1.l_val

get number of occurrences between two columns same table

I have a problem on how to fetch the number of occurrences of a value in between two columns in MySQL.
id1 col1 col2
2 5 3
3 3 4
4 2 1
5 1 3
6 null 2
How am I able to get the number of occurences between the two columns like the following?
value occurrence
3 3
1 2
2 2
4 1
5 1
You could union all the columns and then apply a count aggregate function:
SELECT val, COUNT(*) AS occurrence
FROM (SELECT col1 AS val
FROM mytable
UNION ALL
SELECT col2 AS val
FROM mytable) x
GROUP BY val
ORDER BY occurrence DESC
Depending on the actual data (number of rows per value) pre-aggregating might be more efficient, simply try it out:
SELECT val, SUM(occurrence) AS occurrence
FROM (SELECT col1 AS val, COUNT(*) AS occurrence
FROM mytable
UNION ALL
SELECT col2 AS val, COUNT(*) AS occurrence
FROM mytable) x
GROUP BY val
ORDER BY occurrence DESC

MySQL - use a conditional with count?

I have the following sql statement:
select val, count(*) as tally from sometable group by val order by tally desc
Which produces the following (example) table:
val | tally
----+-------
4 | 20
5 | 10
6 | 5
7 | 3
8 | 2
Now, I want to only display the rows where tally > 5, so the result will be:
val | tally
----+-------
4 | 20
5 | 10
I tried this statement, but it does not work (it says "tally" is unknown):
select val, count(*) as tally from sometable where tally > 5 group by val order by tally desc
Try HAVING (more information):
select val, count(*) as tally from sometable group by val having tally > 5 order by tally desc
There are two approaches you can take:
1: Use HAVING. This means that the query results will be filtered as per the HAVING clause and only some of the original result rows will be returned to you:
SELECT val, COUNT( * ) AS tally
FROM sometable
GROUP BY val
ORDER BY tally DESC
HAVING tally > 5
2: Use a sub-select. This may give you better performance (I 'm no sql guru though so I can't say with confidence):
SELECT val, tally
FROM (SELECT val, COUNT( * ) AS tally
FROM sometable
GROUP BY val
ORDER BY tally DESC)
temp
WHERE tally >1