Insert record into table with position without updating all the records position field - mysql

I am using MySQL, I don't have a good way to do this.
I have a table with a position field, which I need to keep track having values from 1 to 10,000.
Let's say I insert a record in the middle at 5000th position. So position 5000 to 10,000 need to be updated to the new position; old 5000 become 5001, 5002 becomes 5003...
Is there a good way to implement this without affecting so many records, when 1 single position is added?
Adding from the position 1st is the worst.

I'd rethink the database design. If you're going to be limited to on the order of 10K records then it's not too bad, but if this is going to increase without bound then you'll want to do something else. I'm not sure what you are doing but if you want a simple ordering (assuming you're not doing a lot of traversal) then you can have a prev_id and next_id column to indicate sibling relationships. Here's the answer to your questions though:
update some_table
set some_position = some_position + 1
where some_position > 5000 and some_position < 10000

You can try the below approach :
USE tempdb;
GO
CREATE TABLE dbo.Test
(
ID int primary key clustered identity(1,1) ,
OrderNo int,
CreatedDate datetime
);
--Insert values for testing the approach
INSERT INTO dbo.Test
VALUES
(1, GETUTCDATE()),
(2, GETUTCDATE()),
(3, GETUTCDATE()),
(4, GETUTCDATE()),
(5, GETUTCDATE()),
(6, GETUTCDATE());
SELECT *
FROM dbo.Test;
INSERT INTO dbo.Test
VALUES
(3, GETUTCDATE()),
(3, GETUTCDATE());
SELECT *
FROM dbo.Test;
--To accomplish correct order using ROW_NUMBER()
SELECT ID,
OrderNo,
CreatedDate,
ROW_NUMBER() OVER(ORDER BY OrderNo, ID) AS Rno
FROM dbo.Test;
--Again ordering change
INSERT INTO dbo.Test
VALUES
(3, GETUTCDATE()),
(4, GETUTCDATE());
SELECT ID,
OrderNo,
CreatedDate,
ROW_NUMBER() OVER(ORDER BY OrderNo, ID) AS Rno
FROM dbo.Test
DROP TABLE dbo.Test;

Related

ROUND not working when row_number() function is present

This is a simplified version of what I'm trying to do.
I am trying to rank users based on how many miles they walked overall.
This data is stored in the table called walks. Every time a user makes a walk, an entry is added.
create temporary table walks
(
id int unsigned auto_increment primary key,
user_id int unsigned not null,
miles_walked float unsigned default '0' not null,
date date not null
);
To fill in the table:
insert into walks (user_id, miles_walked, date)
values
(1, 10.1, '2022-12-20'),
(2, 60.2, '2022-12-21'),
(3, 30.3, '2022-12-22'),
(1, 0.4, '2022-12-23'),
(2, 10.5, '2022-12-24'),
(3, 10.6, '2022-12-25'),
(1, 40.7, '2022-12-26'),
(2, 80.8, '2022-12-27'),
(3, 30.9, '2022-12-28');
select * from walks;
select user_id,
SUM(miles_walked) as miles_walked_total,
ROUND(SUM(miles_walked), 1) as miles_walked_total_rounded,
row_number() over (order by SUM(miles_walked) desc) as miles_rank
from walks
group by user_id
order by user_id
As you can see, rounding is WRONG for users with id 2 and 3. What happened? Like I said, this is a simplified example. In my real case, not just rounding, but the ranking is wrong for the whole set when I use functions like ROUND and LENGTH:
ROW_NUMBER() OVER (ORDER BY (SUM(LENGTH(reports.comments)) + SUM(report_items.report_items_characters_number)) DESC) AS ranking
I can't duplicate it in 8.0.30: https://dbfiddle.uk/y04TcMlp
I suspect it's a bug that's been fixed. I recommend you upgrade.

Looking For Most Frequent Values SQL Statement

