How to handle an array in a stored procedure on DB2? - sql

I'm developing an app which store data in DB2, and I should be able to 'delete' data in bulk in a table of a DB. Actually the way to 'delete' the data is by changing its 'deleted' value to 'Y'.
The form of the table is:
id | name | deleted |
1 | name1 | N |
2 | name2 | N |
...
x | namex | N |
What I want to do is to make a SQL stored procedure which take as a parameter one array with the IDs of the items I need to change from 'N' to 'Y'.
The way I do it (individually) is:
UPDATE MyTable DELETED = 'Y' where id = '1';
So with an stored procedure I should only send the array with this form:
[1, 20, 5, ... , x]
and the rows with those Id should be changed to Y.
The structure for the stored procedure I was thinking about is:
PROCEDURE deleteSeveral (arrayWithIds)
LANGUAGE SQL
BEGIN
-- loop for ids array
UPDATE MyTable DELETED = 'Y' where id = arrayWithIds[i];
-- Ciclo para recorrer el arreglo
END
Could anybody help me with this? Thanks!

Try to pass the list of IDs as an "xml-like" string:
UPDATE MyTable t
SET DELETED = 'Y'
where exists (
select 1
from xmltable (
'$D/d/i' passing xmlparse(document '<d><i>1</i><i>20</i><i>5</i></d>') as "D"
columns
i int path '.'
) p
where p.i=t.id
)

Related

SQL calculated field based of data in another table

