MYSQL SELECT statement failing within an UPDATE, within a LOOP - mysql

I am writing a stored procedure to update a company calendar. The calendar data is stored within a MYSQL table. The stored procedure is an insert function which should force all calendar events between two dates to be shifted to the next available business day. Type 1 denotes a day on the calendar. Type 2 denotes an event on the calendar.
The calendar recognizes a business day through the Moves column. A day with Moves(more than 0) is a business day on the calendar.
When I run this query by itself, it returns the expected information:
SELECT `Date`, Moves FROM tblschedules WHERE Type = 1;
It gives me the number of Moves for each day. However, when I try and update the table, the update does nothing, because the Moves column returns as NULL. Here is the query for the update:
SET SQL_SAFE_UPDATES = 0;
UPDATE tblschedules tb1
INNER JOIN (SELECT `Date`, Moves FROM tblschedules WHERE Type = 1) tb2
ON tb2.Date = tb1.Date
SET tb1.Date = tb1.Date + Interval 1 Day
WHERE tb1.Type = 2 AND tb1.Date >= ddate AND tb1.Date < opendate AND tb2.Moves = 0;
So, to debug, I began putting select statements within my stored procedure, and found that the initial query is not returning the Moves column, but instead returns null values as Moves. So that tb2 is returning every Date properly, but is not retrieving the Moves values properly.
Here is the larger block of code. The intloop, ddate and opendate are all functioning properly.
looplabel: Loop
/* Select Statements for Debug Purposes*/
SELECT opendate;
SELECT ddate;
SELECT intloop;
SELECT `Date`, Moves FROM tblschedules WHERE Type = 1;
SELECT * FROM tblschedules tb1
LEFT JOIN (SELECT `Date`, Moves FROM tblschedules WHERE Type = 1) tb2
ON tb2.Date = tb1.Date
WHERE tb1.Type = 2 AND tb1.Date >= ddate AND tb1.Date < opendate;
/* End of Select Statements for Debug Purposes*/
IF IFNULL(intloop,0) = 0 THEN
LEAVE looplabel;
END IF;
SET SQL_SAFE_UPDATES = 0;
UPDATE tblschedules tb1
INNER JOIN (SELECT `Date`, Moves FROM tblschedules WHERE Type = 1) tb2
ON tb2.Date = tb1.Date
SET tb1.Date = tb1.Date + Interval 1 Day
WHERE tb1.Type = 2 AND tb1.Date >= ddate AND tb1.Date < opendate AND tb2.Moves = 0;
SET intloop = intloop - 1;
END LOOP;
I am not sure why the table returns the Moves properly outside of the stored procedure, but not inside of the stored procedure. I am wondering if it might have something to do with uncommitted changes/table locking. Its probably something basic and stupid I am missing. Thank you for your time.

Related

Generate random numbers in MySQL without repeat

I have a stored procedure that insert data related to a scooter rent, one of the fields is a "rent code" when i insert the information "rent code" should be a unique random number that don exist in the table "rents"
here is that i tried
SELECT FLOOR(RAND() *9999)+1 AS random_num
FROM reservaciones
WHERE "random_num" NOT IN (SELECT reservaciones.Codigo_Reservacion FROM reservaciones)
LIMIT 1
in the table i got
1
2
3
when i run the code if i reduce the limit to 4 (example) the query still generates the numbers that i already got in my table
Create and use user-defined function similar to:
CREATE FUNCTION generate_random_number (upper_limit INT)
RETURNS INT
BEGIN
IF upper_limit = (SELECT COUNT(*) FROM main_table WHERE id <= upper_limit) THEN
RETURN NULL;
END IF;
IF upper_limit IS NULL THEN
SELECT REPEAT('9', LENGTH(MAX(id) + 1)) FROM main_table INTO upper_limit;
END IF;
RETURN (SELECT t1.id + 1
FROM (SELECT id FROM main_table
UNION ALL
SELECT 0) t1
LEFT JOIN main_table t2 ON t1.id = t2.id - 1
WHERE t2.id IS NULL
AND t1.id < upper_limit
ORDER BY RAND() LIMIT 1);
END
upper_limit parameter specifies the range from which the number should be generated. Can be less than max. existing value. Cannot be greater than 2147483647. Cannot be set to NULL if max. existing value is 999999999 or greater (you may expand this limit by set the parameter and output datatypes to BIGINT).
Function returns NULL if there is no free number in specified range.
fiddle with some comments.

Mysql select counters grouped by time with gaps filled [duplicate]

