SQL Cursor to determine median value - mysql

I am trying to write a Stored Procedure to retrieve the median salary from a table and am having trouble figuring out how to retrieve the data from the cursor.
Currently my code is:
DELIMITER //
CREATE PROCEDURE MedianSalary(OUT median INT)
BEGIN
DECLARE counter int(5) DEFAULT 0;
DECLARE set_size int(5) DEFAULT (SELECT count(*) from employee);
DECLARE median_index int(5) DEFAULT (SELECT floor(count/2));
DECLARE all_salaries CURSOR
FOR SELECT salary from employee,
OPEN all_salaries;
WHILE #counter != #median_index
BEGIN
SET #counter = #counter + 1,
FETCH NEXT from all_salaries,
END;
FETCH all_salaries INTO median;
CLOSE all_salaries;
END //
DELIMITER ;
I can't seem to find any documentation similar to what I am trying to achieve, any help would be greatly appreciated.

I don't have an answer to your stored procedure problem, but note that we can actually find the median from a table in MySQL fairly easily using session variables to simulate the row number:
SET #row_number = 0;
SET #row_count = (SELECT COUNT(*) FROM yourtable);
SELECT AVG(salary) AS median
FROM
(
SELECT (#row_number:=#row_number + 1) AS rn, salary
FROM yourTable
ORDER BY salary
) t
WHERE
(#row_count % 2 = 0 AND rn IN (#row_count / 2, (#row_count / 2) + 1) OR
#row_count % 2 <> 0 AND rn = #row_count / 2);
Demo
Note the ugliness in the WHERE clause has to do with the edge case of your table having an even number of records. In this case, there technically is not a single median record, so instead I report the mean of the two records which sit about the median on either side.

Related

How to split count() query into 10 groups in my sql

I am currently using mysql and I have to split the data into 10 groups.
For example, if the total count of data is 90, it should go like
1~9,
10~18,
19~27,
28~36,
37~45,
46~54,
55~63,
64~72,
73~81,
82~90.
if the total count of data is 100, it should go like
1~10,
11~20,
21~30,
31~40,
41~50,
51~60,
61~70,
71~80,
81~90,
91~100.
Can anyone give me a clue to split the data into 10 groups. I used rownum, but it did not work....
select total.row_num,
total.name,
total.reg,
total.id,
total.motspd
from(
select
(#row_num:=#row_num+1) AS row_num,
cg.group_name as name,
td.reg_date as reg,
td.car_id as id,
td.mcu_motspd as motspd
from
cartracker.tracker_data td
left join car c on (c.car_device_no = td.car_id)
left join car_group cg on (c.car_group_no = cg.car_group_no)
where cg.car_group_no = "1"
group by DATE_FORMAT(td.reg_date, "%Y%M%d%h%m")
)total
This is a result of a query, but it shows wrong row numbers.I want row_num goes from 0 to the end number of the data. but, in the picture, it starts from 44,713. can anyone help me to fix row num as it starts from 0 to the end number of the data.
attached image
I am going by the question in the title:
I am currently using mysql and I have to split the data into 10 groups.
That is exactly what the function ntile() does. So:
select t.*,
ntile(10) over (order by <whatever>) as tile
from t;
I have no idea what the query has to do with this question.
Haven't realize OP want with dynamic table BEFORE re-edit, this is not cleaver and require a lot of time to process though. switch a with OP table name and create rownumber first.
DECLARE #TotalNum INT;
DECLARE #Num INT;
DECLARE #NUm2 INT;
DECLARE #COUNTS INT;
SET #COUNTS = (select count(rownum) from a)/10
SET #TotalNum = 10
SET #Num =1
SET #Num2 =0
WHILE #Num <= #TotalNum
BEGIN
update a
set a.flag = #Num
where a.rownum >= (#COUNTS)*#NUM2+1 and a.rownum <= #NUM*(#COUNTS)
SET #Num = #NUM + 1
SET #Num2 = #NUM2 + 1
END

MySQL Variable Assignment via Procedure Not Working Correctly

In the code below, I'm trying go through the results of endDateTable row by row, comparing the current row's endDate to the previous row's endDate. If there has been any change since the previous, we increment #revisionNum. However, upon populating the new table, all of the #revisionNum entries are 0. What am I doing wrong?
NOTE: I'm using prepared statements in this manner since doing a straightforward SELECT into a variable gives a syntax error due to the LIMIT clause not allowing a variable in our version of MySQL.
BEGIN
DECLARE _currentEndDate DATETIME DEFAULT now();
DECLARE _priorEndDate DATETIME DEFAULT now();
SET #ResultsCount = (SELECT COUNT(*) FROM mainTable);
SET #j = 0;
WHILE #j < #ResultsCount DO
SET #revisionNum = 0;
/*CURRENT END DATE*/
SET #appResultQueryCurrent = CONCAT('
SELECT
end_date
INTO _currentEndDate
FROM endDateTable
LIMIT ', #j, ', 1'
);
PREPARE currentQueryStmt FROM #appResultQueryCurrent;
EXECUTE currentQueryStmt;
/*PREVIOUS END DATE*/
SET #appResultQueryPrior = CONCAT('
SELECT
end_date
INTO _priorAppEndDate
FROM endDateTable
LIMIT ', IF(#j = 0, 0, #j - 1), ', 1'
);
PREPARE priorQueryStmt FROM #appResultQueryPrior;
EXECUTE priorQueryStmt;
SET #revisionNum = IF(
#j = 0 OR (_currentEndDate = _priorEndDate),
#revisionNum,
IF(
_currentEndDate != _priorEndDate,
#revisionNum + 1,
#revisionNum
)
);
INSERT INTO finalTable (RevisionNum)
SELECT
#revisionNum AS RevisionNum
FROM endDateTable;
SET #j = #j +1;
END WHILE;
END $$
You don't need a loop, you can use INSERT INTO ... SELECT ..., incrementing the variable in the select query.
You also need an ORDER BY criteria to specify how to order the rows when comparing one row to the previous row.
INSERT INTO finalTable (RevisionNum, otherColumn)
SELECT revision, otherColumn
FROM (
SELECT IF(end_date = #prev_end_date, #revision, #revision := #revision + 1) AS revision,
#prev_end_date := end_date,
otherColumn
FROM endDateTable
CROSS JOIN (SELECT #prev_end_date := NULL, #revision := -1) AS vars
ORDER BY id) AS x
DEMO
The offset value in the LIMIT clause is tenuous without an ORDER BY.
Without an ORDER BY clause, MySQL is free to return results in any sequence.
There is no guarantee that LIMIT 41,1 will return the row before LIMIT 42,1, or that it won't return the exact same row as LIMIT 13,1 did.
(A table in a relational database represents an unordered set of tuples, there is no guaranteed "order" or rows in a table.)
But just adding ORDER BY to the queries isn't enough to fix the Rube-Goldberg-esque rigmarole.
In the code shown, it looks like each time through the loop, we're inserting a copy of endDateTable into finalTable. If that's 1,000 rows in endDateTable, we're going to get 1,000,000 rows (1,000 x 1,000) inserted into finalTable. Not at all clear why we need so many copies.
Given the code shown, it's not clear what the objective is. Looks like we are conditionally incrementing revisionNum, the end result of which is the highest revision num. Just guessing here.
If there is some kind of requirement to do this in a LOOP construct, within a procedure, I'd think we'd do a cursor loop. And we can use procedure variables vs user-defined variables.
Something along these lines:
BEGIN
DECLARE ld_current_end_date DATETIME;
DECLARE ld_prior_end_date DATETIME;
DECLARE li_done INT;
DECLARE li_revision_num INT;
DECLARE lcsr_end_date CURSOR FOR SELECT t.end_date FROM `endDateTable` t ORDER BY NULL;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET li_done = TRUE;
SET li_done = FALSE;
SET li_revision_num = 0;
OPEN lcsr_end_date;
FETCH lcsr_end_date INTO ld_current_end_date;
SET ld_prior_end_date = ld_current_end_date;
WHILE NOT li_done DO
SET li_revision_num = li_revision_num + IF( ld_current_end_date <=> ld_prior_end_date ,0,1);
SET ld_prior_end_date := ld_current_end_date;
FETCH lcsr_end_date INTO ld_current_end_date;
END WHILE;
CLOSE lcsr_end_date;
INSERT INTO `finalTable` (revisionnum) VALUES (li_revision_num);
END $$
Note the "order by" clause on the SELECT, its not clear what the rows should be ordered on, so we're using a literal as a placeholder.
As the end result, we insert a single row into finalTable.
Again, it's not clear what the code in the question is supposed to achieve, but doing a cursor loop across ordered rows would be much more efficient than a bazillion dynamic SQL executions fetching individual rows.

SQL Server T-SQL breaking a string into a temp table for a join

We have a SQL Server Scalar Function and part of the process is to take one of the input values and do the following
'inputvalue'
Create a table variable and populate with the following rows
inputvalue
inputvalu
inputval
inputva
inputv
input
inpu
inp
Then this table is joined to a query, ordered by len of the inputvalue desc and returns the top 1. The actual code is here
DECLARE #Result NVARCHAR(20);
DECLARE #tempDialCodes TABLE (tempDialCode NVARCHAR(20));
DECLARE #counter INT = LEN(#PhoneNumber);
WHILE #counter > 2
BEGIN
INSERT INTO #tempDialCodes(tempDialCode) VALUES(#PhoneNumber);
SET #PhoneNumber = SUBSTRING(#PhoneNumber, 1, #counter - 1);
SET #counter = #counter - 1;
END
SET #Result = (SELECT TOP 1 [DialCodeID]
FROM DialCodes dc JOIN #tempDialCodes s
ON dc.DialCode = s.tempDialCode
ORDER BY LEN(DialCode) DESC);
RETURN #Result
It works fine but I am asking if there is a way to replace the while loop and somehow joining to the inputvalue to get the same result. When I say it works fine, it's too dam slow but it does work.
I'm stumped on how to break up this string without using a loop and to a table variable but my warning light tells me this is not efficient for running against a table with a million rows.
Are you familiar with tally tables? The speed difference can be incredible. I try to replace every loop with a tally table if possible. The only time I haven't been able to so far is when calling a proc from within a cursor. If using this solution I would recommend a permanent dbo.Tally table with a sufficiently large size rather than recreating every time in the function. You will find other uses for it!
declare #PhoneNumber nvarchar(20) = 'inputvalue';
declare #tempDialCodes table (tempDialCode nvarchar(20));
--create and populate tally table if you don't already a permanent one
--arbitrary 1000 rows for demo...you should figure out if that is enough
--this a 1-based tally table - you will need to tweak if you make it 0-based
declare #Tally table (N int primary key);
insert #Tally
select top (1000) row_number() over (order by o1.object_id) from sys.columns o1, sys.columns o2 order by 1;
--select * from #Tally order by N;
insert #tempDialCodes
select substring(#PhoneNumber, 1, t.N)
from #Tally t
where t.N between 3 and len(#PhoneNumber)
order by t.N desc;
select *
from #tempDialCodes
order by len(tempDialCode) desc;

WHILE LOOP not working as intended in mysql PROCEDURE

I have 100 rows in table tbl_master_sales and an empty table tbl_customer_sales.When I use WHILE loop to insert data from tbl_master_salesto tbl_customer_sales,it only inserts 50 rows.However,it should have insert 100 rows taking two iteration of while loop.What may be my mistake in following PROCEDURE:
CREATE PROCEDURE ROWPERROW()
BEGIN
DECLARE n INT DEFAULT 0;
DECLARE i INT DEFAULT 0;
SELECT COUNT(*) FROM tbl_master_sales INTO n;
SET i=0;
WHILE i<n DO
INSERT INTO tbl_customer_sales (id,card_number,customer_name,customer_phone,bill_no,item_code,division,section,department,item_name,store,promo_name,billdiscount_name,billqty,promo_amount,bill_discount_amount,loyaltyamount,net_amount)
SELECT id, card_number, customer_name, customer_mobile, billno, itemcode, division, section, department, itemname, store, promoname, billdiscountname, billqty, promoamount, billdiscountamount, loyaltyamount, netamount
FROM tbl_master_sales
WHERE NOT EXISTS(SELECT 1
FROM tbl_customer_sales
WHERE id=tbl_master_sales.id)
LIMIT i,50;
SET i = i + 50;
END WHILE;
End;;
I don't see anything wrong with your procedure code logic but the reason for inserting only 50 rows could be the NOT EXISTS part shown below, which is restricting from inserting duplicate rows (or) filtering out the rest records.
WHERE NOT EXISTS(
SELECT 1
FROM tbl_customer_sales
WHERE id=tbl_master_sales.id)

Arithmetic operation in MySQL through Procedure

I have a table t1 which have a column Marks in this column values are 10, 20, 30, 40.
Now I want to use a procedure to get this result:
Marks Total_Marks
10 10
20 30
30 60
40 100
DELIMITER //
CREATE PROCEDURE Total_Marks ( In Num Int(4) )
Begin
Declare Mark Int(4);
Declare Add_M Int(4);
DECLARE NO_MORE_ROWS BOOLEAN;
DECLARE DataCursor CURSOR FOR SELECT Marks
FROM t1 where marks = Num;
DECLARE DataCursor1 CURSOR FOR SELECT Sum(Marks) FROM t1;
OPEN DataCursor;
FETCH DataCursor INTO Mark;
CLOSE DataCursor;
OPEN DataCursor1;
READ_LOOP1: LOOP
FETCH DataCursor1 INTO Add_M;
IF NO_MORE_ROWS THEN
LEAVE READ_LOOP1;
END IF;
BEGIN
SET Add_M = SUM(Mark);
END;
END LOOP READ_LOOP1;
CLOSE dataCursor1;
SET NO_MORE_ROWS = FALSE;
end //
DELIMITER ;
You don't need a procedure at all, let alone cursors. But what you need, is a column that defines the order of the rows as mentioned in the comments.
create table foo (id int auto_increment primary key, bar int);
insert into foo(bar) values (10), (20), (30), (40);
In this example I introduced the column id for that matter. Or you can of course just order by your marks or whatever suits your needs.
select
bar
, #total := #total + bar as my_total
from
foo
, (select #total := 0) var_init
order by id
see it working live in this sqlfiddle
As explanation, with this cross joined query
, (select #total := 0) var_init
we initialize our variable holding the running total #total. It's the same as writing
set #total = 0;
select
bar
, #total := #total + bar as my_total
from
foo
order by id;
The rest is self explaining I guess.
You can read more about these type of variables here.
UPDATE (for completeness):
Here are two other possibilities how to solve it without variables. Although I like variables usually better, cause in this one
select
t1.bar
, sum(t2.bar)
from
foo t1
inner join foo t2 on t1.id >= t2.id
group by t1.id;
you end up with a potentially huge temporary table, since you join every row to all previous rows and then calculate the sum.
And in this solution
select
bar
, (select sum(bar) from foo sf where sf.id <= foo.id) as my_total
from
foo;
you have a dependent subquery executed for each row. This is even worse than the previous solution.
I posted those just for completeness and if you really can't use variables (because of creating a view for example).