mysql running total as view - mysql

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.

Related

Get the longest and shortest "chains" in MySQL?

Consider this schema:
create table Operation(id integer, name varchar(100));
create table Pipeline(operation_in integer, operation_out integer);
Pipeline has foreign keys to Operations, so pipelines are chained, in a way. operation_out is nullable. How do I get the names of operations in both the longest and the shortest pipeline chains using MySQL?
Operations look like this:
INSERT INTO Operation VALUES (1, 'operation one');
While the pipelines look something like this:
INSERT INTO Pipeline VALUES (1, 2);
INSERT INTO Pipeline VALUES (2, 4);
INSERT INTO Pipeline VALUES (4, 7);
INSERT INTO Pipeline VALUES (7, NULL);
I.e. the chain here would be operations with ID 1, 2, 4, 7 and expected result along the lines of:
"operation one", "operation two", "operation four"...
After a few hours of research, I am not sure quite what the solution I am looking for.
Any MySQL version is applicable.
In MySQL 8.x you can use a Recursive CTE to find the chains you need.
For example:
with recursive
a as (
select
p.operation_in,
p.operation_out as current_out,
o.name as op_names,
concat('', p.operation_in) as chain,
1 as size
from pipeline p
join operation o on o.id = p.operation_in
where not exists (
select 1 from pipeline p2 where p2.operation_out = p.operation_in
)
union all
select
a.operation_in,
p.operation_out,
concat(op_names, ', ', o.name),
concat(chain, ',', p.operation_in),
size + 1
from a
join pipeline p on p.operation_in = a.current_out
join operation o on o.id = p.operation_in
),
chains as (
select * from a where current_out is null
)
select op_names, chain, size
from chains
where size = (select max(size) from chains) -- finds the longest one
or size = (select min(size) from chains); -- finds the shortest one
Result:
op_names chain size
--------------------------------- ------- ----
op-nine, op-six 9,6 2
op-one, op-two, op-four, op-seven 1,2,4,7 4
The data script I used is:
create table operation (id integer, name varchar(100));
create table pipeline (operation_in integer, operation_out integer);
insert into operation values (1, 'op-one');
insert into operation values (2, 'op-two');
insert into operation values (4, 'op-four');
insert into operation values (6, 'op-six');
insert into operation values (7, 'op-seven');
insert into operation values (9, 'op-nine');
insert into pipeline values (1, 2);
insert into pipeline values (2, 4);
insert into pipeline values (4, 7);
insert into pipeline values (7, null);
insert into pipeline values (9, 6);
insert into pipeline values (6, null);

SQL Server : how to insert-into-select-from, with scalar function in select caluse

I am having trouble, while inserting data from a select statement having scalar function call. I posted sample script below, and little explanation and question in the comments.
---target table (in my case, this is not table variable, but a regular table, here for simplicity to posted this code as table variable to get the idea)
declare #tblItems table (
Id int identity(1,1)
,ItemID int
,TranNo varchar(20)
,Qty decimal(18,3)
,SomeCalculatedValue decimal(18,3)
)
--a dummay temp table, works like a source table
declare #tblTemp table (
Id int identity(1,1)
,ItemID int
,TranNo varchar(20)
,Qty decimal(18,3)
,SomeCalculatedValue decimal(18,3)
)
--put some dummy data in target table
insert into #tblItems(ItemID, TranNo, Qty, SomeCalculatedValue)
values
(1, 'GRN-001', 10, 0),
(2, 'GRN-002', 20, 0),
(3, 'GRN-003', 15, 0),
(4, 'GRN-004', 32, 0),
(5, 'GRN-005', 18, 0)
;
--insert 3 new rows in temp table, which later I want to insert in target table
insert into #tblTemp(ItemID, TranNo, Qty, SomeCalculatedValue)
values
(1, 'GRN-006', 6, 0), -- this line is working work fine,
(1, 'GRN-007', 3, 0), -- but this line is having problem, because it is not considering the last line( with TranNo='GRN-006' )
(2, 'GRN-008', 8, 0)
--here is the actual work, I need to read data from temp table to target table
--and the key requirement is the column 'SomeCalculatedValue'
--it should call a scalar function, and within that function I have to perform some calculations based on same target table
--for each ItemID passed, that scalar function will works as: it performs some sort of calculations on existing rows
--for that particular ItemID, to simplify the understanding you can think of it as Running-Total(not actually running total, but concept
--is same that each row value will based on previous row value)
insert into #tblItems(ItemID, TranNo, Qty, SomeCalculatedValue)
select
ItemID
,TranNo
,Qty
,[dbo].[GetCalculatedValue] (ItemID, Qty) as SomeCalculatedValue -- this function will perform some calcualations
from #tblTemp
select * from #tblItems
I have two tables, #tblItems and #tblTemp. I have to insert rows from #tblTemp to #tblItems, but in the select clause of #tblTemp, I used a scalar function, lets say, GetCalculatedValue(ItemID, Qty), which performs some calculations for specific ItemID from target table, and for each row it calculates a value which should be inserting in the #tblItems. It is not really Running-Total but for the sake for understanding it can think of as running total, because each row value will depend upon last previous lines.
So problem is that when #tblTemp has more than 1 row for a particular ItemID, it should consider the rows already inserted, but I think this insert-into-select statement will insert all rows at once, so it is not considering the last lines for particular ItemID which are in same select statement. You can review the code, I posted some comments also for explanation.