How i can fill date gaps in MySQL? Here is my query:
SELECT DATE(posted_at) AS date,
COUNT(*) AS total,
SUM(attitude = 'positive') AS positive,
SUM(attitude = 'neutral') AS neutral,
SUM(attitude = 'negative') AS negative
FROM `messages`
WHERE (`messages`.brand_id = 1)
AND (`messages`.`spam` = 0
AND `messages`.`duplicate` = 0
AND `messages`.`ignore` = 0)
GROUP BY date ORDER BY date
It returns proper result set - but i want to fill gaps between dates start and end by zeros. How i can do this?
You'll need to create a helper table and fill it with all dates from start to end, then just LEFT JOIN with that table:
SELECT d.dt AS date,
COUNT(*) AS total,
SUM(attitude = 'positive') AS positive,
SUM(attitude = 'neutral') AS neutral,
SUM(attitude = 'negative') AS negative
FROM dates d
LEFT JOIN
messages m
ON m.posted_at >= d.dt
AND m.posted_at < d.dt + INTERVAL 1 DAYS
AND spam = 0
AND duplicate = 0
AND ignore = 0
GROUP BY
d.dt
ORDER BY
d.dt
Basically, what you need here is a dummy rowsource.
MySQL is the only major system which lacks a way to generate it.
PostgreSQL implements a special function generate_series to do that, while Oracle and SQL Server can use recursion (CONNECT BY and recursive CTEs, accordingly).
I don't know whether MySQL will support the following/similar syntax; but if not, then you could just create and drop a temporary table.
--Inputs
declare #FromDate datetime, /*Inclusive*/
#ToDate datetime /*Inclusive*/
set #FromDate = '20091101'
set #ToDate = '20091130'
--Query
declare #Dates table (
DateValue datetime NOT NULL
)
set NOCOUNT ON
while #FromDate <= #ToDate /*Inclusive*/
begin
insert into #Dates(DateValue) values(#FromDate)
set #FromDate = #FromDate + 1
end
set NOCOUNT OFF
select dates.DateValue,
Col1...
from #Dates dates
left outer join SourceTableOrView data on
data.DateValue >= dates.DateValue
and data.DateValue < dates.DateValue + 1 /*NB: Exclusive*/
where ...?

Unrolling periodical events in MySQL

