I'm creating a table to store rules for another selects and it has the following design:
id
column
operator
value
next_rule_id
next_operator
1
mail
contains
#gmail.com
2
OR
2
mail
contains
#hotmail.com
5
AND
3
city
equals
NY
5
AND
4
mail
contains
jolie#
3
AND
5
city
equals
NY
4
null
6
user_id
greater
10
null
null
I need to select 1 rule id and return all rows based on next_rule_id
Here are some examples:
selecting rule id = 1 would return
1 mail contains #gmail.com 2 OR
2 mail contains #hotmail.com 5 AND
5 city equals NY 4 null
selecting rule id = 4 would return
4 mail contains jolie# 3 AND
3 city equals NY 5 AND
5 city equals NY 4 null
selecting rule id = 6 would return
6 user_id greater 10 null null
SOLUTION 1
I've managed to do that with a temporary table and one loop. But I think there is better approaches to do that.
If anyone knows how to do that in a better way I would appreciate your help.
SOLUTION 2
I created this procedure that uses a loop too.
BEGIN
SET #id = 1; -- hardcoded first id
SET #rules = '';
SET #next_id = NULL;
loop1: LOOP
SET #count = #count + 1;
SELECT next_rule_id INTO #next_id FROM filter_rules WHERE id = #id;
SET #rules = CONCAT(#rules, ',', #id);
SET #id = #next_id;
IF #id IS NULL THEN
LEAVE loop1;
END IF;
END LOOP loop1;
SELECT * FROM filter_rules WHERE FIND_IN_SET(filter_rules.id, #rules);
END
Related
I'm working on some legacy code/database, and need to add a field to the database which will record a sequence number related to that (foreign) id.
Example table data (current):
ID ACCOUNT some_other_stuff
1 1 ...
2 1 ...
3 1 ...
4 2 ...
5 2 ...
6 1 ...
I need to add a sequenceid column which increments separately for each account, achieving:
ID ACCOUNT SEQ some_other_stuff
1 1 1 ...
2 1 2 ...
3 1 3 ...
4 2 1 ...
5 2 2 ...
6 1 4 ...
Note that the sequence is related to account.
Is there a way I can achieve this in SQL, or do I resort to a PHP script to do the job for me?
TIA,
Kev
Create a trigger:
CREATE TRIGGER trg_mytable_bi
BEFORE INSERT ON mytable
FOR EACH ROW
BEGIN
DECLARE nseq INT;
SELECT COALESCE(MAX(seq), 0) + 1
INTO nseq
FROM mytable
WHERE account = NEW.account;
SET NEW.seq = nseq;
END;
The question is tagged as "mysql", so yes, MySQL's auto_increment can create groupwise sequential ids.
see http://dev.mysql.com/doc/refman/5.0/en/example-auto-increment.html:
For MyISAM and BDB tables you can specify AUTO_INCREMENT on a secondary column in a multiple-column index. In this case, the generated value for the AUTO_INCREMENT column is calculated as MAX(auto_increment_column) + 1 WHERE prefix=given-prefix. This is useful when you want to put data into ordered groups.
edit: example php script (using PDO, but it's the same game with the php-mysql module)
$pdo = new PDO('mysql:host=...;dbname=...', '...', '...');
$pdo->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
// example table
$pdo->exec(
'CREATE TEMPORARY TABLE Foo (
id int auto_increment,
account int,
someotherstuff varchar(32),
primary key(account,id)
) engine=MyIsam'
);
// insert example data
$stmt = $pdo->prepare('INSERT INTO Foo (account,someotherstuff) VALUES (?,?)');
$stmt->execute(array(1, '1a'));
$stmt->execute(array(1, '1b'));
$stmt->execute(array(1, '1c'));
$stmt->execute(array(2, '2a'));
$stmt->execute(array(2, '2b'));
$stmt->execute(array(1, '1d'));
unset($stmt);
// query data
foreach( $pdo->query('SELECT account,id,someotherstuff FROM Foo') as $row ) {
echo $row['account'], ' ', $row['id'], ' ', $row['someotherstuff'], "\n";
}
prints
1 1 1a
1 2 1b
1 3 1c
2 1 2a
2 2 2b
1 4 1d
This should work but is probably slow:
CREATE temporary table seq ( id int, seq int);
INSERT INTO seq ( id, seq )
SELECT id,
(SELECT count(*) + 1 FROM test c
WHERE c.id < test.id AND c.account = test.account) as seq
FROM test;
UPDATE test INNER join seq ON test.id = seq.id SET test.seq = seq.seq;
I have called the table 'test'; obviously that needs to be set correctly. You have to use a temporary table because MySQL will not let you use a subselect from the same table you are updating.
How can the following be performed in a single query?
UPDATE clients SET online=0 WHERE id NOT IN(4,5,8,10,12) AND id>=2 AND id<=15 AND parentId=123;
UPDATE clients SET online=1 WHERE id IN(4,5,8,10,12) AND id>=2 AND id<=15 AND parentId=123;
You can use CASE .. WHEN statement:
UPDATE clients
SET online = CASE WHEN id IN(4,5,8,10,12)
THEN 1
ELSE 0
END
WHERE
id BETWEEN 2 AND 15 AND parentId = 123;
IN(..) is a Logical/Comparison function. So you can do the following (in MySQL only) as well:
UPDATE clients
SET online = (id IN(4,5,8,10,12))
WHERE
id BETWEEN 2 AND 15 AND parentId = 123;
I have
Array ( [406] => 1 [407] => 3 [408] => 2 [409] => 7 [410] => 1 )
run as mysql query
UPDATE counter SET total = 1 WHERE id = 406;
UPDATE counter SET total = 3 WHERE id = 407;
UPDATE counter SET total = 2 WHERE id = 408;
UPDATE counter SET total = 7 WHERE id = 409;
UPDATE counter SET total = 1 WHERE id = 410;
I can only optimized query above by grouping same total value as below:
UPDATE counter
SET total = 1
WHERE name IN (406, 410);
Is there any way to optimize it better, rather than execute (loop) the update query one by one.
You need this:
UPDATE counter SET total = CASE
WHEN id = 406 THEN 1
WHEN id = 407 THEN 3
WHEN id = 408 THEN 2
WHEN id = 409 THEN 7
WHEN id = 410 THEN 1
END
you can use key value pair to update like below
UPDATE counter SET total = '".$value."' WHERE id = '".$key."';
What I've got so far is a code which updates one row as based on the IF conditions - but it
It is important that it is reliable and atomic (so with transaction).
It should loop/repeat itself based on #q which is quantity - however it can be in a MYSQL function rather than one fixed variable. How can I do that? I've added how the table should look like in the end
SET #q=1000;
SET #p=5.00;
SET #email='test#test.com';
update 1detail
SET quantity =
if((#q := quantity - #q) >= 0, #q, 0)
WHERE price>=#p ORDER BY datetime DESC LIMIT 1;
SET #q = if(#q <0,#q-#q-#q, #q);
UPDATE 1detail
SET quantity =
if(#q > 0, #q,quantity),
email=
if(#q > 0, #email, email)
WHERE price>=#p ORDER BY datetime DESC LIMIT 1;
The table at the beginning
quantity price email datetime
---------------------------------------------------
800 5.00 test1#test.com oldest
50 5.00 test2#test.com 2nd oldest
100 10.00 test3#test.com 3rd oldest (ignore in processing because #p < price)
How it should looks like after looping
quantity price email datetime
-----------------------------------------------------------------
0 5.00 test1#test.com oldest
150* 5.00 test#test.com (changed to #email) 2nd oldest (now newest)
100 10.00 test3#test.com 3rd oldest (ignored)
*this has changed as the oldest only had 800 and the 2nd oldest just had 50
So #q= 1000 (#q) - 800 (oldest) = 200
then #q = 200 (#q) - 50 (2nd oldest)
--> #q = 150
this updates the 2nd oldest row
A small loop can be achieved like this :
declare #counter int
set #counter=0
gohere:
set #counter=#counter+1
if #counter<10
BEGIN
select #counter
GOTO gohere;
END
I would like some guidance on the best way to number subsets of data in my table and return the full dataset.
My dataset is about 50k records. The database holds test results and each row is an answer to a question. A person does 2 tests but these could be 2 of 20 or so tests.
The data looks like this:
PersonID TestID QuestionID
UID-1 T-1 QuA-1
UID-1 T-1 QuA-2
UID-1 T-1 QaA-3
UID-1 T-2 QuB-1
UID-1 T-2 QuB-2
UID-1 T-2 QuB-3
UID-2 T-1 QuA-1
UID-2 T-3 QuC-1
My aim is to return the dataset with a number to denote whether the test is the 1st or 2nd one the person did (the order is not important, I just need to allocate a 1 or 2 to their TestIDs). Likewise I need to number the questions as well.
Say a person does 2 tests, the first with 15 questions and the second with 20 questions, this is the output I would like:
PersonID TestID QuestionID TestNum QuNum
UID-1 T-1 QuA-1 1 1
UID-1 T-1 ... 1 ...
UID-1 T-1 QaA-15 1 15
UID-1 T-2 QuB-1 2 1
UID-1 T-2 ... 2 ...
UID-1 T-2 QuB-20 2 20
For each person the test number would either be 1 or 2 and the number of questions for a test would start at 1 and increase incrementally to the last question.
What approach would you use?
To the best of my knowledge, the only way to do this in SQL is using "CURSORS".
Before declaring the cursor, I make a new table from the original one and add the two new columns (i.e., testNum and questionNum) with default values or 0 into it. This is done using "select into".
Then, reading the records of this table one by one (and ordered) by a cursor, I check if the personId and testId change or not and decide based on their status (update the two counters for current questionn and test).
At the end of the loop, before reading the next record, update the table using the two counters (for current question and current test).
Here's the code (BTW, I haven't test it. So take care of typos or possibly minor errors):
SELECT personId, testId, 0 as testNum, 0 as questionNum into t1
FROM Questions
DECLARE c1 CURSOR FOR
SELECT *
FROM t1
ORDER BY personId, testId, questionId
OPEN c1
declare #lastPersonId varchar(10);
declare #lastTestId varchar(10);
declare #currentTestNum int;
declare #currentQuestionNum int;
set #lastPersonId = "-1";
set #lastTestId = "-1";
set #currentTestNum = 1;
set #currentQuestionNum = 1;
FETCH NEXT FROM c1
INTO #personId, #testId, #questionId, #testNum, #qNum
WHILE ##FETCH_STATUS = 0
BEGIN
if (#personId <> #lastPersonId)
begin
#currentTestNum = 1
#currentQuestionNum = 1
end
else
if (#testId <> #lastTestId)
begin
#currentTestNum = #currentTestNum + 1
#currentQuestionNum = 1
end
else -- in this case it is assumed that the questions are different.
set #currentQuestionNum = #currentQuestionNum + 1
update t1
set testNum = #currentTestNum, questionNum = #currentQuestionNum
where current of c1
set #lastPersonId = #personId
set #lastTestId = #testId
FETCH NEXT FROM c1
INTO #personId, #testId, #testNum, #qNum
END
CLOSE c1;
DEALLOCATE c1;