I have searched a lot, but cannot find a helpful answer:
i want to have a list of totals from a period the user defines by giving me a start and end date. The totals should every time being from the start date to beginning with the start date and add every row 1 day. so the last row gives the totals from start to end date.
example: - given period = start 2013-01-01 , end = 2013-01-31
total day 1 = 100
total day 2 = 0 (not listed in my totalsperday query, but should have a row in my final query)
total day 3 = 140
total day 4 = 20
...
final table should look like:
end day 1: 100
end day 2: 100
end day 3: 240
end day 4: 260
...
so i have a query who calculates all days:
SELECT '2013-01-01' as startdate, w.endDate
FROM
(
SELECT date('2013-01-01' + INTERVAL u.i*100 + v.i*10 + w.i DAY) AS endDate
FROM sysints AS u
JOIN sysints AS v
JOIN sysints AS w
WHERE ( u.i*100 + v.i*10 + w.i ) <=
(
SELECT DATEDIFF( '2013-01-31','2013-01-01') as ddff
)
) w
ORDER BY w.endDate ASC
and i have a query who calculates the totals per day
SELECT p.selldate, SUM(p.price) as totalPerDay
FROM products p
WHERE '2013-01-01' >= p.selldate <= '2013-01-31'
GROUP BY p.selldate
ORDER BY p.selldate ASC
now combining these two to get my final result is hard.
basically what the final query should look like is:
- make the sum of sumperday from day 1 to day 1
- make the sum of sumperday from day 1 to day 2
- make the sum of sumperday from day 1 to day 3
...
any help?
thx.
this is a simplified example of my final query.
Below is the sample. The idea is to obtain a initial data set ordered by date and having aggregate totals, implicit date range records. Then using the cursor you can pass through each row to get the final total column (amountCalc column in the sample) just by summarize the previous records - will work because you already have the columns ordered by date.
The procedure can have other input/ output parameters. Instead of getting info from table you can get data from one view, where the view can be already order by date asc. Is just a sample so can be customized as needed.
Good luck.
-- drop table `Balance`;
CREATE TABLE `Balance` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`date` DATE NOT NULL,
`account` varchar(30) NOT NULL,
`amount` DECIMAL(10,2) NOT NULL,
PRIMARY KEY (`id`)
);
INSERT INTO `Balance` (`date`, `account`, `amount`) VALUES
('2013-01-02', 'T355176', 8700),
('2013-01-03', 'T355176', 8900),
('2013-01-04', 'T355215', 33308),
('2013-01-03', 'T355215', 116581),
('2013-01-06', 'T812022', 275000),
('2013-01-02', 'T812063', 136500),
('2013-01-05', 'T812063', 11682),
('2013-01-06', 'T812064', 615100),
('2013-01-03', 'T812064', 25000),
('2013-01-02', 'T812085', 82500);
SELECT * FROM Balance WHERE date >= '2013-01-01' AND date <= '2013-01-06' ORDER BY date ASC;
CALL sp_getTotals('2013-01-01', '2013-01-06');
-- --------------------------------------------------------------------------------
-- Routine DDL
-- Note: comments before and after the routine body will not be stored by the server
-- --------------------------------------------------------------------------------
DELIMITER $$
CREATE DEFINER=`root`#`%` PROCEDURE `sp_getTotals`(IN startDate DATE, IN endDate DATE)
BEGIN
DECLARE dt DATE;
DECLARE amt DECIMAL(10,2);
DECLARE amtCalcPart DECIMAL(10,2);
DECLARE done INT DEFAULT 0;
DECLARE dtStart DATE;
DECLARE dtEnd DATE;
DECLARE cur1 CURSOR FOR SELECT date, amount FROM `TempMB`;
DECLARE cur2 CURSOR FOR SELECT startDate, endDate;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
DROP TEMPORARY TABLE IF EXISTS `TempMB`;
CREATE TEMPORARY TABLE IF NOT EXISTS `TempMB` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`date` DATE NOT NULL,
`amount` DECIMAL(10,2) NULL DEFAULT 0.00,
`amountCalc` DECIMAL(10,2) NULL DEFAULT 0.00,
PRIMARY KEY (`id`)
);
SET dtStart = DATE(startDate);
SET dtEnd = DATE(endDate);
WHILE dtStart <= dtEnd DO
INSERT INTO `TempMB` (`date`) SELECT dtStart;
SET dtStart = DATE_ADD(dtStart, INTERVAL 1 DAY);
END WHILE;
SELECT * FROM TempMB;
-- Fill temp table with info needed
UPDATE `TempMB` t
INNER JOIN
(
SELECT date, SUM(amount) AS amount
FROM Balance
WHERE
date >= startDate AND date <= endDate
GROUP BY date
ORDER BY date ASC
) b ON b.date = t.date
SET
t.amount = b.amount;
/*INSERT INTO `TempMB` (`date`, `amount`)
SELECT date, SUM(amount) AS amount
FROM Balance
WHERE
date >= startDate AND date <= endDate
GROUP BY date
ORDER BY date ASC;
*/
SET amtCalcPart = 0.00;
-- Initialise cursor
OPEN cur1;
-- USE BEGIN-END handler for cursor-control within own BEGIN-END block
BEGIN
DECLARE EXIT HANDLER FOR NOT FOUND BEGIN END;
-- Loop cursor throu temp records
LOOP
-- Get next value
FETCH cur1 INTO dt, amt;
-- Calculate amountCalc
SET amtCalcPart = (SELECT SUM(amount) as amt FROM `TempMB` WHERE Date <= dt);
UPDATE `TempMB` SET amountCalc = amtCalcPart WHERE date = dt;
END LOOP;
END;
-- Release cursor
CLOSE cur1;
SELECT * FROM TempMB;
END
Related
I have a cursor pagination implemented with MySQL (version 8.0), which works fine as long as there are no null values involved.
Here's my example data (id is a random UUID, date is date, time is time):
id | date | time
--------------------------
68 | 2017-10-28 | 22:00:00
d3 | 2017-11-03 | null
dd | 2017-11-03 | 21:45:00
62 | 2017-11-04 | 14:00:00
a1 | 2017-11-04 | 19:40:00
The cursor I use always consists of all three columns.
I use this query to get the next results (after the cursor):
SELECT * FROM table
WHERE (date > cursor.date)
OR (date = cursor.date AND time > cursor.time)
OR (date = cursor.date AND time = cursor.time AND id > cursor.id)
ORDER BY date ASC, time ASC, id ASC
And this query for prev results (before the cursor):
SELECT * FROM table
WHERE (date < cursor.date)
OR (date = cursor.date AND time < cursor.time)
OR (date = cursor.date AND time = cursor.time AND id < cursor.id)
ORDER BY date DESC, time DESC, id DESC
When using the prev query with cursor [id = dd, date = 2017-11-03, time = 21:45:00] it won't return the row with id = d3, because time is null, and this won't get selected by time < cursor.time.
Though I tried using time < cursor.time OR time IS NULL instead of time < cursor.time to include rows with null values. Which seems to fix this particular problem, but then creates a new problem: When using the prev query with cursor [id = d3, date = 2017-11-03, time = null], because now the result contains the row of the provided cursor.
I hope there's an easy solution for this. There seems to be no examples or tutorials on the web that deal with null values in cursor pagination.
Note: For the solution it doesn't matter if null will be sorted before or after non-null values, as long as it's consistent. (MySQL's default ordering is null < non-null)
I am not going to touch the topic of using cursors for pagination. There are alternatives, such as limit/offset.
But my recommendation for your queries is to use coalesce(), assigning a fake time for the comparison. MySQL makes this somewhat simple, because it supports time values in excess of 24 hours. And those would not be valid values for a date/time combination.
So:
SELECT *
FROM table
WHERE (date > cursor.date) OR
(date = cursor.date AND COALESCE(time, '24:00:00') > COALESCE(cursor.time, '24:00:00')) OR
(date = cursor.date AND COALESCE(time, '24:00:00') = COALESCE(cursor.time, '24:00:00') AND id > cursor.id)
ORDER BY date ASC, time ASC, id ASC
A more concise WHERE clause would be:
WHERE (date, COALESCE(time, '24:00:00'), id) > (cursor.date, COALESCE(cursor.time, '24:00:00'), cursor.id)
I'm a bit late to the party but give the following a try. The cursor logic needs to be adjusted for nullable columns and when a cursor row returns a null value for the time column.
Both examples listed below:
declare #cursorID nvarchar(2)
declare #cursorDate date
declare #cursorTime time(0)
declare #table table(id nvarchar(2), date date, time time(0))
insert into #table
values
('68', '2017-10-28', '22:00:00'),
('d3', '2017-11-03', NULL),
('dd', '2017-11-03', '21:45:00'),
('62', '2017-11-04', '14:00:00'),
('a1', '2017-11-04', '19:40:00')
--IF SELECTING A ROW WITH A NON-NULL VALUE FOR TIME
set #cursorID = 'dd'
set #cursorDate = (select date from #table where id = #cursorID)
set #cursorTime = (select time from #table where id = #cursorID)
--ASCENDING CURSOR VALUES
select * from #table
where
date >= #cursorDate
and (date > #cursorDate or (time >= #cursorTime
and (time > #cursorTime or id > #cursorID)))
order by
date asc, time asc, id asc
--DESCENDING CURSOR VALUES
select * from #table
where
date <= #cursorDate
and (date < #cursorDate or ((time <= #cursorTime or time is null)
and ((time < #cursorTime or time is null) or id < #cursorID)))
order by
date desc, time desc, id desc
--IF SELECTING A ROW WITH A NON VALUE FOR TIME
set #cursorID = 'd3'
set #cursorDate = (select date from #table where id = #cursorID)
set #cursorTime = (select time from #table where id = #cursorID)
--ASCENDING CURSOR VALUES
select * from #table
where
date >= #cursorDate
and (date > #cursorDate or (time is not null
or (time is null and id > #cursorID)))
order by
date asc, time asc, id asc
--DESCENDING CURSOR VALUES
select * from #table
where
date <= #cursorDate
and (date < #cursorDate or (time is null
and (id < #cursorID)))
order by
date desc, time desc, id desc
Add another column to the table. Make it a DATETIME. Combine date and time into it when not NULL; combine date with some particular time when NULL. Then your cursor has two columns to work with and no nulls.
If you have a reasonably recent version of MySQL, you can use a "generated stored" column, thereby avoiding any code changes.
And be sure to have INDEX(datetime, id).
If you are using MySQL 8.0 then you can consider to use row_number() window funciton create an unique sequential id (rn) for each row. Then just pass the rn for current row to get the previous rows.
Schema and insert statements:
create table cursortable( id varchar(10), date date, time time);
insert into cursortable values('68' , '2017-10-28' , '22:00:00');
insert into cursortable values('d3' , '2017-11-03' , null);
insert into cursortable values('dd' , '2017-11-03' , '21:45:00');
insert into cursortable values('62' , '2017-11-04' , '14:00:00');
insert into cursortable values('a1' , '2017-11-04' , '19:40:00');
Query to get the result for fist time:
select *,row_number()over(order by date,time,id)rn from cursortable
Output:
id
date
time
rn
68
2017-10-28
22:00:00
1
d3
2017-11-03
null
2
dd
2017-11-03
21:45:00
3
62
2017-11-04
14:00:00
4
a1
2017-11-04
19:40:00
5
Query to get the previous rows for cursor [id = dd, date = 2017-11-03, time = 21:45:00, rn=3] with only cursor [rn=3]:
with cte as
(
select *,row_number()over(order by date,time,id)rn from cursortable
)
select * from cte where rn<3
Output:
id
date
time
rn
68
2017-10-28
22:00:00
1
d3
2017-11-03
null
2
db<>fiddle here
If you don't want to introduce an computed column into your code then please try the below solution considering all three columns cursor [id = dd, date = 2017-11-03, time = 21:45:00]
Query:
with cte as
(
select *,row_number()over(order by date,time,id)rn from cursortable
)
,cte2 as
(
select * from cte where id='dd' and date= '2017-11-03' and time= '21:45:00'
)
select cte.id,cte.date,cte.time from cte inner join cte2 on cte.rn<cte2.rn
Output:
id
date
time
68
2017-10-28
22:00:00
d3
2017-11-03
null
db<>fiddle here
Your code would be like below:
with cte as
(
select *,row_number()over(order by date,time,id)rn from cursortable
)
,cte2 as
(
select * from cte where id=cursor.id and date= cursor.date and time= cursor.time
)
select cte.id,cte.date,cte.time from cte inner join cte2 on cte.rn<cte2.rn
Here i have table attendances
I need result as shown below
How can i achieve this in mysql without using any programming language
Sql File is Attendances.sql
We can try a pivot query approach, aggregating by user and date:
SELECT
user_id,
DATE(date_time) AS date,
TIMESTAMPDIFF(MINUTE,
MAX(CASE WHEN status = 'IN' THEN date_time END),
MAX(CASE WHEN status = 'OUT' THEN date_time END)) / 60.0 AS hours
FROM yourTable
GROUP BY
user_id,
DATE(date_time);
The caveats of this answer are many. It assumes that there would be only one IN and OUT entry, per user, per day. If a period could cross over dates, then my answer might not generate correct results. Also, if an IN or OUT value be missing, then NULL would be reported for the hours value.
I have Achieve it my self by creating a mysql function and view
Mysql View
CREATE OR REPLACE VIEW `view_attendances` AS
SELECT
`a`.`id` AS `a1_id`,
`a`.`user_id` AS `user_id`,
CAST(`a`.`date_time` AS DATE) AS `date`,
`a`.`date_time` AS `in`,
`a2`.`id` AS `a2_id`,
`a2`.`date_time` AS `out`,
(TIMESTAMPDIFF(SECOND,
`a`.`date_time`,
`a2`.`date_time`) / 3600) AS `hours`
FROM
(`attendances` `a`
JOIN `attendances` `a2` ON (((`a`.`is_confirm` = 1)
AND (`a`.`status` = 'IN')
AND (`a2`.`id` = FN_NEXT_OUT_ATTENDANCE_ID(`a`.`user_id`, `a`.`date_time`, `a`.`status`))
AND (a2.status = 'OUT')
AND (CAST(`a`.`date_time` AS DATE) = CAST(`a2`.`date_time` AS DATE)))))
Mysql Function
CREATE FUNCTION `fn_next_out_attendance_id`( _user_id INT, _attendance_date_time DATETIME, _status VARCHAR(10) ) RETURNS int(11)
BEGIN
DECLARE _id INT(11);
SELECT
id INTO _id
FROM
attendances
WHERE
is_confirm = 1
AND user_id = _user_id
AND date_time > _attendance_date_time
AND `status` <> _status
ORDER BY
date_time ASC LIMIT 1 ;
RETURN if (_id IS NULL, 0, _id);
END
MariaDB 10 on Windows
When creating the following stored procedure in MySQL Workbench, I get error 1064. The error is marked on the last line "LIMIT 1". Trying to create it in HeidiSQL also returns error 1064. This suggests a missing parentheses, but none is missing.
Any help appreciated:
CREATE PROCEDURE `available_room` (in p_client_id int(11), in p_room_id int(11), in dateQF date, in a_gender varchar(3))
BEGIN
SELECT
room_name,
room_type_name,
counted,
num_guests,
minimum,
room_id AS room_id_selected
FROM (SELECT * FROM room
WHERE room_id=p_room_id AND
(NOT EXISTS (
SELECT * FROM invoice
WHERE client_id=p_client_id AND
product_type = 'LODGING' AND
dateQF BETWEEN date1 AND DATE(DATE_ADD(date2, INTERVAL -1 DAY)) AND
commit_invoice=1 AND
room_id = product_id AND
gender != a_gender
) OR
(NOT EXISTS (
SELECT * FROM invoice
WHERE client_id=p_client_id AND
product_type = 'LODGING' AND
dateQF BETWEEN date1 AND DATE(DATE_ADD(date2, INTERVAL -1 DAY)) AND
commit_invoice=1 AND
room_id = product_id
))))A
LEFT JOIN room_type_content ON
room_type_content.room_type_id=A.room_type AND
language_id='en'
LEFT JOIN(SELECT product_id, count(product_id)AS counted, MIN(NULLIF(quantity,0))AS minimum FROM invoice
WHERE gender =a_gender AND
client_id=p_client_id AND
product_type = 'LODGING' AND
dateQF BETWEEN date1 AND DATE(DATE_ADD(date2, INTERVAL -1 DAY)) AND
(commit_invoice=1 OR commit_invoice=3) AND
invoice_set !='SYSTEM'
GROUP BY product_id)B
ON B.product_id=p_room_id
ORDER BY counted DESC
LIMIT 1
END
You forgot a semi-colon after LIMIT 1.
You also need to change the standard delimiter before and after creating the procedure.
Documentation: CREATE PROCEDURE and CREATE FUNCTION Syntax
Fixed version of your code:
DELIMITER $$
CREATE PROCEDURE `available_room` (in p_client_id int(11), in p_room_id int(11), in dateQF date, in a_gender varchar(3))
BEGIN
SELECT
room_name,
room_type_name,
counted,
num_guests,
minimum,
room_id AS room_id_selected
FROM (SELECT * FROM room
WHERE room_id=p_room_id AND
(NOT EXISTS (
SELECT * FROM invoice
WHERE client_id=p_client_id AND
product_type = 'LODGING' AND
dateQF BETWEEN date1 AND DATE(DATE_ADD(date2, INTERVAL -1 DAY)) AND
commit_invoice=1 AND
room_id = product_id AND
gender != a_gender
) OR
(NOT EXISTS (
SELECT * FROM invoice
WHERE client_id=p_client_id AND
product_type = 'LODGING' AND
dateQF BETWEEN date1 AND DATE(DATE_ADD(date2, INTERVAL -1 DAY)) AND
commit_invoice=1 AND
room_id = product_id
))))A
LEFT JOIN room_type_content ON
room_type_content.room_type_id=A.room_type AND
language_id='en'
LEFT JOIN(SELECT product_id, count(product_id)AS counted, MIN(NULLIF(quantity,0))AS minimum FROM invoice
WHERE gender =a_gender AND
client_id=p_client_id AND
product_type = 'LODGING' AND
dateQF BETWEEN date1 AND DATE(DATE_ADD(date2, INTERVAL -1 DAY)) AND
(commit_invoice=1 OR commit_invoice=3) AND
invoice_set !='SYSTEM'
GROUP BY product_id)B
ON B.product_id=p_room_id
ORDER BY counted DESC
LIMIT 1;
END $$
DELIMITER ;
BEGIN
declare currID INT(30);
declare maxID INT(30);
declare first_period datetime;
declare s_date datetime;
declare tot_sent INT(30);
declare weighted_score FLOAT(30);
declare aid INT(10);
set first_period = date_add(stats_start_from, INTERVAL 49 DAY);
set currID = (select min(rowid) from weekly_stats);
set maxID = (select max(rowid) from weekly_stats);
WHILE (currID <= 40000) DO
select accountid, week_start_date INTO aid, s_date from weekly_stats where rowid = currID;
if (s_date >= first_period) then
set tot_sent = (select sum(weekly_total_sent) from weekly_stats where (accountid = aid AND week_start_date <= s_date AND week_start_date >= (date_add(s_date, INTERVAL (-49) DAY))));
set weighted_score = (select sum(weekly_total_sent*weekly_raw_score) from weekly_stats where (accountid = aid AND week_start_date <= s_date AND week_start_date >= (date_add(s_date, INTERVAL (-49) DAY))));
set weighted_score = weighted_score/tot_sent;
update weekly_stats set weighted_scores = weighted_score where rowid = currID;
end if;
set currID = currID + 1;
end while;
END
Basically, I have a column in the table called weighted_scores that calculates as follows: weighted score = sum of (raw score*sent mail) for the last 8 weeks, divided by the total mail sent in the last 8 weeks. So, it is a 8 week score average weighted by the volume of mail sent each week.
My procedure runs really slowly and times out (after ten minutes) for even a small number of rows. I was wondering what the issue was, maybe inefficient querying, calculations, etc? Any ideas would be much appreciated. Thanks!
create table weekly_stats
(
accountid INT(10),
week_start_date datetime,
weekly_total_sent INT(30),
weekly_hbounce INT(30),
weekly_sbounce INT(30),
weekly_spam INT(30),
weekly_optouts INT(30),
weekly_opens INT(30),
weekly_CT INT(30),
rowid INT(30),
weekly_raw_score FLOAT(30),
weighted_scoresFLOAT(30)
-- indexes not shown
);
I have table 1 with 3 columns id, startdate and enddate. With order id being the primary key how do I list the dates between the date range Startdate and Enddate?
What I have:
id Startdate EndDate
1 2/11/2014 2/13/2014
2 2/15/2014 2/17/2014
What I need:
id Date
1 2/11/2014
1 2/12/2014
1 2/13/2014
2 2/15/2014
2 2/16/2014
2 2/17/2014
How do I do this?
Use recursive CTE:
WITH tmp AS (
SELECT id, StartDate AS [Date], EndDate
FROM MyTable
UNION ALL
SELECT tmp.id, DATEADD(DAY,1,tmp.[Date]), tmp.EndDate
FROM tmp
WHERE tmp.[Date] < tmp.EndDate
)
SELECT tmp.ID, tmp.[Date]
FROM tmp
ORDER BY tmp.id, tmp.[Date]
OPTION (MAXRECURSION 0) -- For long intervals
If you have to use cursor/loop, most times you are doing it wrong.
If you do a one-off setup of an auxiliary calendar table as shown at Why should I consider using an auxiliary calendar table?, possibly omitting a lot of the columns if you don't need them, like this:
CREATE TABLE dbo.Calendar
(
dt SMALLDATETIME NOT NULL
PRIMARY KEY CLUSTERED,
Y SMALLINT,
M TINYINT,
D TINYINT
)
GO
SET NOCOUNT ON
DECLARE #dt SMALLDATETIME
SET #dt = '20000101'
WHILE #dt < '20300101'
BEGIN
INSERT dbo.Calendar(dt) SELECT #dt
SET #dt = #dt + 1
END;
UPDATE dbo.Calendar SET
Y = YEAR(dt),
M = MONTH(dt),
D = DAY(dt);
(You may well not need the Y, M, D columns at all, but I left those in to show that more data can be stored for fast access - the article I linked to shows how that could be used.)
Then if your table is named "so", your code would simply be
SELECT A.id, C.dt
FROM so AS A
JOIN Calendar AS C
ON C.dt >= A.StartDate AND C.dt<= A.EndDate
An advantage of using an auxiliary table like that is that your queries can be faster: the work done in setting one up is a one-time cost which doesn't happen during usage..
Instead of using CTE (to over come recursive and performance when date range is large) below query can be used to get the list of dates between two date range.
DECLARE #StartDateSTR AS VARCHAR(32); DECLARE #EndDateSTR AS
VARCHAR(32); DECLARE #EndDate AS DATE; DECLARE #StartDate AS DATE;
SET #StartDateSTR = '01/01/1990'; SET #EndDateSTR = '03/31/2025'; SET
#StartDate = CAST(#StartDateSTR AS date); SET #EndDate =
cast(#EndDateSTR AS date); SELECT
DATEADD(DAY, n1.rn - 1, #StartDate) AS dt FROM (SELECT rn=Row_number() OVER( ORDER BY (SELECT NULL)) FROM sys.objects a
CROSS JOIN sys.objects b CROSS JOIN sys.objects c CROSS JOIN
sys.objects d) as n1 WHERE n1.[rn] <= Datediff(dd, #StartDate,
#EndDate)+1;