How avoid joining multiple times to the same table to get results back in single rows?

I have a schema that requires joining to the same table multiple times to get more information on the data pointed to by the columns. Below is an example schema that shows this situation:
SQL Fiddle: http://sqlfiddle.com/#!9/7a4019/1
CREATE TABLE STATE
(
employee INT NOT NULL,
boss INT,
manager INT,
rep INT
);
CREATE TABLE EMPLOYEE
(
id INT NOT NULL,
name VARCHAR(255) NOT NULL
);
INSERT INTO EMPLOYEE (id, name) VALUES (1, "Joe");
INSERT INTO EMPLOYEE (id, name) VALUES (2, "John");
INSERT INTO EMPLOYEE (id, name) VALUES (3, "Jack");
INSERT INTO EMPLOYEE (id, name) VALUES (4, "Jeff");
INSERT INTO EMPLOYEE (id, name) VALUES (5, "Jason");
INSERT INTO STATE (employee, boss, manager, rep) VALUES (1, 2, 3, 4);
INSERT INTO STATE (employee, boss, manager, rep) VALUES (2, 3, 3, 4);
INSERT INTO STATE (employee, boss, manager, rep) VALUES (3, NULL, NULL, 4);
INSERT INTO STATE (employee, boss, manager, rep) VALUES (4, 3, 3, NULL);
INSERT INTO STATE (employee, boss, manager, rep) VALUES (5, 2, 3, 4);
Currently, the only way i know to get this information in single rows for each employee, is left joining multiple times like this:
SELECT employee, b.name AS boss, m.name AS manager, r.name AS rep
FROM STATE
LEFT JOIN EMPLOYEE b ON b.employee = STATE.boss
LEFT JOIN EMPLOYEE m ON m.employee = STATE.manager
LEFT JOIN EMPLOYEE r ON r.employee = STATE.rep
Is there a way to do it without joins and without subqueries?
You asked:
Is there a way to do it without joins and without subqueries?
Not really. You are using the JOIN operations precisely as they're intended to be used -- each JOIN reflects a specific relationship between rows of a table.
You can avoid doing multiple joins by using aggregate functions, which I recently found useful after hitting the limit (61) of the number of joins that can be done in a query in MySQL/MariaDB.
SELECT s.employee,
GROUP_CONCAT(if(e.id=s.boss, name, NULL)) as boss,
GROUP_CONCAT(if(e.id=s.manager, name, NULL)) as manager,
GROUP_CONCAT(if(e.id=s.rep, name, NULL)) as rep,
FROM STATE s, EMPLOYEE e
GROUP BY s.employee
The above example uses MySQL's GROUP_CONCAT function. It appears not to be an ANSI standard. Other relational databases may have similar functions. A cursory web search turned up a page that discussed aggregate functions for various relational databases: http://www.postgresonline.com/journal/archives/191-String-Aggregation-in-PostgreSQL,-SQL-Server,-and-MySQL.html

How to add multiple product_ids against single category_id in single query

I have a table ->
table_product_categories - "product_id", "category_id"
I want to add multiple product ids (that are in range) against each category. How can i do that?
I am using:
insert into table_product_categories values (11, 1);
insert into table_product_categories values (12, 1);
insert into table_product_categories values (13, 1);
Is there any way where we can achieve the same in single query?
Devesh
You can't generate sequence numbers in MySQL. You could use a temporary table with sequence numbers to achieve that
insert into table_product_categories
select seq_num, 1
from your_temp_table
where seq_num between 11 and 30
order by seq_num asc
You can add multiple rows in single query.
insert into table_product_categories values (11, 1), (12,1), (13,1);
To add range you should iterate to that range and make a string like (11, 1), (12,1), (13,1). Then execute a query.

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

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;