I wrote a Stored Procedure in MySQL.
I am trying to get days in column of any month respectively.
below is the sql query
DELIMITER $$
DROP PROCEDURE IF EXISTS `GenerateReport`$$
CREATE
DEFINER = `FreeUser`#`localhost`
PROCEDURE `databasename`.`GenerateReport`(
IN gDate DATE,
IN gUserID VARCHAR(10)
)
BEGIN
DECLARE gStart INT;
DECLARE gDays INT;
SET gStart = 1;
SET gDays = DAY(LAST_DAY(gDate));
SELECT e.AssociateID, CONCAT(e.FirstName, ' ', e.MiddleName, ' ', e.LastName) AS `EmployeeName`, d.DesignationName,
ts.TSDate,
/* Trying to get days in column, Starts here */
loopOne: LOOP
IF gStart <= gDays THEN
gStart = gStart + 1;
case gStart IS NOT NULL THEN 'ItsDate' ELSE 'NoDate' END,
ITERATE loopOne;
END IF;
LEAVE loopOne;
END LOOP loopOne;
/* Trying to get days in column, ends here */
gStart AS `Start`, gDays AS `NoofDays`
FROM timesheet ts
LEFT JOIN employee e ON e.EmpID = ts.EmpID
LEFT JOIN designation d ON d.DesignationId = e.DEsignationID
WHERE DATE_FORMAT(ts.TSDate, '%Y-%m') = DATE_FORMAT(gDate, '%Y-%m')
GROUP BY e.AssociateID;
END$$
DELIMITER ;
Desired Output
consider an image for extra UI, below may not be good respresntation
----------------------------------------------------------
AssociateID | EmployeeName | DesignationName | 1 | 2 | 3 | 4 | .... | 31 | Start | gDays
---------------------------------------------------------
001 |John Carter | Dae ja | ItsDate | ItsDate| .... | ItsDate | 1 | 31
----------------------------------------------------------------------------------
It's not possible to build a query mixed with loops, if statements or any other flow control that way.
You need to build an dynamic prepared statement and then execute it.
drop procedure if exists dynamiccolumns;
delimiter //
create procedure dynamiccolumns()
begin
declare v_count int default 1;
set #ps := 'select now()';
oneToTen: loop
if v_count = 10 then
leave oneToTen;
end if;
set #ps := concat(#ps, ", ");
set #ps := concat(#ps, v_count);
set v_count := v_count + 1;
end loop oneToTen;
set #ps := concat(#ps, " from dual");
prepare ps from #ps;
execute ps;
deallocate prepare ps;
end//
delimiter ;
And calling it
mysql> call dynamiccolumns;
+---------------------+---+---+---+---+---+---+---+---+---+
| now() | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
+---------------------+---+---+---+---+---+---+---+---+---+
| 2013-07-10 06:11:43 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
+---------------------+---+---+---+---+---+---+---+---+---+
1 row in set (0.00 sec)
Related
I have a table with matches information, and I need to return a row for each goal and each team. So for example:
+--------+-------+-------+-------+-------+
| Match | Team1 | goal1 | goal2 | Team2 |
+--------+-------+-------+-------+-------+
| 1 | Red | 1 | 0 | Blue |
+--------+-------+-------+-------+-------+
| 2 | Green | 2 | 1 | Black |
+--------+-------+-------+-------+-------+
I want to run a function for each row that returns a row for each goal for each team. So my function result would be:
+--------+-------+-------+
| Goal | Match | Team |
+--------+-------+-------+
| 1 | 1 | Red |
+--------+-------+-------+
| 2 | 2 | Green |
+--------+-------+-------+
| 3 | 2 | Green |
+--------+-------+-------+
| 4 | 2 | Black |
+--------+-------+-------+
My ultimate objective is that I need to have one row for each match/team/goal to fill in manually the Scorer and the minute. Since I hace over 40000 matches, copy pasting each row counting the amount of goals is a pain.
I would like to start with a goal table pre populated with as much information as I already have.
Create a table that contains numbers from 1 to the maximum number of possible goals, i.e.
CREATE TABLE numbers (
num INT PRIMARY KEY
);
INSERT INTO numbers VALUES (1), (2), (3), (4), (5), (6), ...
You can then join this table with your original table:
SELECT num AS Goal, `Match`, Team
FROM numbers
JOIN (
SELECT Team1 AS Team, goal1 AS goals, `Match`
FROM matches
UNION
SELECT Team2 AS Team, goal2 AS goals, `Match`
FROM matches
) ON num <= goals
While loop syntax example in MySQL:
delimiter //
CREATE procedure yourdatabase.while_example()
wholeblock:BEGIN
declare str VARCHAR(255) default '';
declare x INT default 0;
SET x = 1;
WHILE x <= 5 DO
SET str = CONCAT(str,x,',');
SET x = x + 1;
END WHILE;
select str;
END//
Which prints:
mysql> call while_example();
+------------+
| str |
+------------+
| 1,2,3,4,5, |
+------------+
FOR loop syntax example in MySQL:
delimiter //
CREATE procedure yourdatabase.for_loop_example()
wholeblock:BEGIN
DECLARE x INT;
DECLARE str VARCHAR(255);
SET x = -5;
SET str = '';
loop_label: LOOP
IF x > 0 THEN
LEAVE loop_label;
END IF;
SET str = CONCAT(str,x,',');
SET x = x + 1;
ITERATE loop_label;
END LOOP;
SELECT str;
END//
Which prints:
mysql> call for_loop_example();
+-------------------+
| str |
+-------------------+
| -5,-4,-3,-2,-1,0, |
+-------------------+
1 row in set (0.00 sec)
Tutorial: http://www.mysqltutorial.org/stored-procedures-loop.aspx
How do I substitute values in a SELECT with text matches to rows in another table?
**products**
> +-------+--------------------------------------------------+
> | id | description |
> +-------+--------------------------------------------------+
> | 10001 | This product is %block1% and %block4% |
> | 10002 | This product is %block2%, %block3%, and is %block4% |
> +-------+--------------------------------------------------+
**descriptions**
> +-----------+-------------------+
> | blockname | blockcontent |
> +-----------+-------------------+
> | %block1% | 5 feet tall |
> | %block2% | matte white |
> | %block3% | makes music |
> | %block4% | made of real wood |
> +-----------+-------------------+
Ideally, I'd like to run a single query that returns
> +-------+--------------------------------------------------+
> | id | newdescription |
> +-------+--------------------------------------------------+
> | 10001 | This product is 5 feet tall and made of real wood |
> | 10002 | This product is matte white, makes music, and is made of real wood |
> +-------+--------------------------------------------------+
I've investigated REPLACE() and SUBSTITUTE() but they don't appear to be what I am looking for.
You can create function:
DROP FUNCTION my_subst;
DELIMITER $$
CREATE FUNCTION my_subst(str VARCHAR(255))
RETURNS VARCHAR(255)
BEGIN
DECLARE pos1 INT DEFAULT 0;
DECLARE pos2 INT DEFAULT 0;
DECLARE token VARCHAR(255);
DECLARE new_token VARCHAR(255);
label1: LOOP
SET pos1 = LOCATE('%%',str);
IF pos1 = 0 THEN
LEAVE label1;
END IF;
SET pos2 = LOCATE('%%',str,pos1+1);
IF pos2 = 0 THEN
LEAVE label1;
END IF;
SET token = SUBSTR(str,pos1,pos2-pos1+2);
SELECT blockcontent INTO new_token FROM descriptions WHERE blockname = token;
SET str = REPLACE(str,token,new_token);
END LOOP label1;
RETURN str;
END;
$$
DELIMITER ;
and then just use this function to do substitutions.
I have this MySQL procedure where I used a cursor to:
CREATE DEFINER=`root`#`localhost` PROCEDURE `tax_to_salary`()
BEGIN
DECLARE basic_salary INTEGER;
DECLARE new_salary INTEGER;
DECLARE done INTEGER;
declare count INTEGER;
DECLARE counter INTEGER default 0;
DECLARE cur1 CURSOR FOR SELECT salary FROM employee;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
SELECT count(id) INTO count FROM employee;
SET #counter:=0;
OPEN cur1;
l1:LOOP
FETCH cur1 INTO basic_salary;
SET #counter:=#counter+1;
IF #counter>count THEN
leave l1;
end if;
IF basic_salary>2500 THEN
SET #new_salary := 500;
SET #basic_salary := #basic_salary - #new_salary;
else
SET #new_salary := 200;
SET #basic_salary := #basic_salary - #new_salary;
END IF;
SELECT emp_name, salary, basic_salary AS 'Salary after taxes' FROM employee;
END LOOP;
END
And I got this result:
But my procedure should remove 500 from all salaries over 2500 and remove 200 from salaries less than 2500.
I tried to put the final SELECT query inside the loop but I get 5 tabs and every tab contain the same of image below.
Schema
create table employee
( id int auto_increment primary key,
emp_name varchar(100) not null,
salary int not null
);
insert employee (emp_name,salary) values
('John',4400),
('Sarah',2700),
('Peter',2150),
('Ali',2650),
('Ashley',2650);
Note your language was greater than 2500 and also you said less than 2500. yet it has no condition for salary equaling 2500 exactly. So the below is one fix to that concept (otherwise there is no reduction).
Case when
best for many conditions, not that yours has it
select emp_name,salary,
CASE when salary>=2500 then salary-500
ELSE
salary-200
END as modified_salary
from employee;
+----------+--------+-----------------+
| emp_name | salary | modified_salary |
+----------+--------+-----------------+
| John | 4400 | 3900 |
| Sarah | 2700 | 2200 |
| Peter | 2150 | 1950 |
| Ali | 2650 | 2150 |
| Ashley | 2650 | 2150 |
+----------+--------+-----------------+
If
for simple conditions like yours
select emp_name,salary,
if(salary>=2500,salary-500,salary-200) as modified_salary
from employee;
+----------+--------+-----------------+
| emp_name | salary | modified_salary |
+----------+--------+-----------------+
| John | 4400 | 3900 |
| Sarah | 2700 | 2200 |
| Peter | 2150 | 1950 |
| Ali | 2650 | 2150 |
| Ashley | 2650 | 2150 |
+----------+--------+-----------------+
There is no reason to be using a row-by-row cursor the way you are. That is what people sometimes do just starting out with sql. Not only are they slow, often unbearably slow, but they keep you from harnessing the power of relations that make sql shine.
Said another way, you are trying to write procedural code and getting in the middle of it all by helping the sql engine figure it out with that mindset. It doesn't want it that way for optimization. You can, but you will slow it down horribly.
The reason you are getting multiple tabs as you say is that with your strategy, even if it worked well number-wise, each select statement returns a result set. And by going the dangerous cursor route, you returned five of them.
I have this data in my MySQL table fact
+------------+-------+--------+
| timestamp | code | unique |
+------------+-------+--------+
| 1416157200 | 7E001 | 100 |
| 1416157200 | 7E002 | 200 |
| 1416243600 | 7E001 | 100 |
| 1416243600 | 7E002 | 200 |
+------------+-------+--------+
I want to get this result
+-------+------------+------------+
| code | 2014-11-18 | 2014-11-17 |
+-------+------------+------------+
| 7E001 | 100 | 100 |
| 7E002 | 200 | 200 |
+-------+------------+------------+
I use this query select code, from_unixtime(timestamp, '%Y-%m-%d') as date, unique from fact; to produce this result and have no idea to aggregate this result became above desirable result.
+-------+------------+--------+
| code | date | unique |
+-------+------------+--------+
| 7E001 | 2014-11-17 | 100 |
| 7E002 | 2014-11-17 | 200 |
| 7E001 | 2014-11-18 | 100 |
| 7E002 | 2014-11-18 | 200 |
+-------+------------+--------+
Is it possible? And how to achieve that?
PS: Please help me editing the title to be more descriptive since I can't explain this problem in such short
Case based aggregation can be used
As the dates can be many, you need to use dynamic SQL
select code,
Max( case when date=1416157200 then unique end) as '2014-11-17' ,
Max( case when date=1416243600 t hen unique end) as '2014-11-18'
from fact
Group by date
Yes it is possible creating a dynamic query using that values as columns.
I use some code from article describing a way to get a pivot table in MySQL:
mysql> call get_fact_pivot_table();
+-------+------------+------------+
| code | 2014-11-16 | 2014-11-17 |
+-------+------------+------------+
| 7E001 | 100 | 100 |
| 7E002 | 200 | 200 |
+-------+------------+------------+
2 rows in set (0,06 sec)
The procedure get_fact_pivot_table contains a cursor to make a dynamic query, you can change it as you need:
DELIMITER $$
DROP PROCEDURE if exists get_fact_pivot_table$$
CREATE PROCEDURE `get_fact_pivot_table`()
BEGIN
DECLARE done INT DEFAULT 0;
DECLARE p_sql text;
DECLARE p_col_date VARCHAR(20);
DECLARE p_col_value int;
DECLARE c_columns cursor FOR
select distinct from_unixtime(timestamp, '%Y-%m-%d') as `col_date` ,
`timestamp` as col_value
from fact;
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1;
SET p_sql = 'select f.code ';
OPEN c_columns;
read_loop: LOOP
FETCH c_columns INTO p_col_date, p_col_value;
IF done THEN
LEAVE read_loop;
END IF;
SET p_sql = concat(p_sql,
', (select c.`unique` from fact as c where
c.`timestamp` = ', p_col_value ,'
and c.code = f.code limit 1) as `',p_col_date,'` ');
END LOOP;
SET #SQL = concat(p_sql,' from fact as f group by f.code');
close c_columns;
PREPARE stmt1 FROM #SQL;
EXECUTE stmt1;
DEALLOCATE PREPARE stmt1;
END$$
delimiter ;
I have one table that looks like this called survey_1:
================================================
|id | token | 1X2X1 | 1X2X2 |
=====+========+===============+================|
| 1 | 1 | YES | Justin Beiber |
|----+--------+---------------+----------------|
| 2 | 1 | YES | Britney Spears |
|----+--------+---------------+----------------|
note: 1X2X1 represents- survey-id X group-id X question-id
I have another table called survey_questions:
===============================================================
|sid | gid | qid | question |
=====+========+===============+===============================|
| 1 | 2 | 1 | Do you listen to music? |
|----+--------+-----------------------------------------------|
| 1 | 2 | 2 | Who is your favorite music artists? |
|----+--------+-----------------------------------------------|
The sid (survey-id), gid (group-id) and qid(question-id) define that specific question in this table
I need a query that will give me a result like this:
======================================================
| Question | Answer |
=========+===========+===============================|
| Do you listen to music? | YES |
|----------------------------------------------------|
| Who is your favorite music artists? | Justin Beiber|
|----------------------------------------------------|
NOTE: My database contains thousands of these columns, so it would be very time consuming to edit every survey to match up perfectly in this format.
Can anyone help out with this? Thank you
Can you change the table schema? Cause that first table, survey_1 is better written with one row per answer and with the entire key of the other table per row. Like this (add your own indexes)
create table survey_1 (
id int,
token int,
sid int,
gid int,
qid int,
answer varchar(255)
)
Than the data would be
------------------------------------------
| 1 | 1 | 1 | 2 | 1 | "YES" |
| 1 | 1 | 1 | 2 | 2 | "Justin Beiber" |
| 2 | 1 | 1 | 2 | 1 | "YES" |
| 2 | 1 | 1 | 2 | 2 | "Britney Spears" |
------------------------------------------
It's going to be much easier to work with and generally a better design.
Here is how it would look http://sqlfiddle.com/#!2/4f1ca/2
Create a view for each survey. For the old surveys a simple script should be able to do it, for new surveys make it a part of the process when creating new surveys. This is how the view could look for survey_1
create or replace view v_survey_1 as
select id, question, 1X2X1 as answer
from question
join survey_1 s
where sid = 1
and gid = 2
and qid = 1
union
select id, question, 1X2X2
from question
join survey_1 s
where sid = 1
and gid = 2
and qid = 2
;
http://sqlfiddle.com/#!2/63aee/1
To build the views a script would roughly do like this.
Find all tables to build views on by running
select table_name
from information_schema.tables
where table_schema = 'test'
and table_name like 'survey\_%';
For each view find the union parts by running this for its table
select column_name
from information_schema.columns
where table_name = 'survey_1'
and column_name regexp '^[0-9]+X[0-9]+X[0-9]+$';
Extract the number parts and use them when comparing with sid, gid and qid.
This script could also be used to populate new proper tables.
You need to use 'UNPIVOT', which MySQL unfortunately does not support. You can do a similar thing by hardcoding the column names (but you need to know all the columns in advance) like this:
SELECT survey_questions.Question,
CASE survey_questions.qid
WHEN 1 THEN survey_1.`1X2X1`
WHEN 2 THEN survey_1.`1X2X2`
WHEN 3 THEN survey_1.`1X2X3`
WHEN 4 THEN survey_1.`1X2X4`
// ...
END as Answer
FROM survey_questions
JOIN survey_1
ON survey_questions.qid = survey_1.id
AND survey_questions.gid = survey_1.token_id
WHERE survey_questions.sid = 1
Of course, you can always use some scripting language to generate the column names for you... For example, here is a stored procedure you can make:
CREATE PROCEDURE 'get_qa_for_survey'
(
IN surveyId INT
)
BEGIN
DECLARE query1 TEXT;
SET #tableName = 'survey_' + surveyId;
SET query1 = 'SELECT survey_questions.Question,
CASE survey_questions.qid ';
DECLARE col_names CURSOR FOR
SELECT column_name
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name = #tableName
AND (column_name LIKE surveyId +'X%');
ORDER BY ordinal_position;
select FOUND_ROWS() into num_rows;
SET i = 1;
the_loop: LOOP
IF i > num_rows THEN
CLOSE col_names;
LEAVE the_loop;
END IF;
FETCH col_names
INTO col_name;
SET query1 = query1 + ' WHEN ' + i + ' THEN ' + #tableName + '.' + col_name
SET i = i + 1;
END LOOP the_loop;
SET query1 = query1 + ' END as Answer
FROM survey_questions
JOIN ' + #tableName + '
ON survey_questions.qid = ' + #tableName + '.id
AND survey_questions.gid = ' + #tableName + '.token_id
WHERE survey_questions.sid = ' + surveyId;
SET #Sql = query1;
PREPARE STMT FROM #Sql;
EXECUTE STMT;
DEALLOCATE PREPARE STMT;
END