I have a little exercise where I need to access data from a view and print it out in a report. I have created a #temporary table to store the data in and the to retrieve it and display it in the report by using a while loop.
Problem is the temporary table seems to go 'missing'.
--Creating my report
USE PetShopDataBase
CREATE PROCEDURE spPetShopReport
#customerID INT
SELECT *
INTO #temporary
FROM vwPetshop
WHERE customerID = #customerID
GO
ALTER TABLE #temporary
ADD Printed SMALLINT
GO
Then from this point the object is regarded as invalid
UPDATE #temporary
SET Printed = 0
GO
the error message I get when I run the code is
Msg 4902, Level 16, State 1, Line 2
Cannot find the object "#temporary" because it does not exist or you do not have
permissions.
Why is that?
Kind regdards
Do not use GO inside of a stored proc. Go ends the batch and thus the stored proc.
BTW way all of this code can be compressed into one statement
SELECT * INTO #temporary
FROM vwPetshop
WHERE customerID = #customerID
ALTER TABLE #temporary
ADD Printed SMALLINT
UPDATE #temporary
SET Printed = 0
try this instead:
SELECT *, CAST(0 AS SMALLINT) AS Printed
INTO #temporary
FROM vwPetshop
WHERE customerID = #customerID
You can use global temporary table in this case (just two ## instead of one #)..
SELECT *
INTO ##temporary
FROM vwPetshop
WHERE customerID = #customerID
Local temporary tables are not visible outside procedure in which it was created..
Related
I have a Stored Procedure in mySQL that takes a subset of data from a table and performs some analysis on that subset within a temp table. Here is my code:
CREATE PROCEDURE GetPortfolioStats
(
InIdx_i INT,
InStart_i INT,
InEnd_i INT
)
BEGIN
DECLARE myLimit INT;
DECLARE myOffset INT;
SET myLimit = InEnd_i - InStart_i + 1;
SET myOffset = InStart_i - 1;
CREATE TEMPORARY TABLE IF NOT EXISTS myTmpTable AS (SELECT * FROM Leases WHERE Portfolio_i = InIdx_i ORDER BY Index_i LIMIT myLimit OFFSET myOffset);
SET #Additive_i := (SELECT COUNT(Index_i) FROM myTmpTable WHERE ReviewType_vc = 'Additive');
DROP TABLE myTmpTable;
SELECT #Additive_i;
END; GO
This works fine. However, the problem I have is that this is a multi-threaded application and when multiple threads are calling this stored proc, they start sharing the same temp table, which messes up the Stats I'm trying to compile.
Is there a way to either apply a unique table name to each call of the stored proc or limit the scope of the temp table to just that instance of the stored proc?
To answer the specific question: the easiest solution would be to use a different database connection per thread because temporary tables are session (connection) specific:
You can use the TEMPORARY keyword when creating a table. A TEMPORARY table is visible only to the current session, and is dropped automatically when the session is closed. This means that two different sessions can use the same temporary table name without conflicting with each other or with an existing non-TEMPORARY table of the same name.
However, after checking out the actual code, I would suggest not to use a temporary table at all, use a single query with a subquery:
SELECT COUNT(Index_i)
FROM
(SELECT Index_i, ReviewType_vc
FROM Leases
WHERE Portfolio_i = InIdx_i
ORDER BY Index_i
LIMIT myLimit OFFSET myOffset) t
WHERE ReviewType_vc = 'Additive'
I have a stored procedure, internally I want to call another procedure that returns a record set, how do I get an navigate the record set returned by the stored procedure via the 'CALL' ?
[edit] I've been trying to use a TEMPORARY TABLE as suggested, but having problems:
DROP TEMPORARY TABLE IF EXISTS tbl_HeadOfDepts;
CREATE TEMPORARY TABLE tbl_HeadOfDepts (biDept_id tinyint(4))
INSERT INTO tbl_HeadOfDepts CALL rsHeadOfAnyDepartments(vcCompKey, biWho_id);
I need to use CALL because 'rsHeadOfAnyDepartments' is not a function, but this will not be accepted.
Work in progress, but what I have so far that is not accepted by editor:
BEGIN
#--
# Procedure:
# rsWhoCanIaccess
#
# Parameters:
# vcCompKey, the key corresponding to the company
# biWho_id, the id of the person to check access for
#
# Returns:
# recordset containing all the people this person can access
#--
DECLARE tiSuperUser tinyint(4);
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
GET DIAGNOSTICS CONDITION 1 #sqlstate = RETURNED_SQLSTATE,
#errno = MYSQL_ERRNO, #text = MESSAGE_TEXT;
CALL procLogError(vcCompKey, CONCAT("rsWhoCanIaccess: "
,#errno, " (", #sqlstate, "): ", #text));
END;
#Is this user a super user?
SELECT tiIsSuperUser(vcCompKey, biWho_id) INTO tiSuperUser;
SET tiSuperUser = 0;#Hack for testing
IF (tiSuperUser = 1) THEN
#The user is a superuser, return everyone in the company
SELECT
t1.biPerson_id
FROM
tbl_people t1
INNER JOIN
tbl_companies t2
ON
t1.biCompany_id=t2.biCompany_id
AND
t2.vcKey=vcCompKey;
ELSE
#User is not a superuser, is the user head of any departments?
DROP TEMPORARY TABLE IF EXISTS tbl_HeadOfDepts;
CREATE TEMPORARY TABLE tbl_HeadOfDepts (biDept_id tinyint(4))
INSERT INTO tbl_HeadOfDepts CALL rsHeadOfAnyDepartments(vcCompKey, biWho_id);
SELECT * FROM tbl_HeadOfDepts;
END IF;
END
No, stored procedures can produce result sets, but not consume them directly as output from inner calls to other stored procedures. The best that you can do performance-wise is to populate a non-temporary work table and use the results.
Depending on your software and the reality of multiple callers concurrently, you might need to include a session id concept with an auto_increment (AI) column in some control table. This would ensure that with concurrency, multiple callers are not stomping on each other's rows, thus making it non-viable.
How that session would work, at a high level, is the following. The inner stored proc would be handed an AI value (theSession) from the control table, use it to populate a safely segmented session in the work table, and return as an out parameter to the outer (calling) stored proc. That outer one could then safely use those rows, and clean up at the end (delete from workTable where sessionId=theSession).
Why do I suggest a non-temporary work table? To be clear, the work table would be non-temporary. First of all there is the hassle of getting the if exists drop to work. Most importantly, though, it is about performance. DDL calls for temporary table creation are not inexpensive. You will only believe this when you do performance testing to see what I mean. It may seem trivial, but in trivial operations, those DDL calls for creation could very well account for the lion share of the time necessary for the inner stored proc to complete.
internally I want to call another procedure that returns a record set,
In your inner procedure create a TEMPORARY TABLE and populate that temp table saying insert into your_temp_table select query. then you can use that same temp table in your outer query anywhere.
It can even be a normal table as well and need not be temporary table. Also make sure to DROP the table once your procedure computation done as clean-up.
That's wrong per your comment. You should do it like below (a sample code)
create procedure rsHeadOfAnyDepartments(vcCompKey varchar(10), biWho_id int)
as
begin
DROP TEMPORARY TABLE IF EXISTS tbl_HeadOfDepts;
CREATE TEMPORARY TABLE tbl_HeadOfDepts(col1 int, col2 varchar(10), col3 varchar(30));
INSERT INTO tbl_HeadOfDepts
SELECT col1, col2, col3
FROM tblTest;
end
Is it possible that each time a table has a record inserted, delete a view, then re-create a view using the data in the table. For example, my table structure is like such
Create Table NeedView (
Databasename varchar(100)
,DateAdded datetime
)
And the information in that table is just a fully qualified database name like so servername.database.dbo.table My thought is to have a trigger applied that each time a record is inserted into this table, 1st drop the view (it would be called dbo.ViewMaster then recreate it with a quick select of
Select fullname, phone
from each table in the database. Is this achievable through a trigger?
EDIT
I now just need to figure out how to cycle the table to create a view for each databasename in the table...I have this rough syntax ready, with the exception of actually iterating each database. Can someone help me with the missing piece?
CREATE TRIGGER GetViewReady ON [FullOn]
FOR INSERT
AS
IF OBJECT_ID('FullInformation', 'V') IS NOT NULL
DROP VIEW FullInformation
Declare #database varchar(100), #campaignID varchar(10)
Declare C1 Cursor FOR
Select database
FROM [FullOn]
Open C1
Fetch Next FROM C1 INTO #database
WHILE ##Fetch_Status = 0
Begin
'Here is where the actual iteration to create the view would go...
'How would that statement actually need to be syntactically written?
Fetch Next From C1 Into #database
End
GO
EDIT --
A few database names would be hellfire.master.dbo.hennigar, hellfire.mainframe.dbo.dekalb, hellfire.master.dbo.ftworth and the syntax I am after would be like so:
Select fullname, phone
FROM hellfire.master.dbo.hennigar
UNION ALL
select fullname, phone
from hellfire.mainframe.dbo.dekalb
UNION ALL
select fullname, phone
from hellfire.master.dbo.ftworth
Background - I have a DB created from a single large flat file. Instead of creating a single large table with 106 columns. I created a "columns" table which stores the column names and the id of the table that holds that data, plus 106 other tables to store the data for each column. Since not all the records have data in all columns, I thought this might be a more efficient way to load the data (maybe a bad idea).
The difficulty with this was rebuilding a single record from this structure. To facilitate this I created the following procedure:
DROP PROCEDURE IF EXISTS `col_val`;
delimiter $$
CREATE PROCEDURE `col_val`(IN id INT)
BEGIN
DROP TEMPORARY TABLE IF EXISTS tmp_record;
CREATE TEMPORARY TABLE tmp_record (id INT(11), val varchar(100)) ENGINE=MEMORY;
SET #ctr = 1;
SET #valsql = '';
WHILE (#ctr < 107) DO
SET #valsql = CONCAT('INSERT INTO tmp_record SELECT ',#ctr,', value FROM col',#ctr,' WHERE recordID = ',#id,';');
PREPARE s1 FROM #valsql;
EXECUTE s1;
DEALLOCATE PREPARE s1;
SET #ctr = #ctr+1;
END WHILE;
END$$
DELIMITER ;
Then I use the following SQL where the stored procedure parameter is the id of the record I want.
CALL col_val(10);
SELECT c.`name`, t.`val`
FROM `columns` c INNER JOIN tmp_record t ON c.ID = t.id
Problem - The first time I run this it works great. However, each subsequent run returns the exact same record even though the parameter is changed. How does this persist even when the stored procedure should be dropping and re-creating the temp table?
I might be re-thinking the whole design and going back to a single table, but the problem illustrates something I would like to understand.
Unsure if it matters but I'm running MySQL 5.6 (64 bit) on Windows 7 and executing the SQL via MySQL Workbench v5.2.47 CE.
Thanks,
In MySQL stored procedures, don't put an # symbol in front of local variables (input parameters or locally declared variables). The #id you used refers to a user variable, which is kind of like a global variable for the session you're invoking the procedure from.
In other words, #id is a different variable from id.
That's the explanation of the immediate problem you're having. However, I would not design the tables as you have done.
Since not all the records have data in all columns, I thought this might be a more efficient way to load the data
I recommend using a conventional single table, and use NULL to signify missing data.
This may or may not be a simple question.
I am looking for a way to NOT select anything from a table, but return a set of table-like data.
Basically I have a procedure that loops through a table and stores data into a variable, then displays it by the line:
SELECT #args as parents;
Is there any better way to set up a table data structure and return that whole thing? Right now I am returning a single variable with multiple (parse-needed) data.
EDIT:
To try and explain better:
What I have to do is loop through and perform a series of select statements, is there a way to 'concat' this data into a big datatable structure and then return that table?
Use a temp table
DROP TABLE IF EXISTS tmp;
CREATE TABLE tmp( --fields-- );
WHILE ( --condition-- ) DO
INSERT INTO tmp VALUES ( --fields-- );
SELECT --Statement--
END WHILE;
--do stuff--
and clean up
DROP TABLE tmp
I'm not positive for mySQL, but you should be able to use a function to return a table. I found the following on another question, which might be helpful:
CREATE FUNCTION getdepartments()
RETURNS #departments TABLE(
DNAME VARCHAR(25),
DEPTID VARCHAR(10),
DBONUS DECIMAL(7,2))
AS
BEGIN
INSERT #departments SELECT * FROM DEPARTMENT;
RETURN
END
I've done this in SQL Server, and you can do fun things like recursive functions that keep adding records to your output table, etc.
Maybe you could return a recordset like described here http://forums.mysql.com/read.php?102,50520,50626#msg-50626. Hope that this will help.