How can I sequence a column within a table using SQL (MySQL)? - mysql

I have a list mapped using Hibernate with an index column. The table looks like this:
mysql> select * from chart;
+----+--------+------+-----------------------+
| id | report | indx | name |
+----+--------+------+-----------------------+
| 2 | 1 | 0 | Volume |
| 3 | 2 | 0 | South Africa (Volume) |
| 5 | 2 | 2 | People |
| 6 | 2 | 3 | Platforms |
| 7 | 2 | 4 | People (Gender) |
+----+--------+------+-----------------------+
As you can see chart id=4 for report=2 with indx=1 has been deleted.
I need to eliminate the gaps so all the indx values for a given report run in sequence from 0. I could write Java code to sort this out but SQL solution would be much easier to deploy.
Anyone know how to do this? I am using MySQL.

if this technique works on your version of MYSQL you can try
SET #row := -1;
UPDATE chart
SET indx = #row := #row + 1
WHERE report = 2
ORDER BY indx
but I think better to change design.

Related

MYSQL Adding plus one in loop or for each

I have next quarry:
UPDATE PREDMETIF t
CROSS JOIN (select COALESCE(MAX(predf_aa),0) max_predf_aa from PREDMETIF) m
set t.predf_aa = m.max_predf_aa + 1
where t.strf_ID = '1';
Witch gives me result of:
| predf_aa | strp_ID |
| -------- | ------- |
| 1 | 1 |
| 1 | 1 |
| null | 2 |
And I need:
| predf_aa | strp_ID |
| -------- | ------- |
| 1 | 1 |
| 2 | 1 |
| null | 2 |
I have made Example fiddle
I find that closest thing to for each is PROCEDURE with LOOP but never used that and it seems a bit overkill. What is simplest way to achieve this with given example?
And if LOOP Statement is what I need I would appreciate if someone would write it for me on this example and ill decode it for future references.
I think you want variables:
select #rn := COALESCE(MAX(predf_aa), 0) as max_predf_aa
from PREDMETIF;
update PREDMETIF t
set t.predf_aa = (#rn := #rn + 1)
where t.strf_ID = 1;
That said, if you want a unique id on each row, then perhaps you want an auto_increment column. Of course, you would not have null in that column, so it is not clear if that is a viable solution for you.

Making changes to multiple records based on change of single record with SQL

I have a table of food items. They have a "Position" field that represents the order they should appear in on a list (listID is the list they are on, we don't want to re-order items on another list).
+--id--+--listID--+---name---+--position--+
| 1 | 1 | cheese | 0 |
| 2 | 1 | chips | 1 |
| 3 | 1 | bacon | 2 |
| 4 | 1 | apples | 3 |
| 5 | 1 | pears | 4 |
| 6 | 1 | pie | 5 |
| 7 | 2 | carrots | 0 |
| 8,9+ | 3,4+ | ... | ... |
+------+----------+----------+------------+
I want to be able to say "Move Pears to before Chips" which involves setting the position of Pears to position 1, and then incrementing all the positions inbetween by 1. so that my resulting Table look like this...
+--id--+--listID--+---name---+--position--+
| 1 | 1 | cheese | 0 |
| 2 | 1 | chips | 2 |
| 3 | 1 | bacon | 3 |
| 4 | 1 | apples | 4 |
| 5 | 1 | pears | 1 |
| 6 | 1 | pie | 5 |
| 7 | 2 | carrots | 0 |
| 8,9+ | 3,4+ | ... | ... |
+------+----------+----------+------------+
So that all I need to do is SELECT name FROM mytable WHERE listID = 1 ORDER BY position and I'll get all my food in the right order.
Is it possible to do this with a single query? Keep in mind that a record might be moving up or down in the list, and that the table contains records for multiple lists, so we need to isolate the listID.
My knowledge of SQL is pretty limited so right now the only way I know of to do this is to SELECT id, position FROM mytable WHERE listID = 1 AND position BETWEEN 1 AND 5 then I can use Javascript (node.js) to change position 5 to 1, and increment all others +1. Then UPDATE all the records I just changed.
It's just that anytime I try to read up on SQL stuff everyone keeps saying to avoid multiple queries and avoid doing syncronous coding and stuff like that.
Thanks
This calls for a complex query that updates many records. But a small change to your data can change things so that it can be achieved with a simple query that modifies just one record.
UPDATE my_table set position = position*10;
In the old days, the BASIC programming language on many systems had line numbers, it encouraged spagetti code. Instead of functions many people wrote GOTO line_number. Real trouble arose if you numbered the lines sequentially and had to add or delete a few lines. How did people get around it? By increment lines by 10! That's what we are doing here.
So you want pears to be the second item?
UPDATE my_table set position = 15 WHERE listId=1 AND name = 'Pears'
Worried that eventually gaps between the items will disappear after multiple reordering? No fear just do
UPDATE my_table set position = position*10;
From time to time.
I do not think this can be conveniently done in less than two queries, which is OK, there should be as few queries as possible, but not at any cost. The two queries would be like (based on what you write yourself)
UPDATE mytable SET position = 1 WHERE listID = 1 AND name = 'pears';
UPDATE mytable SET position = position + 1 WHERE listID = 1 AND position BETWEEN 2 AND 4;
I've mostly figured out my problem. So I've decided to put an answer here incase anyone finds it helpful.
I can make use of a CASE statement in SQL. Also by using Javascript beforehand to build my SQL query I can change multiple records.
This builds my SQL query:
var sql;
var incrementDirection = (startPos > endPos)? 1 : -1;
sql = "UPDATE mytable SET position = CASE WHEN position = "+startPos+" THEN "+endPos;
for(var i=endPos; i!=startPos; i+=incrementDirection){
sql += " WHEN position = "+i+" THEN "+(i+incrementDirection);
}
sql += " ELSE position END WHERE listID = "+listID;
If I want to move Pears to before Chips. I can set:
startPos = 4;
endPos = 1;
listID = 1;
My code will produce an SQL statement that looks like:
UPDATE mytable
SET position = CASE
WHEN position = 4 THEN 1
WHEN position = 1 THEN 2
WHEN position = 2 THEN 3
WHEN position = 3 THEN 4
ELSE position
END
WHERE listID = 1
I run that code and my final table will look like:
+--id--+--listID--+---name---+--position--+
| 1 | 1 | cheese | 0 |
| 2 | 1 | chips | 2 |
| 3 | 1 | bacon | 3 |
| 4 | 1 | apples | 4 |
| 5 | 1 | pears | 1 |
| 6 | 1 | pie | 5 |
| 7 | 2 | carrots | 0 |
| 8,9+ | 3,4+ | ... | ... |
+------+----------+----------+------------+
After that, all I have to do is run SELECT name FROM mytable WHERE listID = 1 ORDER BY position and the output will be as follows::
cheese
pears
chips
bacon
apples
pie

Grouping data in a SSRS report

In developing an SSRS 2008 R2 report, I'll like to show some data grouped by values, while merging others, I've run into a problem doing this on report builder.
| Parent Group |
|_______________________________________|
|Group A|Group B|Group C|Group D|Group E|
|_______|_______|_______|______|________|
| 5 | 2 | 1 | 1 | 5 |
| 4 | 2 | 4 | 2 | 2 |
| 1 | 3 | 1 | 3 | 2 |
Can I create a filter or grouping to combine Group C, D, E together while leaving A and B alone?
Like such,
| Parent Group |
|_____________________________|
|Group A |Group B|Other Groups|
|________|_______|____________|
| 5 | 2 | 7 |
| 4 | 2 | 8 |
| 1 | 3 | 6 |
There are two ways you can achieve this:
SQL query
Usually the best way to get the result you want is to let SQL do the heavy lifting:
SELECT GroupA, GroupB, (GroupC + GroupD + GroupE) AS OtherGroups
FROM MyTable
In the report
If you can't change your query result (for example, it is a stored procedure) then you can do the same thing in SSRS VB code.
Right-click the cell and choose Expression... and enter something like the following:
=Fields!GroupC.Value + Fields!GroupD.Value + Fields!GroupE.Value
and SUM in the same way:
=SUM(Fields!GroupC.Value) + SUM(Fields!GroupD.Value) + SUM(Fields!GroupE.Value)

MySQL: optimize query for scoring calculation

I have a data table that I use to do some calculations. The resulting data set after calculations looks like:
+------------+-----------+------+----------+
| id_process | id_region | type | result |
+------------+-----------+------+----------+
| 1 | 4 | 1 | 65.2174 |
| 1 | 5 | 1 | 78.7419 |
| 1 | 6 | 1 | 95.2308 |
| 1 | 4 | 1 | 25.0000 |
| 1 | 7 | 1 | 100.0000 |
+------------+-----------+------+----------+
By other hand I have other table that contains a set of ranges that are used to classify the calculations results. The range tables looks like:
+----------+--------------+---------+
| id_level | start | end | status |
+----------+--------------+---------+
| 1 | 0 | 75 | Danger |
| 2 | 76 | 90 | Alert |
| 3 | 91 | 100 | Good |
+----------+--------------+---------+
I need to do a query that add the corresponding 'status' column to each value when do calculations. Currently, I can do that adding the following field to calculation query:
select
...,
...,
[math formula] as result,
(select status
from ranges r
where result between r.start and r.end) status
from ...
where ...
It works ok. But when I have a lot of rows (more than 200K), calculation query become slow.
My question is: there is some way to find that 'status' value without do that subquery?
Some one have worked on something similar before?
Thanks
Yes, you are looking for a subquery and join:
select s.*, r.status
from (select s.*
from <your query here>
) s left outer join
ranges r
on s.result between r.start and r.end
Explicit joins often optimize better than nested select. In this case, though, the ranges table seems pretty small, so this may not be the performance issue.

Sort table records in special order

I have table:
+----+--------+----------+
| id | doc_id | next_req |
+----+--------+----------+
| 1 | 1 | 4 |
| 2 | 1 | 3 |
| 3 | 1 | 0 |
| 4 | 1 | 2 |
+----+--------+----------+
id - auto incerement primary key.
nex_req - represent an order of records. (next_req = id of record)
How can I build a SQL query get records in this order:
+----+--------+----------+
| id | doc_id | next_req |
+----+--------+----------+
| 1 | 1 | 4 |
| 4 | 1 | 2 |
| 2 | 1 | 3 |
| 3 | 1 | 0 |
+----+--------+----------+
Explains:
record1 with id=1 and next_req=4 means: next must be record4 with id=4 and next_req=2
record4 with id=5 and next_req=2 means: next must be record2 with id=2 and next_req=3
record2 with id=2 and next_req=3 means: next must be record3 with id=1 and next_req=0
record3 with id=3 and next_req=0: means that this is a last record
I need to store an order of records in table. It's important fo me.
If you can, change your table format. Rather than naming the next record, mark the records in order so you can use a natural SQL sort:
+----+--------+------+
| id | doc_id | sort |
+----+--------+------+
| 1 | 1 | 1 |
| 4 | 1 | 2 |
| 2 | 1 | 3 |
| 3 | 1 | 4 |
+----+--------+------+
Then you can even cluster-index on doc_id,sort for if you need to for performance issues. And honestly, if you need to re-order rows, it is not any more work than a linked-list like you were working with.
Am able to give you a solution in Oracle,
select id,doc_id,next_req from table2
start with id =
(select id from table2 where rowid=(select min(rowid) from table2))
connect by prior next_req=id
fiddle_demo
I'd suggest to modify your table and add another column OrderNumber, so eventually it would be easy to order by this column.
Though there may be problems with this approach:
1) You have existing table and need to set OrderNumber column values. I guess this part is easy. You can simply set initial zero values and add a CURSOR for example moving through your records and incrementing your order number value.
2) When new row appears in your table, you have to modify your OrderNumber, but here it depends on your particular situation. If you only need to add items to the end of the list then you can set your new value as MAX + 1. In another situation you may try writing TRIGGER on inserting new items and calling similar steps to point 1). This may cause very bad hit on performance, so you have to carefully investigate your architecture and maybe modify this unusual construction.