MySQL delete records between 'n' rows - mysql

I am trying to select records between let's say 2nd and 5th row. My id's are not in sequentially order so I tried to retrieve the row_number this way:
SELECT #curRow := #curRow + 1 AS row_number
FROM product_image p
JOIN (SELECT #curRow := 0) r
WHERE row_number BETWEEN 2 AND 5
My table is kind of:
id name
23 A
42 B
98 C
102 D
109 E
What I get is row_number column doesn't exist and it really does not exist. But how can retrieve the records between 2 and 5 ( 3th and 4th )? I red similar post but didn't understand the query well. Thank you in advance!

The reason the query fails is that you can't use column aliases in a WHERE clause. You can however use them in a GROUP BY and HAVING:
SELECT *, #curRow := #curRow + 1 AS row_number
FROM product_image p
JOIN (SELECT #curRow := 0) r
GROUP BY row_number
HAVING row_number between 2 and 5

Since you don't really need to select the row number, there is no need to rely on undocumented "features" like #curRow := #curRow + 1. Just use LIMIT and OFFSET. To select the rows from 2 to 5 you would just need
select id
from product_image
order by name, id
limit 4
offset 1
Note that you need a well defined ORDER BY clause. Otherwise the result can depend on what index has been used by the engine.
To delete those rows, use a DELETE .. JOIN statement
delete product_image
from product_image
join (
select id
from product_image
order by name, id
limit 4
offset 1
)x using(id)
Demo: http://sqlfiddle.com/#!9/66fa4/1
No tricks. No complex queries.

You can do a DELETE with JOIN:
DELETE p
FROM product_image p
INNER JOIN (
SELECT id, #curRow := #curRow + 1 AS row_number
FROM product_image p
CROSS JOIN (SELECT #curRow := 0) r
ORDER BY id
) AS t ON p.id = t.id
WHERE t.row_number IN (2,3)
Derived tabe t is created using your query. You can join to this table in order to identify any records you want and delete them.
Demo here

Check this -
http://sqlfiddle.com/#!9/c462d/5
select * from
(
SELECT #curRow := #curRow + 1 AS row_number,p.*
FROM product_image p
JOIN (SELECT #curRow := 0) r)
a
WHERE a. row_number BETWEEN 2 AND 5

To add a synthetic row number to a query, you need a subquery. Like so.
SELECT id FROM (
SELECT p.*, #curRow := #curRow + 1 AS row_number
FROM product_image p
JOIN (SELECT #curRow := 0) r
) q
WHERE row_number BETWEEN 2 AND 5
Then, you can, if you wish, use the result to drive a delete operation.
DELETE FROM product_image
WHERE id IN (
SELECT id FROM (
SELECT p.*, #curRow := #curRow + 1 AS row_number
FROM product_image p
JOIN (SELECT #curRow := 0) r
) q
WHERE row_number BETWEEN 2 AND 5
)
But this is an extremely bad thing to do. Why? You're relying on a particular order in the result set of your inner query. The rows in a SQL result set are, without any ORDER BY clause, returned in an unpredictable order. Server optimizers exploit this. Unpredictable is like random, but worse. Random implies that it's unlikely the order will be the same each time you run the query. Random here is good because you'll catch your problem during testing. Unpredictable, on the other hand, means the order remains the same until it doesn't. If you're not sure why that's bad, look up Murphy's law.

Related

User-defined variable in ranking statement

I will appreaciate any help on this issue. I already spent hours without any real solution.
I have a SQL
SELECT to_place, rank
FROM
(SELECT g1.to_place as to_place, g1.pcount as pcount,
#rank := IF(#current_to_place = g1.to_place, #rank + 1, 1) AS rank,
#current_to_place := g1.to_place
FROM
(select
to_place, count(*) as pcount
from temp_workflows
group by to_place
order by to_place,pcount desc) g1
ORDER BY g1.to_place, g1.pcount DESC) ranked
In table g1, I am grouping my data to find the most common occurrence of to_place.And then I want to rand those occurrences in ascending order (so I can later select top 3 of the most common occurrences per each to_place category.
The issue is that the user-defined variable is unpredictable (#rank is sometimes always 1) which probably is related to the fact that in one statement, I should not reference the same variable (current_to_place). I read a lot about using separate statements etc. but I could find a way to write my statement in a different way. How can I define #current_to_place elsewhere so the result is the same?
Thanks in advance for your help.
I think you should be testing pcount to get rank and you should initialise variables
DROP TABLE IF EXISTS T;
CREATE TABLE T
(to_place int);
insert into t values (1),(2),(2),(3),(3),(3);
SELECT to_place, rank
FROM
(
SELECT g1.to_place as to_place, g1.pcount as pcount,
#rank := IF(#current_to_place <> pcount, #rank + 1, 1) AS rank,
#current_to_place := pcount
FROM
(select
to_place, count(*) as pcount
from t
group by to_place
order by to_place,pcount desc) g1
cross join(select #rank:=0,#current_to_place:=0) r
ORDER BY g1.pcount DESC
)
ranked
+----------+------+
| to_place | rank |
+----------+------+
| 3 | 1 |
| 2 | 2 |
| 1 | 3 |
+----------+------+
3 rows in set (0.016 sec)

Strange behavior of temporary auto increment column in MySQL against HAVING clause

Assume we have a table named Test with a single column col1 (Varchar(10)).
For the sake of this example, assume it has the following data:
col1
a
b
c
d
Now, I want to select the data in col1 and add a temporary auto-increment column, which we will call Rank. The following query does the job:
SELECT
(#cnt := #cnt + 1) AS Rank, Test.col1
FROM Test
CROSS JOIN (SELECT #cnt := 0) AS tmp;
The result is
Rank col1
1 a
2 b
3 c
4 d
No problems so far. Now assume we need to select the rows with Rank larger than 1. I do the following:
SELECT
(#cnt := #cnt + 1) AS Rank, Test.col1
FROM Test
CROSS JOIN (SELECT #cnt := 0) AS tmp
having Rank > 1;
The result becomes
Rank col1
3 b
5 c
7 d
Notice that the first item in the Rank column is 3 rather than 2 as one would expect naturally. Can someone please point out a possible reason for this?
Why does the Rank (the temporary auto-increment) jump form 2 to 3 ?
The MySQL version is 5.6.32-78.1.
There are a lot of unexpected things that can happen when you use variables. Some of those are listed in the manual:
In a SELECT statement, each select expression is evaluated only when sent to the client. This means that in a HAVING, GROUP BY, or ORDER BY clause, referring to a variable that is assigned a value in the select expression list does not work as expected
The exact details vary, but in your case, your increment is evaluated twice (once in the select, once for having), so it basically behaves like
SELECT (#cnt := #cnt + 1) AS Rank, Test.col1
FROM Test
CROSS JOIN (SELECT #cnt := 0) AS tmp
having (#cnt := #cnt + 1) > 1;
One way around this is to force MySQL to evaluate the expression beforehand.
The proper solution (as far as possible when using variables) is probably:
select (
SELECT (#cnt := #cnt + 1) AS Rank, Test.col1
FROM Test
CROSS JOIN (SELECT #cnt := 0) AS tmp
) x
where Rank > 1;
where Rank > 1 (without the subquery) is likely what you originally intended to do anyway.
But you can also do this by other means. In your example, you should be able to use
SELECT (#cnt := #cnt + 1) AS Rank, Test.col1
FROM Test
CROSS JOIN (SELECT #cnt := 0) AS tmp
group by col1
having Rank > 1;
Unless col1 is a primary key candidate (e.g. unique not null), MySQL needs to actually evaluate the expression to do the group by. On the other hand, if col1 is e.g. the primary key, MySQL can optimize the group by away and you are back at your original situation - if it does do it may depend on your MySQL version, but afaik MySQL 5.6 should do this.
Side note: since rank became a keyword in MySQL 8, you should not use it as an alias in case you ever intend to upgrade.

MySQL COUNT of distinct values from two independent columns of a table

I want to get the COUNT of distinct values from two independent columns of a table.
My table is:
ID CR PB DB CB
-----------------------------
1 1000 1000
2 60000 1000
3 1000 (NULL)
4 1500000 13000
5 60000 12000
6 1000 (NULL)
expected output:
CR PB cnt_crpb DB CB cnt_dbcb
1000 3 1000 2
60000 2 13000 1
1500000 1 12000 1
I have tried to separate both columns CR PB and DB CB in two different tables and joined them using LEFT JOIN but does not give expected output as MySQL does not support FULL OUTER JOIN.
I have also tried using UNION which but gives result in rows.
Any help will be appreciated...
Thanks you.
I think you need to do this using union all:
select max(CRPB) as CRPB, max(CRPB_cnt) as CRPB_cnt, max(DBCB) as DBCB, max(DBCB_cnt) as DBCB_cnt
from ((select (#rn1 := #rn1 + 1) as rn, CRPB, count(CRPB) as CRPB_cnt, NULL as DBCB, NULL as DBCB_cnt
from table t cross join
(select #rn1 := 0) as vars
group by CRPB
) union all
(select (#rn2 := #rn2 + 1) as rn, NULL, NULL, DBCB, count(DBCB) as DBCB_cnt
from table t cross join
(select #rn2 := 0) as vars
group by DBCB
)
) x
group by rn;
This will guarantee results regardless of which list is longest.
Note you need to determine which column will produce more results aka either CR PB or DB CB whichever produces the most results will be the first select you want to do then left join the other. assuming that there is an uneven number of results from the two
SELECT `CR PB`, cnt_crpb, `DB CB`, cnt_dbcb
FROM
( SELECT `CR PB`, COUNT(*) as cnt_crpb, #a := #a + 1 as num_rows_a
FROM test_table
CROSS JOIN (SELECT #a := 0 ) temp
WHERE `CR PB` is not null
GROUP BY `CR PB`
)t
LEFT JOIN
( SELECT `DB CB`, COUNT(*) as cnt_dbcb, #b := #b + 1 as num_rows_b
FROM test_table
CROSS JOIN (SELECT #b := 0)temp1
WHERE `DB CB` is not null
GROUP BY `DB CB`
)t1 ON t1.num_rows_b = t.num_rows_a;
Fiddle Demo

Combining 2 tables to produce 1 output - SQL

I have a query below, and it works.
$query_for_cat3 = "SELECT sum,candidate_no,#curRank := #curRank + 1 AS rank
FROM (SELECT SUM(score) / 5 SUM,candidate_no
FROM SCORE
WHERE category_no='$category_no1'
GROUP BY candidate_no
) a, ( SELECT #curRank := 0 ) r
ORDER BY sum DESC,candidate_no DESC
LIMIT 5";
What I need to do is to combine this query to another table which is named candidates. It has columns candidate_no and candidate_name. I want to produce the candidate_name in respect to their corresponding candidate_no.
Please help. Thanks.
Is this is something you looking for?
SELECT temp.candidate_no,
C.candidatename,
temp.[sum],
temp.rank
FROM candidate C INNER JOIN
(SELECT sum,candidate_no,#curRank := #curRank + 1 AS rank
FROM (SELECT SUM(score) / 5 SUM,candidate_no
FROM SCORE
WHERE category_no='$category_no1'
GROUP BY candidate_no
) a, ( SELECT #curRank := 0 ) r
ORDER BY sum DESC,candidate_no DESC
LIMIT 5) As temp
ON C.candidate_no=temp.candidate_no
You can do a join like this
SELECT
table1.field
table2.field
FROM
table1
LEFT JOIN
table2
ON
table1.field=table2.field
If you want to select data from two table then you need to have primary-key or foreign key Method in both table...like if Candidate_no is in one table as primary key other table as Foreign key..then using this you can access data as::
select * from table_name1 where candidate_no=(select candidate_no from table_name2 where name="Charn");

php-sql limit query per category

I wanted to limit my query per category, I've seen a lot of same topic here but too complicated so I will ask another.
for example I have
id title category
1 one number
2 two number
3 three number
4 four number
5 a letter
6 b letter
7 c letter
and I wanted to limit my query, let say 2 per category so I have on my output like these
one
two
a
b
I got answer from diff topic
I'll post it here for others who will drop in this same question
SELECT * FROM (
SELECT
table.*,
#rn := CASE WHEN #category=category THEN #rn + 1 ELSE 1 END AS rn,
#category := category
FROM table, (SELECT #rn := 0, #category := NULL) AS vars
ORDER BY category
) AS T1
WHERE rn <= 2
Created using this link, there's an explanation there. This also works for a large number of categories, but beware, it can become very slow without the right keys defined.
set #num := 0, #category := '';
select title, category, #num := if(#category = category, #num +1, 1) as row_number,
#category := category as dummy
from test
group by category, title
having row_number <=2;
Tell me if i'm getting you right -
That's the sql -
SELECT * FROM `categories` ORDER BY `id` ASC LIMIT 0,2
What I did is this: Select all the items in categories table and order it by the id row, limit it for two results only and order it from the beggining