SQL Iterating Select Statement - sql

In my Database I have various Schemas & every Schema have a Table as [Company] & Other Tables.
I have written below Query which iterates all Schemas & in case i want to INSERT something in a Table for all Schemas I run this Query.
I am stuck in a Scenario where Insert Query requires Values from a [Company] Table.
Example - In 1 Schema I have [Company] Table & I have 4 Records in it.
So I want to INSERT 4 Records in [Menu] Table & Company Id will be picked from [Company] Table.
Right Now, In the below Query I am just Selecting Id from [Company] table.
I want to know How to iterate through the Records of Select Statement?
-- in-memory schema table to hold distinct schema_names
DECLARE #i int
DECLARE #numrows int
DECLARE #schema_names nvarchar(max)
DECLARE #schema_table TABLE (
idx smallint Primary Key IDENTITY(1,1)
, schema_names nvarchar(max)
)
DECLARE #company_table nvarchar(max)
DECLARE #sql nvarchar(max)
-- populate schema table
INSERT #schema_table
SELECT name FROM sys.schemas Where name <> 'dbo' AND name <> 'guest' AND name <> 'INFORMATION_SCHEMA' AND name <> 'db_accessadmin' AND name <> 'db_backupoperator' AND name <> 'db_datareader' AND name <> 'db_datawriter' AND name <> 'db_ddladmin' AND name <> 'db_denydatareader' AND name <> 'db_denydatawriter' AND name <> 'db_owner' AND name <> 'db_securityadmin' AND name <> 'sys'
select * from #schema_table
-- enumerate the table
SET #i = 1
SET #numrows = (SELECT COUNT(*) FROM #schema_table)
IF #numrows > 0
WHILE (#i <= (SELECT MAX(idx) FROM #schema_table))
BEGIN
-- get the next record primary key
SET #schema_names = (SELECT schema_names FROM #schema_table WHERE idx = #i)
SET #company_table = '['+#schema_names+'].[Company]'
SET #sql = 'select Id from ' + #company_table
EXEC(#sql)
BEGIN TRY
DECLARE #sSQL nvarchar(500);
SELECT #sSQL = N'INSERT ['+#schema_names+'].[Menu] VALUES (9, N''Dashboard'', N''Charts'', N''/Dash/Chart'', 1)'
EXEC sp_executesql #sSQL
END TRY
BEGIN CATCH
SELECT ERROR_MESSAGE()+' '+#schema_names AS ErrorMessage;
END CATCH
-- increment counter for next record
SET #i = #i + 1
END
In this Query - 9 will be replaced by Value from [Company] Table.

Just it simple for iterating in each row you can use the below Example
CREATE PROCEDURE cursor1()
BEGIN
DECLARE finished INTEGER DEFAULT 0;
DECLARE fname1 CHAR(20) DEFAULT "";
DECLARE lname1 CHAR(20) DEFAULT "";
DECLARE nameList CHAR(100) DEFAULT "";
-- 1. Declare cursor for employee
DECLARE emp_cursor CURSOR FOR SELECT fname, lname FROM employee WHERE salary > 40000;
-- 2. Declare NOT FOUND handler
DECLARE CONTINUE HANDLER FOR NOT FOUND SET finished = 1;
-- 3. Open the cursor
OPEN emp_cursor;
L: LOOP
-- 4. Fetch next tuple
FETCH emp_cursor INTO fname1, lname1;
-- Handler will set finished = 1 if cursor is empty
IF finished = 1 THEN
LEAVE L;
END IF;
-- build emp list
SET nameList = CONCAT( nameList, fname1, ' ', lname1, ';' );
END LOOP ;
-- 5. Close cursor when done
CLOSE emp_cursor;
SELECT nameList ;
END //
DELIMITER

Eg2.
DROP PROCEDURE IF EXISTS depreciation_calculator;
# depreciation calculator..........................................
CREATE PROCEDURE depreciation_calculator(IN deprcesionDate INT)
BEGIN
DECLARE acc DOUBLE;
DECLARE diff INT;
DECLARE currentDate DATE;
DECLARE depDate VARCHAR(12);
DECLARE dep DOUBLE;
DECLARE bookValue DOUBLE;
DECLARE assetId INT;
DECLARE depStatus VARCHAR(12);
DECLARE finished INTEGER DEFAULT 0;
DECLARE emp_cursor CURSOR FOR SELECT
dep_date,
dep_amount,
dep_status,
asset_ass_id,
book_value,
accumulative_value
FROM depreciation;
-- 2. Declare NOT FOUND handler
DECLARE CONTINUE HANDLER FOR NOT FOUND SET finished = 1;
-- 3. Open the cursor
OPEN emp_cursor;
L: LOOP
-- 4. Fetch next element
FETCH emp_cursor
INTO depDate, dep, depStatus, assetId, bookValue, acc;
-- Handler will set finished = 1 if cursor is empty
IF finished = 1
THEN
LEAVE L;
END IF;
SET currentDate = DATE(now());
SET diff := TIMESTAMPDIFF(MONTH, depDate, currentDate);
IF diff > 12 && diff <= 13 && bookValue > 0
THEN
SET depDate = currentDate;
SET dep = dep;
SET acc = acc + dep;
SET bookValue = bookValue - dep;
IF bookValue = 0
THEN
SET depStatus = 'depreciated';
END IF;
INSERT INTO depreciation (dep_date, dep_amount, dep_status, dep_description, dep_commnet, asset_ass_id, book_value, accumulative_value)
VALUES (depDate, dep, depStatus, 1, 1, assetId, bookValue, acc);
END IF;
END LOOP;
-- 5. Close cursor when done
CLOSE emp_cursor;
SELECT diff;
END;

Related

Stored Procedure - Table Name as variable

I am tasked with writing a stored procedure that will validate the input data against a few tables before inserting into a main table named CHANGES.
Here is what I am dealing with table wise:
There is a lookup table. This table basically gives the user the rules of data validation before it can be inserted into the MAIN table. The lookup table looks like this:
ID TABLECODE COLUMNAME ACCEPTEDDATATYPE
1 luDEPT DEPTCODE INT
2 luEMP GENDERCODE INT
3 luDEPT BLDGcode INT
So if a user is inserting an ID of 1, we know they are trying to make a correction to the DeptCode column and they must meet the requirements that only an Integer will be accepted before inserting into the CHANGES table (this is the main table that will hold the new values).
CHANGES table - Data is inserted into this table with the new value per column. Data will only be inserted into this table if it passes validation against the lookup table and explained in part 3.
Structure of CHANGES table
ID pkid NEWVALUE
1 67 01
1 84 09
2 56 03
This is the part I would like some help/input with to even see if it's doable. The column from the LOOKUP table name TABLECODE is the name of an actual table that exists in the database with codes and description for each column. So for example, all the DEPTCODE codes will be found in a lookup table named: luDEPT
Here is how the luDEPT that looks like this:
CODE DEPARTMENTNAME
01 BIOLOGY
02 CHEMISTRY
03 INFORMATION TECHNOLOGY
So another validation step I have to take is, make sure that the NEW VALUE being inserted into CHANGES table is a valid code found in the lookup table related to the COLUMNNAME.
This is what I have so far, which works
CREATE PROCEDURE [dbo].[NewValueData]
(
#ID int,
#pkid VARCHAR(40),
#value VARCHAR(50)
)
AS
Begin
declare #result bit = 0;
declare #result1 bit = 0;
declare #result2 bit = 0;
declare #result3 bit = 0;
declare #result4 bit = 0;
DECLARE #tablename varchar(50);
DECLARE #columndatatype varchar(30);
set #columndatatype=(select accepteddatatype from lookup where ID=#ID)
**set #tablename=(select TABLE_NAME from INFORMATION_SCHEMA.TABLES A, lookup b
where a.TABLE_NAME= b.lutablecode
and TABLE_SCHEMA = 'Test' and ID=#ID)**
--CHECK IF ID, pkid and VALUE are PROVIDED
if (#pkid IS NULL OR #pkid = '') or (#ID IS NULL OR #ID = '') or (#value IS NULL OR #value =
'')
begin
set #result = 1
PRINT 'PKID,ID or Value is missing'
end
--CHECK IF ID EXISTS IN LOOKUP TABLE
if #ID not in (select ID from lookup
where #ID=ID)
begin
set #result1=1
PRINT 'ID is not in lookup table'
end
--IF datatype is an integer, only accept a numeric value
if #columndatatype = 'INT'
begin
set #result3 = IIF(ISNUMERIC(#value)=1,0,1)
PRINT 'column type is INT '
end
**--ATTEMPT of trying to use #tablename
--CHECK IF VALUE IS AN ACCEPTED VALUE IN THE LOOKUP TABLE FOR THAT COLUMN
if #value not in (select code from #tablename where #value=code)
begin
set #result4=1
PRINT 'Not a valid code')
end**
if (#result = 0 and #result1 = 0 and #result2 = 0 and #result3 = 0 and #result4 = 0)
begin
BEGIN TRANSACTION;
begin try
INSERT INTO [CHANGES] (ID, pkid,newvalue) VALUES (#ID, #pkid, #value)
PRINT 'New Record Inserted'
COMMIT TRANSACTION
end TRY
begin catch
ROLLBACK TRANSACTION
PRINT 'id is not acceptable'
END
end
GO
The text in bold is my attempt at trying to derive the tablename dynamically but it doesn't work. Does anyone have suggestion on how to go about this issue? Any help will be welcomed.
Try something like:
DECLARE #tablename sysname = 'luDEPT'
DECLARE #columnname sysname = 'DEPTCODE'
DECLARE #value INT = 123
DECLARE #valid BIT
DECLARE #sql NVARCHAR(MAX) = '
SET #Valid = CASE
WHEN EXISTS(SELECT * FROM ' + QUOTENAME(#tablename) + ' WHERE ' + QUOTENAME(#columnname) + ' = #Value)
THEN 1
ELSE 0
END'
EXECUTE sp_executesql
#sql,
N'#value INT, #valid BIT OUTPUT',
#value = #value, #valid = #valid OUTPUT
SELECT Valid = #Valid
The data type could potentially also be parameterized if types other than INT are needed.

reverse where query in mysql2

I want to find records using like query but in reverse mode
For exa: I have one string ts5e434
And now in databse I have one column called geohash and its contan comma seperated values
1) "ts5e4,ts5,ts5e434"
2) "ab,ye"
3) "ts,thh"
4) "t"
So here I want to get 1, 3 and 4 no records because its partially matching string
exa like clause
SELECT
*
FROM
service_geohashes
WHERE
'ts5e434' LIKE geohashes
Can anyone help me
Thanks in advance
I created function "LikeAny" in MSSQL which looks like:
CREATE FUNCTION [dbo].[LikeAny](#text nvarchar(MAX), #delimiter varchar(20), #comparestring nvarchar(MAX))
RETURNS BIT AS
BEGIN
DECLARE #LikeAny BIT = 0,
#TempString nvarchar(MAX)
DECLARE MY_CURSOR CURSOR
LOCAL STATIC READ_ONLY FORWARD_ONLY
FOR
SELECT value FROM STRING_SPLIT(#text, #delimiter)
OPEN MY_CURSOR
FETCH NEXT FROM MY_CURSOR INTO #TempString
WHILE ##FETCH_STATUS = 0
BEGIN
--Do something with Id here
IF (#TempString <> '' AND #comparestring LIKE N'%' + #TempString + '%')
BEGIN
SET #LikeAny = 1
BREAK;
END
ELSE
FETCH NEXT FROM MY_CURSOR INTO #TempString
END
CLOSE MY_CURSOR
DEALLOCATE MY_CURSOR
RETURN #LikeAny
END
If you use this in your example, it should look like:
SELECT
*
FROM
service_geohashes
WHERE
[dbo].[LikeAny](geohashes ,',', 'ts5e434') = 1
I tried also to convert the function above into MySQL but I had no option to test it on real environment
it looks like:
DROP FUNCTION IF EXISTS LikeAnyCommaDelimited;
DELIMITER $$
CREATE FUNCTION LikeAnyCommaDelimited(p_text longtext, p_comparestring longtext)
RETURNS TINYINT
DETERMINISTIC
BEGIN
DECLARE v_finished INTEGER DEFAULT 0;
DECLARE v_LikeAny TINYINT DEFAULT 0;
DECLARE v_TempString longtext;
DECLARE v_SQL longtext;
drop temporary table if exists tempa;
drop temporary table if exists tempb;
CREATE TEMPORARY TABLE tempa( txt text );
CREATE TEMPORARY TABLE tempb( val char(255) );
insert into tempa values(p_text);
set v_SQL = concat("insert into tempb (val) values ('", replace(( select group_concat(distinct txt) as data from tempa), ',', "'),('"),"');");
prepare statement1 from #sql;
execute statement1;
DEClARE split_cursor CURSOR FOR
SELECT value FROM (select distinct(val) as value from tempb);
DECLARE CONTINUE HANDLER
FOR NOT FOUND SET v_finished = 1;
OPEN split_cursor;
get_string: LOOP
FETCH split_cursor INTO v_TempString;
IF v_finished = 1 THEN
LEAVE get_string;
END IF;
IF (v_TempString <> '' AND p_comparestring LIKE N'%' + CONCAT(v_TempString , '%') THEN
BEGIN
SET v_LikeAny = 1;
LEAVE get_string;
END
END LOOP get_string;
CLOSE split_cursor;
END$$
DELIMITER ;
Let me know if you have any issues.

Adding value plus 1 each time as an update

In Advantage trying to update a value in a temp table, but adding 1 each time. The starting value needs to be one more than the max value from another table, and each time a new row is updated, one is added to that. Ignore all but the declare cursor and #nevid strings at the top. I can get it to populate the evid column in my temp table, but it adds 71 to each record, which is the correct next number, but I need it to be 71,72,73 etc. Where am I going wrong?
DECLARE cur CURSOR;
DECLARE #nevid INTEGER;
DECLARE #startdate string;
DECLARE #starttime string;
DECLARE #expectedenddate string;
DECLARE #expectedendtime string;
DECLARE #enddate string;
DECLARE #endtime string;
#nevid =
(
SELECT (max(evid)+1)
FROM pcplevnt
);
SELECT *
INTO #tmpev
FROM <table>;open cur
AS
SELECT *
FROM #tmpev;
WHILE
FETCH cur do
UPDATE #tmpev
SET evid = cast(#nevid AS sql_char(4));SET #nevid = #nevid + 1;
END WHILE;
close cur;
I have build a MVCE to reproduce and fix your problem:
DECLARE cur CURSOR;
DECLARE #nevid INTEGER;
TRY DROP TABLE #pcplevnt; CATCH ALL END TRY;
TRY DROP TABLE #tmpev; CATCH ALL END TRY;
CREATE TABLE
#pcplevnt
(
evid INTEGER
);
DELETE FROM #pcplevnt;
INSERT INTO #pcplevnt (evid) SELECT 111 FROM system.iota;
SET #nevid = (
SELECT
max(evid) + 1
FROM #pcplevnt
);
CREATE TABLE
#tmpev
(
id AUTOINC,
evid NVARCHAR(4)
);
INSERT INTO
#tmpev
(
evid
)
SELECT '1' FROM system.iota
UNION SELECT '2' FROM system.iota
UNION SELECT '3' FROM system.iota
;
OPEN cur AS SELECT * FROM #tmpev;
WHILE FETCH cur DO
UPDATE
#tmpev
SET
evid = CAST(#nevid AS SQL_CHAR(4))
WHERE
id = cur.id
;
SET #nevid = #nevid + 1;
END WHILE;
CLOSE cur;
select * from #tmpev;
Pay attention to the WHERE condition in the UPDATE inside of the loop:
UPDATE
#tmpev
SET
evid = CAST(#nevid AS SQL_CHAR(4))
WHERE
id = cur.id
;
SET #nevid = #nevid + 1;
I have added a primary key field id that I can compare against inside the loop in order to only update one row of the temp table at once.

SQL Dynamic Schema Name in Cursor

I want to use Cursor with dynamic SchemaName.
In my below Code SELECT id FROM #schema_names.company line is my issue. #schema_names is dynamic Schema Name.
How to use Dynamic Schema/Table Name in Cursor?
-- in-memory schema table to hold distinct schema_names
DECLARE #i int
DECLARE #numrows int
DECLARE #schema_names nvarchar(max)
DECLARE #schema_table TABLE (
idx smallint Primary Key IDENTITY(1,1)
, schema_names nvarchar(max)
)
DECLARE #company_id NVARCHAR(max)
-- populate schema table
INSERT #schema_table
SELECT name FROM sys.schemas Where name <> 'dbo' AND name <> 'guest' AND name <> 'INFORMATION_SCHEMA' AND name <> 'db_accessadmin' AND name <> 'db_backupoperator' AND name <> 'db_datareader' AND name <> 'db_datawriter' AND name <> 'db_ddladmin' AND name <> 'db_denydatareader' AND name <> 'db_denydatawriter' AND name <> 'db_owner' AND name <> 'db_securityadmin' AND name <> 'sys'
select * from #schema_table
-- enumerate the table
SET #i = 1
SET #numrows = (SELECT COUNT(*) FROM #schema_table)
IF #numrows > 0
WHILE (#i <= (SELECT MAX(idx) FROM #schema_table))
BEGIN
-- get the next record primary key
SET #schema_names = (SELECT schema_names FROM #schema_table WHERE idx = #i)
DECLARE my_cursor CURSOR local static read_only forward_only FOR
SELECT id FROM #schema_names.company
OPEN my_cursor
FETCH next FROM my_cursor INTO #company_id
WHILE ##FETCH_STATUS = 0
BEGIN
--Do something with Id here
PRINT #company_id + 'a'
FETCH next FROM my_cursor INTO #company_id
END
CLOSE my_cursor
DEALLOCATE my_cursor
BEGIN TRY
DECLARE #sSQL nvarchar(500);
SELECT #sSQL = N'INSERT ['+#schema_names+'].[Menu] VALUES (9, N''Dashboard'', N''Charts'', N''/Dash/Chart'', 1)'
EXEC sp_executesql #sSQL
END TRY
BEGIN CATCH
SELECT ERROR_MESSAGE()+' '+#schema_names AS ErrorMessage;
END CATCH
-- increment counter for next record
SET #i = #i + 1
END
try This approach
1 - Create a Temp Table
2 - Insert the Id from your Table to the Temp table using Dynamic SQL
3 - Fetch Cursor from Temp Table
CREATE TABLE #Temp
(
Id INT
)
SET #schema_names =
(
SELECT
schema_names
FROM #schema_table
WHERE idx = #i
)
TRUNCATE TABLE #temp
INSERT INTO #temp(Id)
EXEC('SELECT id FROM '+#schema_names+'.company;')
DECLARE my_cursor CURSOR LOCAL STATIC READ_ONLY FORWARD_ONLY
FOR
SELECT ID FROM #TEMP
OPEN my_cursor;
FETCH NEXT FROM my_cursor INTO #company_id;

procedure that returns varchar

I tried to make a function that returns varchar, but I can't because I'm using CREATE TABLE inside, and when I'm creating it with a procedure I can't return a value.
I wanted to know if you have some advice.
I made this just to make a string with emails separated by ";" so I can have all the "manager" mails in one varchar (for the recipients).
ALTER procedure [dbo].[Manager_email]
AS
BEGIN
declare #mails varchar (max),
#number_of_mails int,
#counter int
set #counter=2
create table #temp ( id int identity, email varchar(30))
insert into #temp (email)
select Email
from hr.Employees
where lower (EmpRole) like 'manager'
set #number_of_mails=##ROWCOUNT
set #mails = (select email from #temp where id =1 ) + ';'
while #counter <= #number_of_mails
BEGIN
set #mails = #mails + (select email from #temp where id =#counter ) + ';'
set #counter = #counter+1
END
drop table #temp
return cast (#mails as varchar (200))
END
You can only return integer value back from the procedure, If you want to return varchar value from procedure its good to make use of output variable in procedure.
Example
CREATE PROCEDURE Sales.uspGetEmployeeSalesYTD
#SalesPerson nvarchar(50),
#SalesYTD money OUTPUT
AS
SET NOCOUNT ON;
SELECT #SalesYTD = SalesYTD
FROM Sales.SalesPerson AS sp
JOIN HumanResources.vEmployee AS e ON e.BusinessEntityID = sp.BusinessEntityID
WHERE LastName = #SalesPerson;
RETURN
like in above procedure return #SalesYTD from procedure.
you can check full post on MSDN : Returning Data by Using OUTPUT Parameters
You can use function instead
CREATE FUNCTION Manager_email ()
RETURNS varchar(max)
AS
BEGIN
declare #email varchar(30)
declare #emails varchar(max)
set #emails = ''
declare cur cursor for
select Email
from hr.Employees
where lower (EmpRole) like 'manager'
open cur
fetch next from cur into #email
while ##fetch_status = 0
begin
set #emails = #emails + #email + ';'
fetch next from cur into #email
end
close cur
deallocate cur
return #emails
END
You can use table variable instead of temporary table. In that case you can continue to use UDF.