optimization sql query update depend of another table - mysql

I did a sql query for specific need, but I am not a professional of SQL, so i need help to "improve" the query.
Firstly, I have 3 tables :
user (id, username, ...)
action (id, point, limitPerDay)
history (id, user_id, action_id, created_at, is_used_for_score)
Each time user do a specific action, it is log in history table (is_used_for_score will be true or false if limit has been reached or not)
But I need query for update all is_used_for_score because under certain conditions is_used_for_score can be pass from false to true
My query actually is
SET #num := 0, #user_id := 0, #type := '', #date := '';
UPDATE history SET is_used_for_score = 1 WHERE history.id IN (
SELECT history_id FROM (
SELECT
history.id AS history_id,
action.limitPerDay AS limit_per_day,
action.point,
#num := IF(
#type = action_id,
IF(
#date = CONCAT(YEAR(history.created_at), '-', MONTH(history.created_at), '-', DAY(history.created_at)),
IF (
#user_id = user_id,
#num + 1,
1
),
1
),
1
) AS row_number,
#user_id := user_id AS user_id,
#type := action_id AS action_id,
#date := CONCAT(YEAR(history.created_at), '-', MONTH(history.created_at), '-', DAY(history.created_at)) AS `date`
FROM history
LEFT JOIN `action` ON action.id = action_id
HAVING (row_number <= limit_per_day) OR (limit_per_day IS NULL)
ORDER BY history.created_at ASC, user_id, action_id
) AS history_id_count_for_vote
);
But, I am pretty sure is probably not the best way to do that. Did you have some suggestion which can improve the query ?
Thank you

On the issues like this, I'm repeating relentlessly, again and again: SQL is a DECLARATIVE language, do NOT forget this (slipping into imperativeness, just like you do)!
That means, that you're going to define, DECLARE the set(s) you going to work with - in other words, tell the Engine WHAT you want, leaving HOWs at its discretion.
That's the cornerstone.
So try to think through it like this: "I need to update SOME set of records [ones fell under '... certain conditions is_used_for_score can be pass from false to true'], so let's define that set first"
And that is what you lacking here - so please go ahead, define these conditions and update your question with it, cause it's quite a time-eater trying to extract it from that mess you've posted.

Related

Insert sequential numbers based on another field - MySQL