I have a data set that looks like this:
id | Unit_Ids
1 | {"unit_ids" : ["5442","28397"]}
2 | {"unit_ids" : ["5442","3492","2290"]}
etc.
And I'm trying to find the most frequently appearing values in Unit_Ids. As in my example 5442 appears in both lines 1 and 2, it would be the most frequent value. I was just having trouble finding a good way of creating this statement.
Thank you in advanced!
EDIT: Sorry everyone I'm working with MySQL
If 2016+
Example
Declare #YourTable Table ([id] int,[Unit_Ids] varchar(max))
Insert Into #YourTable Values
(1,'{"unit_ids" : ["5442","28397"]}')
,(2,'{"unit_ids" : ["5442","3492","2290"]}')
Select top 1 value
From #YourTable A
Cross Apply OpenJSON([Unit_Ids],'$.unit_ids') R
Order By sum(1) over (partition by value) desc
Returns
value
5442
I'm assuming you are storing JSON strings in the Unit_Ids field. If you do that, you won't be able to extract or aggregate data stored in that field.
You can however create a child table and query it to get aggregated data. Ie:
-- Master table
create table parent(id number primary key);
-- List of units per parent
create table units(
id number not null,
parent_id number not null,
primary key (id, parent_id),
foreign key (parent_id) references parent(id)
);
-- Insert sample data
insert into parent values 1;
insert into parent values 2;
insert into units(parent_id, id) values(1, 5442);
insert into units(parent_id, id) values(1, 28397);
insert into units(parent_id, id) values(2, 5442);
insert into units(parent_id, id) values(2, 3492);
insert into units(parent_id, id) values(2, 2290);
-- Count the number of times a unit id is in the table
select id, count(id) from units group by id;

mysql running total as view