I've got a database with two tables, that I want to combine. One of the tables contains "incidental events", which just occur once. Next to this, I also have "periodical events". Now I want to combine these two in a view.
The incidental one simply has two columns, one called changes, the other one called date. The periodical one has three columns, changes, startDate and endDate. The difference between these two can be a maximum of 50 years, so manually typing out one case for every day is not going to work. Both views also have an AI ID. In this view I want to have a column date and a column changes.
To achieve this I want to unroll the periodical changes table, so that it shows one entry for every day in between the startDate and endDate. For instance:
incidental changes:
date | change
09/08/2015 | 5
11/08/2015 | 10
periodical changes:
startDate | endDate | change
09/08/2015 | 12/08/2015 | 7
These two I want combined into:
changes view:
date | change
09/08/2015 | 5
09/08/2015 | 7
10/08/2015 | 7
11/08/2015 | 10
11/08/2015 | 7
12/08/2015 | 7
My idea is to use something like this:
SELECT * FROM incidental_changes,(
SET #id = (SELECT min(ID) AS min FROM periodical_changes WHERE 1)
SET #maxID = (SELECT max(ID) AS max FROM periodical_changes WHERE 1)
WHILE (#id <= #maxID) DO
SET #firstDate = (SELECT startDate FROM periodical_changes WHERE id = #id)
SET #lastDate = (SELECT endDate FROM periodical_changes WHERE id = #id)
WHILE (#firstDate <= #lastDate) DO
SELECT #firstDate AS date, change FROM periodical_changes WHERE id = #id
#firstDate = #firstDate + INTERVAL 1 DAY
END
#id = #id + 1
END
) WHERE 1
This gives me an error,
CREATE ALGORITHM = UNDEFINED VIEW all_periodicals AS SELECT * FROM
incidental_changes,( SET #id = (SELECT min(ID) AS min FROM
periodical_changes WHERE 1) SET #maxID = (SELECT max(ID) AS max FROM
periodical_changes WHERE 1) WHILE (#id <= #maxID) DO SET #firstDate =
(SELECT startDate FROM periodical_changes WHERE id = #id) SET
#lastDate = (SELECT endDate FROM periodical_changes WHERE id = #id)
WHILE (#firstDate <= #lastDate) DO SELECT #firstDate AS date, change
FROM periodical_changes WHERE id = #id #firstDate = #firstDate +
INTERVAL 1 DAY END #id = #id + 1 END ) WHERE 1
#1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use
near 'SET #id = (SELECT min(ID) AS min FROM periodical_changes WHERE
1) SET #' at line 5
and I'm guessing that if I'd manage to fix this error there'd be more. So, is there any way to do this the way I want, or do I have to look for a different approach?
EDIT:
Okay, so far I have not found a way to do this in a view or so. So instead I am now using a routine. This routine has one parameter, account INT. The definition I am using so far is as followed:
BEGIN
DECLARE periodicalID int;
DECLARE v_finished INTEGER DEFAULT 0;
DECLARE periodicalCursor CURSOR
FOR SELECT periodicals.periodicalID FROM periodicals WHERE periodicals.accountID = account;
DECLARE CONTINUE HANDLER
FOR NOT FOUND SET v_finished = 1;
CREATE TEMPORARY TABLE results LIKE incidentials;
ALTER TABLE results DROP INDEX date;
SET #periodicalID = -1;
OPEN periodicalCursor;
allPeriodicals: LOOP
FETCH periodicalCursor INTO periodicalID;
IF (v_finished) THEN
LEAVE allPeriodicals;
END IF;
SELECT periodicals.startDate,periodicals.numberOfPeriods,periodicals.period,periodicals.endDate,periodicals.money FROM periodicals WHERE periodicals.periodicalID = periodicalID AND periodicals.accountID = account INTO #startDate, #numberOfPeriods, #period,#endDate,#money;
SET #intervalStatement = "SELECT ? + INTERVAL ? ";
SET #intervalStatement = CONCAT(#intervalStatement,#period," INTO #res");
PREPARE intervalStatement FROM #intervalStatement;
WHILE #startDate <= #endDate DO
EXECUTE intervalStatement USING #startDate,#numberOfPeriods;
SET #startDate = #res;
INSERT INTO results(accountID,date,money) VALUES (account,#startDate,#money);
END WHILE;
END LOOP allPeriodicals;
INSERT INTO results(accountID,date,money) SELECT accountID,date, money FROM incidentials WHERE incidentials.accountID = account;
SELECT * FROM results ORDER BY date;
END
This poses the problem of performance though. With only one periodical entry spread over a year this query already takes about 16 seconds. So even though this approach works, I either did something wrong causing it to take this long or this is not the right way to go.
Let me presume you have a numbers table. Then you can do:
select i.date, i.change
from incidental
union all
select date_add(p.startDate, interval n.n - 1 day), p.change
from periodic p join
numbers n
on date_add(p.startDate, interval n.n - 1 day) <= p.endDate;
For a select query, you can generate the numbers using a subquery, if you know the maximum length. Something like:
select i.date, i.change
from incidental
union all
select date_add(p.startDate, interval n.n - 1 day), p.change
from periodic p join
(select 1 as n union all select 2 union all select 3 union all select 4 union all
select 5 union all select 6 union all select 7
) n
on date_add(p.startDate, interval n.n - 1 day) <= p.endDate;
This doesn't work in a view, however. For that, you really do need a numbers table.

Select value in mysql but check another database at clause WHERE

I'm trying to select values from a database, but I need to check another value in another database .
I created this code, but only get 1 result and I don't know why:
SELECT `id` FROM `mc_region`
WHERE `is_subregion` = 'false'
AND lastseen < CURDATE() - INTERVAL 20 DAY
AND (SELECT id_region FROM mc_region_flags
WHERE flag <> 'expire'
AND id_region = mc_region.id
)
LIMIT 0, 30
What I've made wrong?
#Edit
I think I know why this code is not working. At database mc_region_flags not all records from the primary database has flag.
I would like to do the following:
1º Select all records on the first database, where is not subregion and lastseen is more than 20 day
2º Check if any result on the 1st database has flag 'expire', if yes, they are not included in the result.
I cant do this in 1 only SQL Code?
#Edit2
I created this code that simulate FULL JOIN but seems that WHERE is not work
SELECT *
FROM mc_region AS r RIGHT OUTER JOIN
mc_region_flags AS f ON r.id = f.id_region
UNION ALL
SELECT * from
mc_region AS r LEFT OUTER JOIN
mc_region_flags AS f
ON r.id = f.id_region
WHERE r.is_subregion = 'false'
AND f.flag = 'exipre'
AND r.lastseen < CURDATE() - INTERVAL 20 DAY
Problems WHERE not work
f.flag is not 'expire'
f.lastseen is not > 20 days
UPDATED
SELECT *
FROM `mc_region` AS r LEFT JOIN
`mc_region_flags` AS f ON r.`id` = f.`id_region`
WHERE r.`is_subregion` = 'false' AND
r.`lastseen` < CURDATE() - INTERVAL 20 DAY AND
COALESCE(f.`flag`, '-') <> 'expire'
LIMIT 0, 30;
Before the inner nested select add :
id in (select...)

Catching latest column value change in SQL

How can I get the date for the latest value change in one column with one SQL query?
Possible database situation:
Date State
2012-11-25 state one
2012-11-26 state one
2012-11-27 state two
2012-11-28 state two
2012-11-29 state one
2012-11-30 state one
So result should return 2012-11-29 as latest change state. If I group by State value, I will get the date for first time I have that state in database.
The query will group the table on state and show the state and in the date field the latest date created of that state.
From the given input the output would be
Date State
2012-11-30 state one
2012-11-28 state two
This will get you the last state:
-- Query 1
SELECT state
FROM tableX
ORDER BY date DESC
LIMIT 1 ;
Encapsulating the above, we can use it to get the date just before the last change:
-- Query 2
SELECT t.date
FROM tableX AS t
JOIN
( SELECT state
FROM tableX
ORDER BY date DESC
LIMIT 1
) AS last
ON last.state <> t.state
ORDER BY t.date DESC
LIMIT 1 ;
And then use that to find the date (or the whole row) where the last change occurred:
-- Query 3
SELECT a.date -- can also be used: a.*
FROM tableX AS a
JOIN
( SELECT t.date
FROM tableX AS t
JOIN
( SELECT state
FROM tableX
ORDER BY date DESC
LIMIT 1
) AS last
ON last.state <> t.state
ORDER BY t.date DESC
LIMIT 1
) AS b
ON a.date > b.date
ORDER BY a.date
LIMIT 1 ;
Tested in SQL-Fiddle
And a solution that uses MySQL variables:
-- Query 4
SELECT date
FROM
( SELECT t.date
, #r := (#s <> state) AS result
, #s := state AS prev_state
FROM tableX AS t
CROSS JOIN
( SELECT #r := 0, #s := ''
) AS dummy
ORDER BY t.date ASC
) AS tmp
WHERE result = 1
ORDER BY date DESC
LIMIT 1 ;
I believe this is the answer:
SELECT
DISTINCT State AS State, `Date`
FROM
Table_1 t1
WHERE t1.`Date`=(SELECT MAX(`Date`) FROM Table_1 WHERE State=t1.State)
...and the test:
http://sqlfiddle.com/#!2/8b0d8/5
If you add another column 'changed datetime' you can fill this using an update trigger that inserts NOW(). If you query your table ordering on the changed column, it will endup first.
CREATE TRIGGER `trigger` BEFORE UPDATE ON `table`
FOR EACH ROW
BEGIN
SET ROW.changed = NOW();
END$$
Try this ::
Select
MAX(`Date`), state from mytable
group by state
If you had been using postgres, you could compare different rows in the same table using "LEAD .. OVER" I have not managed to find the same functionallity in mysql.
A bit hairy, but I think this will do:
select min(t1.date) from table_1 t1 where
(select count(distinct state) from table_1 where table_1.date>=t1.date)=1
Basically, this asks for the first time no changes in state is found for any later values. Be warned, it may be this query scales terribly for large data sets....
I think your best choice here are analytical functions. Try this - it should be OK performance-wise:
SELECT *
FROM test
WHERE my_date = (SELECT MAX (my_date)
FROM (SELECT MY_DATE
FROM ( SELECT MY_DATE,
STATE,
LAG (state) OVER (ORDER BY MY_DATE)
lag_val
FROM test
ORDER BY MY_DATE) a
WHERE state != lag_val))
In the inner select, the LAG function gets the previous value in the STATE column and in the outer select I mark the date of a change - those with lag value different than the current state value. And outside, I'm getting the latest date from those dates of a change... I hope that this is what you needed.
SELECT MAX(DATE) FROM YOUR_TABLE
Above answer doesn't seem to satisfy what OP needs.
UPDATED ANSWER WITH AFTER INSERT/UPDATE TRIGGER
DELCARE #latestState varchar;
DELCARE #latestDate date;
CREATE TRIGGER latestInsertTrigger AFTER INSERT ON myTable
FOR EACH ROW
BEGIN
IF OLD.DATE <> NEW.date THEN
SET #latestState = NEW.state
SET #latestDate = NEW.date
END IF
END
;
CREATE TRIGGER latestUpdateTrigger AFTER UPDATE ON myTable
FOR EACH ROW
BEGIN
IF OLD.DATE = NEW.date AND OLD.STATE <> NEW.STATE THEN
SET #latestState = NEW.state
SET #latestDate = NEW.date
END IF
END
;
You may use the following query to get the latest record added/updated:
SELECT DATE, STATE FROM myTable
WHERE STATE = #latestState
OR DATE = #latestDate
ORDER BY DATE DESC
;
Results:
DATE STATE
November, 30 2012 00:00:00+0000 state one
November, 28 2012 00:00:00+0000 state two
November, 27 2012 00:00:00+0000 state two
The above query results needs to be limitted to 2, 3 or n based on what you need.
Frankly it seems like you want to get max from both columns based on the data sample you have given. Assuming that your state only increases with the date. Only I wish if the state was an integer :D
Then union of two max sub queries on both columns would have solved it easily. Still a string manipulation regex can find what's the max in state column. Finally this approach needs limit x. However it still has lope hole. Anyway it took me sometime to figure your need out :$