There is a similar question
Insert sequential number in MySQL
I want to insert sequential numbers to the table, but based on another field. I have two columns page_numner and parent, so the rows with same parent should have page_number as consequtive numbers. If parent changes, the page should start from 1 again and increase by one.
I was thinking to use smth like this
SELECT #i:=0;
SELECT #p:=0;
UPDATE my_table AS t SET page_number = CASE
WHEN #p = t.`parent` THEN #i:=#i+1
ELSE 1 -- assign current parent to #p ??
END
but, it cant figure out how to assign the new parent into #p for the else case.
Please note, that I am trying to achieve this with pure mysql (if possible of course)
Thanks
You can do what you want with this code:
set #p := -1;
set #i := 0;
UPDATE my_table t
SET page_number = (CASE WHEN #p = t.`parent` THEN #i := #i+ 1
WHEN (#p := t.parent) = NULL THEN NULL -- never happens
ELSE #i := 1
END)
ORDER BY t.parent;
Unfortunately, MySQL doesn't allow both ORDER BY and JOIN in the same UPDATE query. If it did, you could initialize the variables in the query.
Note the second condition just does the assignment. = NULL never returns TRUE.

Modify column values to that they are in order

I got a very special problem that I'd like to solve in SQL. I need to make sure that optionOrder for the same questionID goes from 0-[any number].
So for example the rows with questionID = 18386, their optionOrder are right now 1,2,3,4. They need to be 0,1,2,3.
Also if the rows are like this 1,2,4, it needs to be 0,1,2
I'm sorry for the incorrect grammars.
In MySQL, you can do this with variables:
set #rn := 0;
set #q := -1;
update table t
set optionorder = (case when #q = questionid then (#rn := #rn + 1)
when (#q := questionid) is not null then (#rn := 0)
else -1 -- should never happen
end)
order by questionid, optionorder;
Because of the order by, you need to set the variables outside the update.

INSERT IF condition met

IF (( SELECT param FROM changes WHERE type = 'uptime' AND website_id = 1 ORDER BY timestamp DESC LIMIT 1 ) != 'up' )
INSERT INTO changes ( website_id, timestamp, type, param ) VALUES ( 1, NOW(), 'uptime', 'up' )
END IF;
This appears to be an incorrect syntax. How else can I make this work in a single query?
Why are you reading param record outside the query using IF instead of just adding it in the WHERE Condition? If you do it like this then you can just Insert the complete query, if the query returns something your data will be inserted, if your query returns nothing then nothing would be inserted:
INSERT INTO changes ( website_id, timestamp, type, param )
SELECT 1, NOW(), 'uptime', 'up'
FROM changes
WHERE type = 'uptime' AND website_id = 1 AND param != 'up' LIMIT 1
changes seems to be a table for logging events. Given all the variations of INSERTit is not possible to do it. You can try using STORED PROCEDURE to achieve it. You can do something like following:
select param into #last_param from changes where website_id = 1 and type = 'uptime' limit 1;
Now #last_param will contain up or down. In the next statement can be conditional on if last_param is up or down.

MySQL incrementing value

Is there a way to make a value increment with every insert if having multiple inserts? (I dont speak of the primary key that autoincrements)
Lets say I have a structure like this:
|ID_PRODUCT|ID_CATEGORY|NAME|POSITION|
So I have individual product ids, each produt belongs to a category and has a different position in this category. I want to do something like this:
INSERT INTO products
( SELECT id_product, id_category, name, MY_POSITION++
FROM db2.products WHERE id_category = xxx )
So there should be a variable MY_POSITION that starts with 1 and increments every insert.
It would be really easy to do this all just with a scripting-language like php or python, but I want to get better with SQL :)
Yes: Use a user defined variable:
SET #position := 0; -- Define a variable
INSERT INTO products
SELECT id_product, id_category, name, (#position := #position + 1)
FROM db2.products
WHERE id_category = xxx;
The result of increment to #position is the value used for the insert.
Edit:
You can skip the declaration of the variable by handling the initial value in-line:
...
SELECT ..., (#position := ifnull(#position, 0) + 1)
...
This can be particularly handy when executing the query using a driver that does not allow multiple commands (separated by semicolons).
You will need to ORDER BY id_category and use two user variables so you can track the previous category id -
SET #position := 0;
SET #prev_cat := 0;
INSERT INTO products
SELECT id_product, id_category, name, position
FROM (
SELECT
id_product,
id_category,
name,
IF(#prev_cat = id_category, #position := #position + 1, #position := 1) AS position,
#prev_cat := id_category
FROM db2.products
ORDER BY id_category ASC, id_product ASC
) AS tmp;
This will allow you to do all categories in one query instead of category by category.
Purely to add to #Bohemians answer - I wanted the counter to reset every so often and this can be done like such:
SELECT *,(#position := IF (#position >= 15,1,#position + 1))
Where 15 is obviously the maximum number before reset.
Try setting a value using a subquery like this
(SELECT MAX(position) FROM products AS T2)+1
Or
(SELECT MAX(position) FROM products AS T2 WHERE id_category = 'product category')+1
Hope this will work.
update <tbl_name> set <column_name>=<column_name>+1 where <unique_column/s>='1'";
For those who are looking for example of update query, here it is:
SET #i := 0;
UPDATE products SET id = (#i := #i + 1);

Mysql, tree, Hierarchical query, performance

My question is based on the following article (the table and the function hierarchy_connect_by_parent_eq_prior_id) http://explainextended.com/2009/03/17/hierarchical-queries-in-mysql/
Lets assume that the table t_hierarchy has two additional fields (beside id and parent) typ1(char) and time(int). the field typ1 can have two values A and B.
My goal is to display the whole tree as described in the article but I need an extra field in the result that displays the time of the current node (if typ1 = B) and of all of its descendants (if typ1 = B). So I need the sum of all descendants' times for a certain node (including itself) when typ1=B.
I have the following solution but it is way too slow:
main query:
SELECT CONCAT(REPEAT(' ', level - 1), hi.id) AS treeitem, get_usertime_of_current_node_and_descendants(hi.id) as B_time,
hierarchy_sys_connect_by_path('/', hi.id) AS path,
parent, level
FROM (
SELECT hierarchy_connect_by_parent_eq_prior_id(id) AS id,
CAST(#level AS SIGNED) AS level
FROM (
SELECT #start_with := 0,
#id := #start_with,
#level := 0
) vars, t_hierarchy
WHERE #id IS NOT NULL
) ho
JOIN t_hierarchy hi
ON hi.id = ho.id
The function get_usertime_of_current_node_and_descendants(input int):
BEGIN
DECLARE _id INT;
DECLARE _desctime INT;
DECLARE _nodetime INT;
SET _id = input;
select COALESCE((select sum(time) from (
SELECT hi.id, time,typ1
FROM (
SELECT hierarchy_connect_by_parent_eq_prior_id_2(id) AS id, #levela AS level
FROM (
SELECT #start_witha := _id,
#ida := #start_witha,
#levela := 0,
) vars, t_hierarchy a
WHERE #ida IS NOT NULL
) ho
JOIN t_hierarchy hi
ON hi.id = ho.id
) q where typ1 = 'B'), 0) into _desctime;
select COALESCE((select time from t_hierarchy where id = _id and typ1='B'), 0) into _nodetime;
return _desctime + _nodetime;
END $$
The function hierarchy_connect_by_parent_eq_prior_id_2 is the same as in the article and as the one above hierarchy_connect_by_parent_eq_prior_id but it has differently named global variables so it won't interfere with the the ones used in the main query.
The above solution works as desired but it is way too slow (especially when working with large datasets). Can you offer a better solution or can you suggest how to improve the query? Thank you in advance for your time and help!
I solved the problem retrieving the time of the descendants outside of mysql (before inserting the entries into the table).