I have two tables, Engineering table and Instrumentation table. In the Engineering table I have the columns and possible data below:
Tag | Speed Control
PC-1234 |
ME-1235 |
BF-1236 |
In the Instrumentation Table I have the following columns and data
Function | Tag
SC | 1234
SC | 1235
SC | 1237
I want to automate the Speed Control column in the Engineering table to say Yes or No IF there is a line of data in the Instrumentation table with the function as SC and the Tag column have matching data with the number part of the Tag column in the Engineering table. So the results would like like the below:
Tag | Speed Control
PC-1234 | Yes
ME-1235 | Yes
BF-1236 | No
Please help with the best way to do this. Thanks in advance for any help.
You don't want a separate column in the Engineering table for this. You just need a view which you can query
CREATE VIEW EngineeringSpeedControl
AS
SELECT
e.Tag,
SpeedControl = CASE WHEN i.Tag IS NULL THEN 'No' ELSE 'Yes' END
FROM dbo.Engineering e
LEFT JOIN dbo.Instrumentation i
ON i.Tag = RIGHT(e.Tag, LEN(e.Tag) - 3)
AND i.[Function] = 'SC';
Unfortunately, due to the poor design of the tables, you need to muck around with string manipulation.
Ideally you would have the Engineering.Tag column split into separate parts, so you could just do a straight join
LEFT JOIN dbo.Instrumentation i
ON i.Tag = e.Tag
AND i.[Function] = 'SC';
as i don't know if the Tag column in engineers is always of the same format, i keep my query so that it can have similar design xxxxx-nnnnnnnnn with a minus between.
UPDATE [dbo].[Engineering]
SET [Speed Control] =
CASE WHEN EXISTS ( SELECT 1 FROM [dbo].[Instrumentation] i WHERE RIGHT([dbo].[Engineering] .[tag],CHARINDEX('-', (REVERSE([dbo].[Engineering] .[tag]))) - 1) = CAST(i.[Tag] AS VARCHAR(10))) then 'YES' ELSE 'NO' END
WHERE [Speed Control] IS NULL
result will be
Create a function and use a function in calculated columns
create table instrumentation([Function] varchar(200) null, Tag varchar(200) null)
insert into instrumentation values('SC', '1234'),('SC', '1235'),('SC', '1237')
create Function fn_Speed (#tag varchar(200))
returns varchar(200)
as
begin
declare #tagg varchar(200)= (select SUBSTRING(#tag, charindex('-', #tag)+1,10))
declare #result varchar(200)
--return #tagg
If exists (
select 1 from instrumentation where tag =#tagg)
select #result= 'True'
else
select #result= 'False'
return #result
end
Create table engineering (tag varchar(200), Speed as dbo.fn_Speed (tag) )
insert into engineering(tag)values('PC-1234'), ('ME-1235'), ('BF-1236')

How to create trigger that counts sum of column and inserts it into another one

I want to put sum of one column into column from another table. Everything for each ID. At start my columns are NULL, so I want to update them with this value.
Untill now I've tried to count SUM and it works, of course, but when I'm trying to write more code, it fails everytime. More. It's not that I can't code that if I want to. I just can't find logic that would make me feel like "Oh, I should go that way". This is my first trigger and it's really hard for me. Sadly all answers on StackOverflow looks much harder than my own.
UPDATE: Just found out there are built in fuctions. Is there any that makes things like that possible?
DROP TABLE IF EXISTS daily;
CREATE TABLE daily (
idd SERIAL PRIMARY KEY,
p_id INTEGER NOT NULL,
dailyDate DATE NOT NULL,
dailyKcal INTEGER DEFAULT NULL,
burnedKcal INTEGER DEFAULT NULL,
FOREIGN KEY(p_id) REFERENCES proteges(idp)
);
DROP TABLE IF EXISTS exercises;
CREATE TABLE exercises (
d_did INTEGER NOT NULL,
exerciseName VARCHAR(30) NOT NULL,
startAt TIME NOT NULL,
endAt TIME NOT NULL,
kcalPerHour INTEGER NOT NULL,
FOREIGN KEY (d_did) REFERENCES daily(idd)
);
--- FAIL ---
CREATE TRIGGER update_daily_exercises
AFTER INSERT ON exercises
UPDATE daily
SET burnedkcal = (SELECT sum(kcalperhour) FROM exercises GROUP BY d_did)
WHERE idd = d_did
What do I expect?
Let's say there is something like that:
d_did | ... | kcalperhour
1 | ... | 492
2 | ... | 321
1 | ... | 346
idd | ... | burnedkcal
1 | ... | NULL
2 | ... | NULL
And after POST I want it to be like:
idd | ... | burnedkcal
1 | ... | 838
2 | ... | 321
Materializing values that can be calculated from other values is dangerous, it can quickly lead to inconsistencies. You should rather drop the column storing the sum. If you need it, query it from the existing values. For convenience you can create a view.
ALTER TABLE daily
DROP burnedkcal;
CREATE VIEW daily_with_burnedcal
AS
SELECT d.idd,
d.p_id,
d.dailydate,
d.dailykcal,
coalesce(sum(e.kcalperhour), 0) burnedkcal
FROM daily d
LEFT JOIN exercises e
ON e.d_did = d.idd
GROUP BY d.idd,
d.p_id,
d.dailydate,
d.dailykcal;
In case someone has same problem. I'm not even sure if it's answer on my question, but I know that that's what I have done.
CREATE OR REPLACE FUNCTION public.sum_exercises() RETURNS TRIGGER AS
$update_daily_exercises$
DECLARE
total INTEGER;
r daily%rowtype;
BEGIN
---SELECT sum(kcalPerHour) FROM exercises JOIN daily ON idd = d_did WHERE d_did = x INTO total;---
FOR r IN
SELECT * FROM daily ORDER BY idd DESC
LOOP
SELECT sum(kcalPerHour) FROM exercises JOIN daily ON idd = d_did WHERE d_did = r.idd INTO total;
UPDATE daily SET burnedKcal = total WHERE idd = r.idd;
END LOOP;
RETURN NEW;
END;
$update_daily_exercises$
LANGUAGE PLPGSQL;
CREATE TRIGGER update_daily_exercises
AFTER INSERT ON exercises
FOR EACH ROW
EXECUTE PROCEDURE sum_exercises();

How does one automatically insert the results of several function calls into a table?

Wasn't sure how to title the question but hopefully this makes sense :)
I have a table (OldTable) with an index and a column of comma separated lists. I'm trying to split the strings in the list column and create a new table with the indexes coupled with each of the sub strings of the string it was connected to in the old table.
Example:
OldTable
index | list
1 | 'a,b,c'
2 | 'd,e,f'
NewTable
index | letter
1 | 'a'
1 | 'b'
1 | 'c'
2 | 'd'
2 | 'e'
2 | 'f'
I have created a function that will split the string and return each sub string as a record in a 1 column table as so:
SELECT * FROM Split('a,b,c', ',', 1)
Which will result in:
Result
index | string
1 | 'a'
1 | 'b'
1 | 'c'
I was hoping that I could use this function as so:
SELECT * FROM Split((SELECT * FROM OldTable), ',')
And then use the id and string columns from OldTable in my function (by re-writing it slightly) to create NewTable. But I as far as I understand sending tables into the function doesn't work as I get: "Subquery returned more than 1 value. ... not premitted ... when the subquery is used as an expression."
One solution I was thinking of would be to run the function, as is, on all the rows of OldTable and insert the result of each call into NewTable. But I'm not sure how to iterate each row without a function. And I can't send tables into the a function to iterate so I'm back at square one.
I could do it manually but OldTable contains a few records (1000 or so) so it seems like automation would be preferable.
Is there a way to either:
Iterate over OldTable row by row, run the row through Split(), add the result to NewTable for all rows in OldTable. Either by a function or through regular sql-transactions
Re-write Split() to take a table variable after all
Get rid of the function altogether and just do it in sql transactions?
I'd prefer to not use procedures (don't know if there is a solutions with them either) mostly because I don't want the functionality inside of the DB to be exposed to the outside. If, however that is the "best"/only way to go I'll have to consider it. I'm quite (read very) new to SQL so it might be a needless worry.
Here is my Split() function if it is needed:
CREATE FUNCTION Split (
#string nvarchar(4000),
#delimitor nvarchar(10),
#indexint = 0
)
RETURNS #splitTable TABLE (id int, string nvarchar(4000) NOT NULL) AS
BEGIN
DECLARE #startOfSubString smallint;
DECLARE #endOfSubString smallint;
SET #startOfSubString = 1;
SET #endOfSubString = CHARINDEX(#delimitor, #string, #startOfSubString);
IF (#endOfSubString <> 0)
WHILE #endOfSubString > 0
BEGIN
INSERT INTO #splitTable
SELECT #index, SUBSTRING(#string, #startOfSubString, #endOfSubString - #startOfSubString);
SET #startOfSubString = #endOfSubString+1;
SET #endOfSubString = CHARINDEX(#delimitor, #string, #startOfSubString);
END;
INSERT INTO #splitTable
SELECT #index, SUBSTRING(#string, #startOfSubString, LEN(#string)-#startOfSubString+1);
RETURN;
END
Hope my problem and attempt was explained and possible to understand.
You are looking for cross apply:
SELECT t.index, s.item
FROM OldTable t CROSS APPLY
(dbo.split(t.list, ',')) s(item);
Inserting in the new table just requires an insert or select into clause.

filtering in stored procedure using oracle 11 g

I have a stored procedure which returns some data . I want to use a filter to get specific row
ID NAME SCHOOL
1 Jack Highschool
2 Mike Charter
3 Merry University
4 Ahmet Ph.D
I want to get only jack and Ahmet by filtering ID .
How to achieve it ?
Thanks
You can define a table parameter to your stored procedure
CREATE TYPE paramTableType AS TABLE
(
[ID] [varchar](10) NOT NULL,
)
and fill a table with with this type with all IDs you want to filter with and then use a select statement like below one
CREATE PROCEDURE yourProcedureName
(
#TableVariable paramTableType READONLY
)
AS
BEGIN
select * from yourTable where ID IN (select * from #TableVariable )
END
I'm coming from a SQL back ground put try if works for Oracle .
List item

Select from hundreds of tables at once (.mdb)

We have .mdb file with hundreds of tables: Lesson1, Lesson2, Lesson3, Lesson4, etc. All tables have the same structure:
Lesson<n>
----------------
slide_id
name
description
status
created_date
created_by
updated_date
updated_by
What SQL statement would generate a result like this:
| table_name | slide_id | name |
|-----------------------|-------------------------------|
| Lesson1 | 1 | name for slide 1 of lesson 1 |
| Lesson1 | 2 | name for slide 2 of lesson 1 |
| Lesson2 | 1 | name for slide 1 of lesson 2 |
| Lesson2 | 2 | whatever |
| Lesson2 | 3 | again whatever |
etc.
So there are a few points here:
table names must be included
there are hundreds of tables
If the table names are known, you can create a query like:
SELECT 'Lesson1' AS table_name, slide_id, name, ... FROM Lesson1
UNION ALL SELECT 'Lesson2', slide_id, name, ... FROM Lesson2
UNION ALL SELECT 'Lesson3', slide_id, name, ... FROM Lesson3
UNION ALL SELECT 'Lesson4', slide_id, name, ... FROM Lesson4
UNION ALL SELECT 'Lesson5', slide_id, name, ... FROM Lesson5
Cursors are only needed if the number of tables is in constant flux. If not, this should do the trick.
Hint: to generate the initial query, paste the names of the table in Excel, and use a formula in the next cell over to create that table's "UNION ALL" statement. Then copy and paste straight back into Access. (Or create it dynamically using a cursor, but copy/paste and a quick formula is easy, and you can save the excel file just in case you need to add tables in bulk, change the columns selected, etc.)
And, obviously, the end solution should be to consolidate the tables, if possible, and add a discriminator field when querying. Heck, if you have to, it's easier to maintain hundreds of queries that each pull one lesson's rows (again, Excel can be a handy batch-update tool), than hundreds of lessons tables that must have identical structures.
Using sql server, I can unfortunately see this only done with a CURSOR X-(.
This should help
DECLARE #Name VARCHAR(50)
DECLARE Cur CURSOR FOR
SELECT name
FROM sysobjects
WHERE xtype = 'U'
and name like 'Lesson%'
OPEN Cur
FETCH NEXT FROM Cur INTO #Name
DECLARE #RetTable TABLE(
TableName VARCHAR(50),
slide_id INT,
name VARCHAR(100)
)
WHILE ##FETCH_STATUS = 0
BEGIN
INSERT INTO #RetTable EXEC ('SELECT ''' + #Name + ''',slide_id , Name FROM ' + #Name)
FETCH NEXT FROM Cur INTO #Name
END
CLOSE Cur
DEALLOCATE Cur
SELECT *
FROm #RetTable
OK, then if you can use a macro/vba code you can create a temp table called AllLessons and run the following code. I tested this from a form with a button.
Private Sub Command0_Click()
Dim iTable As Integer
For iTable = 0 To CurrentDb.TableDefs.Count - 1
Dim tableName As String
tableName = CurrentDb.TableDefs(iTable).Name
If (Left(tableName, Len("Lesson")) = "Lesson") Then
CurrentDb.Execute "INSERT INTO AllLessons ([table_name],[slide_id],[name]) SELECT """ & tableName & """, [slide_id],[name] FROM " & tableName
End If
Next iTable
End Sub