I am still an sql greenhorn and try to convert this script, building a running total as view in mysql:
DROP TABLE IF EXISTS `table_account`;
CREATE TABLE `table_account`
(
id int(11),
account int(11),
bdate DATE,
amount DECIMAL(10,2)
);
ALTER TABLE `table_account` ADD PRIMARY KEY(id);
INSERT INTO `table_account` VALUES (1, 1, '2014-01-01', 1.0);
INSERT INTO `table_account` VALUES (2, 1, '2014-01-02', 2.1);
INSERT INTO `table_account` VALUES (4, 1, '2014-01-02', 2.2);
INSERT INTO `table_account` VALUES (5, 1, '2014-01-02', 2.3);
INSERT INTO `table_account` VALUES (3, 1, '2014-01-03', 3.0);
INSERT INTO `table_account` VALUES (7, 1, '2014-01-04', 4.0);
INSERT INTO `table_account` VALUES (6, 1, '2014-01-06', 5.0);
INSERT INTO `table_account` VALUES (8, 1, '2014-01-07', 6.0);
SET #iruntot:=0.00;
SELECT
q1.account,
q1.bdate,
q1.amount,
(#iruntot := #iruntot + q1.amount) AS runningtotal
FROM
(SELECT
account AS account,
bdate AS bdate,
amount AS amount
FROM `table_account`
ORDER BY account ASC, bdate ASC) AS q1
This is much more faster than building a sum over the whole history on each line.
The problems I cannot solve are:
Set in view
Subquery in view
I think it might be posssible to use some kind of JOIN instead of "SET #iruntot:=0.00;"
and use two views to prevent the need of a subquery.
But I do know how.
Will be happy for any hints to try.
Regards,
Abraxas
MySQL doesn't allow subqueries in the from clause for a view. Nor does it allow variables. You can do this with a correlated subquery, though:
SELECT q.account, q.b_date, q.amount,
(SELECT SUM(q2.amount)
FROM myview1 q2
WHERE q2.account < q.account OR
q2.account = q.account and q2.date <= q.date
) as running total
FROM myview1 q;
Note that this assumes that the account/date column is unique -- no repeated dates for an account. Otherwise, the results will not be exactly the same.
Also, it seems a little strange that you are doing a running total across all accounts and dates. I might expect a running total within accounts, but this is how you formulated the query in the question.

Insert multiple rows, most of them constant

I have to insert big amount of records in a table. It is not quite normalized, so most of the fields are repeated.
I know the proper command is:
INSERT INTO table_name (field1, field2, ..., field_n)
VALUES (value1, value2, ..., value_n),
...
(value1, value2, ..., value_n)
But I wonder whether it is possible to keep some of the values fixed and just indicate the different ones.
Let's say instead of
INSERT INTO table_name (shop, month, sale)
VALUES (1, 2, 23),
(1, 2, 28),
(1, 2, 29),
(1, 2, 30)
Having something like
INSERT INTO table_name (shop, month, sale)
VALUES (1, 2, 23), ... 28 / 29 / 30
If it is not possible I would create a procedure with a loop, feeding a string, etc. It would not be a big issue, but my point is to know if INSERT INTO has any particularity that allows doing this without procedures.
You can try something like the following:
INSERT INTO table_name (shop, month, sale)
SELECT * FROM
(SELECT 1 as shop, 2 as month) as sm,
(SELECT 23 as sale UNION ALL SELECT 28 UNION ALL SELECT 30) as sales;
You can use the default constraint which will add the default value when you do not specify that in the insert into statement. If you specify a value that value will be added.
Just set the default value for your column in your table
ALTER TABLE tblname ALTER columnName SET DEFAULT 'value'
Refer http://www.w3schools.com/sql/sql_default.asp
You can use a temporary table to insert the different values and then use insert ... select. I don't know if it will be a big saving for you:
CREATE TEMPORARY TABLE sale_temp (sale int);
INSERT sale_temp (sale) VALUES (23), (28), (29), (30);
INSERT INTO table_name (shop, month, sale)
SELECT 1, 2, sale
FROM sale_temp;
DROP TABLE sale_temp;

table with records that can be arranged

How can I make a table that has rows with order to one another and can be rearranged:
Example:
Rows:idappearance name
Records
(1,"john"),(2,"mike")
Now, I want to insert "Avi" between them:
not having to worry about rearranging them
(1,"john"),(2,"Avi"),(3,"mike")
This table can have a foreign key in
another table (like departments..).
idappearance is the order of appearance I want
to set, doesn't need to be PK.
It needs to handle about 50K of names so O(n) isn't best solution.
Simple solution would be having reasonable numerical gaps between records. In other words;
(10000,"John"),(20000,"mike")
(10000,"John"),(15000,"Avi"),(20000,"mike")
(10000,"John"),(12500,"tom"),(15000,"avi"),(20000,"mike")
etc..
Gap between records should be determined based on your data domain
You could have a trigger on inserts. I don't use MySQL, but here's the code for sql-server...
Basically, on an insert, the trigger increments the appearanceId of all rows with appearanceId which are equal to or greater than the new appearance id.
CREATE Table OrderedTable
(
id int IDENTITY,
name varchar(50),
appearanceOrder int
)
GO
CREATE TRIGGER dbo.MyTrigger
ON dbo.OrderedTable
AFTER INSERT
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
UPDATE OrderedTable SET
AppearanceOrder = AppearanceOrder + 1
WHERE AppearanceOrder >= (
SELECT TOP 1 AppearanceOrder
FROM inserted )
AND id NOT IN (
SELECT id
FROM inserted )
END
GO
INSERT INTO OrderedTable VALUES ('Alice', 1)
INSERT INTO OrderedTable VALUES ('Bob', 1)
INSERT INTO OrderedTable VALUES ('Charlie', 1)
INSERT INTO OrderedTable VALUES ('David', 1)
This returns David, Charlie, Bob, Alice as expected.
SELECT *
FROM OrderedTable
ORDER BY AppearanceOrder
Note that I haven't fully tested this implementation. One issue is that it will leave holes in the AppearanceOrder if items are deleted, or the inserts deliberately insert outside the current range. If these matter, they are left as an exercise to the reader ;-)
If appearance order were a double-precision floating point number, you could insert any name between any two adjacent names with a single insert. If you start with a table like this:
create table test (
person_id integer primary key,
person_name varchar(10) not null,
appearance_order double precision not null unique
);
insert into test values (100, 'John', 11);
insert into test values (23, 'Mike', 12);
Insert Avi between them by simply
insert into test values (3, 'Avi', 11.5);
Sort by the column 'appearance_order'.
select * from test order by appearance_order
100 John 11
3 Avi 11.5
23 Mike 12
Insert Eva between John and Avi by
insert into test values (31, 'Eva', 11.25);
select * from test order by appearance_order
100 John 11
31 Eva 11.25
3 Avi 11.5
23 Mike 12
You do need to separate identification from sort order. That means using one column for the id number (and as the target for foreign key references) and another for the appearance order. But, depending on your application, you might not need a unique constraint on appearance_order.