How count the number of rows starting from a specified row? - mysql

I have a query problem about retrieving number of rows that the counting will start from the specified row. These are the row values on my MySQL database.
John Parker
Tony Graham
Perter Smith
Annabelle Sergio
Kris Pata
Neshren Luca
Paul Pon
Zervich Nuckrav
Allan Paulson
Imanu Hashmarida
Varick Dagovich
Senster Burgsman
Lito Umani
Ramsay Nudillo
Now I want to retrieve the first 5 row that will start from Neshren Luca so that the resultset may look like this:
Neshren Luca
Paul Pon
Zervich Nuckrav
Allan Paulson
Imanu Hashmarida
How may I do that?
Note: I will not try to retrieve the row values base on number of row but base from a specific row value.

This looks very ugly but have this a try, this uses local variable
SET #selectedName := 'Neshren Luca'; -- set the name here
SET #selectRow :=
(
SELECT RankNo
FROM
(
SELECT #rowNum := #rowNum + 1 AS RankNo,
a.Names
FROM tableName a, (SELECT #rowNum:= 0) b
) x
WHERE Names = #selectedName
);
SELECT Names
FROM
(
SELECT #rowNum1 := #rowNum1 + 1 AS RankNo, a.Names
FROM tableName a, (SELECT #rowNum1:= 0) b
) x
WHERE RankNo BETWEEN #selectRow AND #selectRow + 4
SQLFiddle Demo

Looks like the above select statement will work fine for that..other wise you have to do other way but it will bit lengthy..that..
-> You have to get the above records into a cursor
-> Looping through the cursor and getting the records..

SQL engine may have some 'natural' row order, but it is not guaranteed. You should have primary key, say ID, then ORDER BY ID, find first ID WHERE your string is. And then select first N items WHERE ID >= that id.
Edit: Or, if your database guarantees natural order, find row id/index and LIMIT/OFFSET by this value. SQLite has built-in natural rowid for each table row, for example.

Related

Sort alphabetically and get only first of every letter of the alphabet

I have a MySQL table called names with a column names name.
This column has the following values:
Beacher
Ackley
Addison
Caldwell
Cameron
Alcott
Carling
Beardsley
Beldon
I want to get, alphabetically sorted, the value of every first letter.
Alphabetically sorted the list above is like this:
Ackley
Addison
Alcott
Beacher
Beardsley
Beldon
Caldwell
Cameron
Carling
The first value starting with A is Ackley, the first value starting with B is Beacher …
So I want the following output:
Ackley
Beacher
Caldwell
Is this even possible with a single SQL query? How?
The answers that use the row_number() window function require that you have MySQL 8.0.
Here's a solution that should work on MySQL 5.x.
select left(name, 1) as c, min(name) from names group by c;
+------+-----------+
| c | min(name) |
+------+-----------+
| A | Ackley |
| B | Beacher |
| C | Caldwell |
+------+-----------+
You can do it using row_number:
select nombre
from (
select
row_number() over (partition by left(nombre, 1) order by nombre asc) as rn,
nombre
from names
) n
where rn = 1
You can test on this db<>fiddle
with main as (
select
*,
row_number() over(partition by left(column,1) order by column) as rank_
)
select column where rank_ = 1
Steps:
column = your actual column name
First we are creating a CTE that does two things. It creates a rank using the windows function row_number(). This rank is build using the following logic: Pick the column's first letter i.e the use of LEFT() function(1 specifies the placement i.e the first letter in our case). This will eventually tell SQL to pick every word's first letter. The second functionality is the ORDER BY within the window function.
The ORDER BY orders all the words based on the A-Z i.e Ascending order by default. If you use DESC it will return the last word for the same starting character.
Connecting both logics, what we get is the rank based on the ascending order of each word and the first character. [All the first words starting with each alphabet in a given data set]
In the final select we just filter the rank_ = 1 in the where clause
If you want the last word you can just use order by column DESC
IF windows function is not supported you can use the following solution, the logic remains almost the same:
with main as (
select
column,
left(column,1) as first_letter_per_word
from [table_name]
)
select
first_letter_per_word,
min(column) as word
from main
group by 1 -- by default it is ASC

Creating new data table on existing one

Hello I've got a question, how (if it possible), I can create new datatables with close same rows but if In column value is in string "/" for example
ID
column_param
column_sym
column_value
column_val2
First
param_test1
ABC
11/12
test
Second
param_test2
CDE
22/11
test
Third
param_test3
EFG
44
teste
4'th
param_test4
HIJ
33/22
test
And here if I have param_test1 and param_test4 and if in this column value has "/" I want to create 2 other rows but if I will not set param_test2 then it stay as it is and everything should be in new datatable. Is any way to create this?
Thank you in advance.
Expected result:
As per Gordon's answer, I'm not sure what should be done with the your ID column.
I've replaced these with row numbers.
Depending on your version of MySQL/MariaDB, the ROW_NUMBER() window function may not be available. Depending on whether IDs in the output are necessary you may be able to simply omit this.
I've assumed the existence of a table called myNumbers which contains a single field num and is populated with positive integers from 1 to whatever you're likely to need.
I've included more in the output that you asked for, which will hopefully help you understand what's going on
SELECT
ROW_NUMBER() OVER (ORDER BY d.ID, n.num) as NewID,
d.ID as OriginalID,
n.num as,
d.column_param,
d.column_sym,
d.column_value as orig_value,
CASE WHEN column_param = 'param_test2' THEN d.column_value
ELSE substring_index(substring_index(d.column_value,'/',n.num),'/',-1) END as split_value,
d.column_val2
FROM
myData d
JOIN myNumbers n on char_length(d.column_value)-char_length(replace(d.column_value,'/','')) >= n.num-1
WHERE
n.num = 1 OR d.column_param <> 'param_test2'
ORDER BY
d.ID,
n.num
See this DB Fiddle (the columns output in a different order than I've specified, but I think that's a DB Fiddle quirk).
If you only want to "split" say param_test1 and param_test4 rows the code above code could be amended as follows:
SELECT
ROW_NUMBER() OVER (ORDER BY d.ID, n.num) as NewID,
d.ID as OriginalID,
d.column_param,
d.column_sym,
n.num,
d.column_value as orig_value,
CASE WHEN column_param NOT IN ('param_test1','param_test4') THEN d.column_value
ELSE substring_index(substring_index(d.column_value,'/',n.num),'/',-1) END as split_value,
d.column_val2
FROM
myData d
JOIN myNumbers n on char_length(d.column_value)-char_length(replace(d.column_value,'/','')) >= n.num-1
WHERE
n.num = 1 OR d.column_param IN ('param_test1','param_test4')
ORDER BY
d.ID,
n.num
I don't know how the id is being set, but you can do what you want using union all:
select column_param, column_sym,
substring_index(column_value, '/', 1) as column_value,
column_val2
from t
union all
select column_param, column_sym,
substring_index(column_value, '/', -1) as column_value,
column_val2
from t
where column_value = '%/%';

select one row multiple time when using IN()

I have this query :
select
name
from
provinces
WHERE
province_id IN(1,3,2,1)
ORDER BY FIELD(province_id, 1,3,2,1)
the Number of values in IN() are dynamic
How can I get all rows even duplicates ( in this example -> 1 ) with given ORDER BY ?
the result should be like this :
name1
name3
name2
name1
plus I shouldn't use UNION ALL :
select * from provinces WHERE province_id=1
UNION ALL
select * from provinces WHERE province_id=3
UNION ALL
select * from provinces WHERE province_id=2
UNION ALL
select * from provinces WHERE province_id=1
You need a helper table here. On SQL Server that can be something like:
SELECT name
FROM (Values (1),(3),(2),(1)) As list (id) --< List of values to join to as a table
INNER JOIN provinces ON province_id = list.id
Update: In MySQL Split Comma Separated String Into Temp Table can be used to split string parameter into a helper table.
To get the same row more than once you need to join in another table. I suggest to create, only once(!), a helper table. This table will just contain a series of natural numbers (1, 2, 3, 4, ... etc). Such a table can be useful for many other purposes.
Here is the script to create it:
create table seq (num int);
insert into seq values (1),(2),(3),(4),(5),(6),(7),(8);
insert into seq select num+8 from seq;
insert into seq select num+16 from seq;
insert into seq select num+32 from seq;
insert into seq select num+64 from seq;
/* continue doubling the number of records until you feel you have enough */
For the task at hand it is not necessary to add many records, as you only need to make sure you never have more repetitions in your in condition than in the above seq table. I guess 128 will be good enough, but feel free to double the number of records a few times more.
Once you have the above, you can write queries like this:
select province_id,
name,
#pos := instr(#in2 := insert(#in2, #pos+1, 1, '#'),
concat(',',province_id,',')) ord
from (select #in := '0,1,2,3,1,0', #in2 := #in, #pos := 10000) init
inner join provinces
on find_in_set(province_id, #in)
inner join seq
on num <= length(replace(#in, concat(',',province_id,','),
concat(',+',province_id,',')))-length(#in)
order by ord asc
Output for the sample data and sample in list:
| province_id | name | ord |
|-------------|--------|-----|
| 1 | name 1 | 2 |
| 2 | name 2 | 4 |
| 3 | name 3 | 6 |
| 1 | name 1 | 8 |
SQL Fiddle
How it works
You need to put the list of values in the assignment to the variable #in. For it to work, every valid id must be wrapped between commas, so that is why there is a dummy zero at the start and the end.
By joining in the seq table the result set can grow. The number of records joined in from seq for a particular provinces record is equal to the number of occurrences of the corresponding province_id in the list #in.
There is no out-of-the-box function to count the number of such occurrences, so the expression at the right of num <= may look a bit complex. But it just adds a character for every match in #in and checks how much the length grows by that action. That growth is the number of occurrences.
In the select clause the position of the province_id in the #in list is returned and used to order the result set, so it corresponds to the order in the #in list. In fact, the position is taken with reference to #in2, which is a copy of #in, but is allowed to change:
While this #pos is being calculated, the number at the previous found #pos in #in2 is destroyed with a # character, so the same province_id cannot be found again at the same position.
Its unclear exactly what you are wanting, but here's why its not working the way you want. The IN keyword is shorthand for creating a statement like ....Where province_id = 1 OR province_id = 2 OR province_id = 3 OR province_id = 1. Since province_id = 1 is evaluated as true at the beginning of that statement, it doesn't matter that it is included again later, it is already true. This has no bearing on whether the result returns a duplicate.

SQL sort column A & update column B

I have a table with data similar to:
Inven DESCRIPT PrintOrder
---------------------------------
1 D 9
2 B 0
3 A 5
4 Z 0
5 X 1
. . .
. . .
. . .
I would like to sort the table on column DESCRIPT descending alpha (A - Z) and then update the column PRINTORDER so that when done, the record with PRINTORDER = 1 is the the highest alpha (A) and the record with the highest value for PRINTORDER will be the lowest in alpha (Z).
Is this possible without using temporary columns? Not a deal breaker if not, just a preference.
DESIRED RESULT:
To update the PrintOrder values based on the sorting result
Inven DESCRIPT PrintOrder
---------------------------------
1 D 3
2 B 2
3 A 1
4 Z 5
5 X 4
I am unclear whether you want to modify the table or just create a result set. Here is the solution to the latter using standard SQL:
select Inven, Descript, row_number() over (order by Descript) as PrintOrder
from table t
order by Descript;
EDIT:
In MySQL, the select would look like:
select t.*
from (select Inven, Descript, (#rn := #rn + 1) as PrintOrder
from table t cross join (select #rn := 0) params
order by Descript
) t
order by Inven;
The update is slightly trickier, because you cannot sort and initialize a variable:
update t
set PrintOrder = (#rn := coalesce(#rn + 1, 1))
order by Descript;
This should do it:
Update T
set PRINTORDER = ASCII(UPPER(DESCRIPT)) - 64
while the ASCII of A is 65 in your system. :)
EDIT:
As you said there might be something more, so we need to change it a bit by assuming you have a PK column name as Id:
UPDATE T
set PRINTORDER = T2.Order
FROM T
INNER JOIN (SELECT IDENTITY(int, 1,1) AS Order,Id FROM T Order By DESCRIPT ASC) T2 ON T.Id=T2.Id
But in this way even two record has same DESCRIPT would end up have different printorder, it could be a bit more complex if you want keep them the same.
EDIT:
Sorry just realized you are using MySql, the IDENTITY(int, 1,1) is from SQL Server, basically it generate a row number, you might need something different to have one: http://blog.sqlauthority.com/2014/03/08/mysql-generating-row-number-for-each-row-using-variable/
After arriving at my office, and chatting with my colleague, the question posed is completely inaccurate. So I'll close this thread, and an open a new one with a more accurate description of the issue.
Thank you Gordon & Simon for your responses, they are most appreciated.

Create column corresponding to entry rank position in SQL

I am using SQL and I have a table that looks like:
variable1 123
variable2 39
variable3 993
variable4 2
and I would like to add a column that corresponds to the rank of the integer, had that column been sorted. ie:
variable1 123 3
variable2 39 2
variable3 993 4
variable4 2 1
So far I haven't been able to get the syntax correct - I can sort or order by that column but not generate the correct ranking variable.
Many thanks.
The best performant way is to use variables:
select t.*,
(#rn := #rn + 1) as rank
from table t cross join
(select #rn := 0) params
order by intcol;
Note: this technically does a row_number() but the two are equivalent for your sample data, because there are no ties.
it sounds like you'd need to create a trigger before insert that would update that column to increase the rank integer for each variable that held a value higher than the value which you are about to insert by 1.