Stored Procedure calling variables from table - sql

I have a stored procedure that uses a variable ID, I have a list of valid IDs in a table.
I'm trying to write a stored procedure that runs a specific piece of code if the ID exists in table. I'm just not sure of the syntax.
Below is my pseudo-code of what I'm attempting to do.
IF
#ID = possible id IN (SELECT DISTINCT ID FROM [dbo].ID_TABLE WHERE ID = 'valid')
SELECT * FROM dbo.[results]
ELSE
SELECT * FROM dbo.[otherresults]
I'm using SQL Server

Typically, this is the case where you would use EXISTS; as in....
IF EXISTS (SELECT * FROM ID_TABLE WHERE ID = #ID)
While #ID IN (SELECT DISTINCT would work, that query requires going through the table data to assemble a result set that is then checked for #ID's inclusion. EXISTS queries do not create result sets, and return early on the first row fitting the criteria.

If your query
SELECT DISTINCT ID FROM [dbo].ID_TABLE WHERE ID = 'valid'
always returns only one id, then below solution is enough
#ID = (SELECT DISTINCT ID FROM [dbo].ID_TABLE WHERE ID = 'valid')
If it returns list of ids, then you need to create a temp table and store those id's like below
create table #temp_table(id int)
insert into #temp_table SELECT DISTINCT ID FROM [dbo].ID_TABLE WHERE ID = 'valid'

Related

How to use a stored procedure inside of a select statement in Redshift

I have found some other workarounds for mysql and other database providers but i wasn't able to find a solution to use stored procedure (get_data) inside of a select statement in redshift. I have a stored procedure passing some ids and creating a temp table (tmp_tbl) from another table. Then i want to use that table and return some data from the temp table with some manipulations.
SELECT
tl.code,
tl.name,
SUM(tl.imps) as sales,
SUM(tl.clicks) as clicks
FROM (
CALL get_data('id1,id2');
SELECT * FROM tmp_tbl tl
)
WHERE filter='1990'
GROUP BY 1,2;
As a solution, i could possibly do the stored procedures separately somewhere else, and then combine the results but that means i'll have to make two separate requests from my database and i want to avoid it. Or i came up with other solutions where i still make a one database call without using a stored procedure inside a select statement but i have some other codes relying on this structure so i would like to achieve an inside statement. Is there a way to do this?
The procedure;
CREATE OR REPLACE PROCEDURE get_data(f1 varchar(max))
AS $$
BEGIN
DROP TABLE IF EXISTS id_list;
CREATE TEMPORARY TABLE id_list (
Id varchar(40)
);
DROP TABLE IF EXISTS tmp_tbl;
FOR loop_var IN 1..(REGEXP_COUNT(f1,',') + 1) LOOP
INSERT INTO id_list VALUES (SPLIT_PART(f1,',',loop_var));
END LOOP;
CREATE TEMP TABLE tmp_tbl as
SELECT code, name,sales,clicks
FROM mview_a
WHERE id IN (SELECT id FROM id_list)
UNION ALL
SELECT code, name,sales,clicks
FROM mview_b
WHERE id IN (SELECT id FROM id_list)
UNION ALL
SELECT code, name,sales,clicks
FROM mview_c
WHERE id IN (SELECT id FROM id_list)
UNION ALL
SELECT code, name,sales,clicks
FROM view_d (combination of multiple mviews)
WHERE id IN (SELECT id FROM id_list)
UNION ALL
SELECT code, name,sales,clicks
FROM mview_e
WHERE id IN (SELECT id FROM id_list)
Can't you just run the CALL first to make the temp table, then use it in the subsequent join?
CALL get_data('id1,id2');
SELECT
tl.code,
tl.name,
SUM(tl.imps) as sales,
SUM(tl.clicks) as clicks
FROM tmp_tbl tl
WHERE filter='1990'
GROUP BY 1,2;

Doing a join only if count is greater than one

I wonder if the following a bit contrived example is possible without using intermediary variables and a conditional clause.
Consider an intermediary query which can produce a result set that contain either no rows, one row or multiple rows. Most of the time it produces just one row, but when multiple rows, one should join the resulting rows to another table to prune it down to either one or no rows. After this if there is one row (as opposed to no rows), one would want to return multiple columns as produced by the original intermediary query.
I have in my mind something like following, but it won't obviously work (multiple columns in switch-case, no join etc.), but maybe it illustrates the point. What I would like to have is to just return what is currently in the SELECT clause in case ##ROWCOUNT = 1 or in case it is greater, do a INNER JOIN to Auxilliary, which prunes down x to either one row or no rows and then return that. I don't want to search Main more than once and Auxilliary only when x here contains more than one row.
SELECT x.MainId, x.Data1, x.Data2, x.Data3,
CASE
WHEN ##ROWCOUNT IS NOT NULL AND ##ROWCOUNT = 1 THEN
1
WHEN ##ROWCOUNT IS NOT NULL AND ##ROWCOUNT > 1 THEN
-- Use here #id or MainId to join to Auxilliary and there
-- FilteringCondition = #filteringCondition to prune x to either
-- one or zero rows.
END
FROM
(
SELECT
MainId,
Data1,
Data2,
Data3
FROM Main
WHERE
MainId = #id
) AS x;
CREATE TABLE Main
(
-- This Id may introduce more than row, so it is joined to
-- Auxilliary for further pruning with the given conditions.
MainId INT,
Data1 NVARCHAR(MAX) NOT NULL,
Data2 NVARCHAR(MAX) NOT NULL,
Data3 NVARCHAR(MAX) NOT NULL,
AuxilliaryId INT NOT NULL
);
CREATE TABLE Auxilliary
(
AuxilliaryId INT IDENTITY(1, 1) PRIMARY KEY,
FilteringCondition NVARCHAR(1000) NOT NULL
);
Would this be possible in one query without a temporary table variable and a conditional? Without using a CTE?
Some sample data would be
INSERT INTO Auxilliary(FilteringCondition)
VALUES
(N'SomeFilteringCondition1'),
(N'SomeFilteringCondition2'),
(N'SomeFilteringCondition3');
INSERT INTO Main(MainId, Data1, Data2, Data3, AuxilliaryId)
VALUES
(1, N'SomeMainData11', N'SomeMainData12', N'SomeMainData13', 1),
(1, N'SomeMainData21', N'SomeMainData22', N'SomeMainData23', 2),
(2, N'SomeMainData31', N'SomeMainData32', N'SomeMainData33', 3);
And a sample query, which actually behaves as I'd like it to behave with the caveat I'd want to do the join only if querying Main directly with the given ID produces more than one result.
DECLARE #id AS INT = 1;
DECLARE #filteringCondition AS NVARCHAR(1000) = N'SomeFilteringCondition1';
SELECT *
FROM
Main
INNER JOIN Auxilliary AS aux ON aux.AuxilliaryId = Main.AuxilliaryId
WHERE MainId = #id AND aux.FilteringCondition = #filteringCondition;
You don't usually use a join to reduce the result set of the left table. To limit a result set you'd use the where clause instead. In combination with another table this would be WHERE [NOT] EXISTS.
So let's say this is your main query:
select * from main where main.col1 = 1;
It returns one of the following results:
no rows, then we are done
one row, then we are also done
more than one row, then we must extend the where clause
The query with the extended where clause:
select * from main where main.col1 = 1
and exists (select * from other where other.col2 = main.col3);
which returns one of the following results:
no rows, which is okay
one row, which is okay
more than one row - you say this is not possible
So the task is to do this in one step instead. I count records and look for a match in the other table for every record. Then ...
if the count is zero we get no result anyway
if it is one I take that row
if it is greater than one, I take the row for which exists a match in the other table or none when there is no match
Here is the full query:
select *
from
(
select
main.*,
count(*) over () as cnt,
case when exists (select * from other where other.col2 = main.col3) then 1 else 0 end
as other_exists
from main
where main.col1 = 1
) counted_and_checked
where cnt = 1 or other_exists = 1;
UPDATE: I understand that you want to avoid unnecessary access to the other table. This is rather difficult to do however.
In order to only use the subquery when necessary, we could move it outside:
select *
from
(
select
main.*,
count(*) over () as cnt
from main
where main.col1 = 1
) counted_and_checked
where cnt = 1 or exists (select * from other where other.col2 = main.col3);
This looks much better in my opinion. However there is no precedence among the two expressions left and right of an OR. So the DBMS may still execute the subselect on every record before evaluating cnt = 1.
The only operation that I know of using left to right precedence, i.e. doesn't look further once a condition on the left hand side is matched is COALESCE. So we could do the following:
select *
from
(
select
main.*,
count(*) over () as cnt
from main
where main.col1 = 1
) counted_and_checked
where coalesce( case when cnt = 1 then 1 else null end ,
(select count(*) from other where other.col2 = main.col3)
) > 0;
This may look a bit strange, but should prevent the subquery from being executed, when cnt is 1.
You may try something like
select * from Main m
where mainId=#id
and #filteringCondition = case when(select count(*) from Main m2 where m2.mainId=#id) >1
then (select filteringCondition from Auxilliary a where a.AuxilliaryId = m.AuxilliaryId) else #filteringCondition end
but it's hardly very fast query. I'd better use temp table or just if and two queries.

Postgresql Select rows and update column

I have SQL Select query with where clauses. For e.g
select * from table where status = 1
And how can I update single column with selected rows simultaneously while selecting? I want to mark selected rows, to avoid reselect on the next loop. Something like:
select * from table where status = 1; update table set proc = 1 where id in (select id from table where status = 1)
But this query will not return results.
Use the returning clause:
update table
set proc = 1
where id in (select id from table where status = 1)
returning *;
(Btw: I assume the inner select is not actually selecting from the same table, because then the statement does not really makes sense as it could be rewritten with a simple where stauts = 1)

Can I select the data of a given row and column while executing a sql statement

To clarify the title, in a select statement, in the where clause, I need to verify to table on which I am doing using another select. In that second select, I have to find all the secondary ID. Here is what I have worked out so far
Declare #id INT
--inserting values in temp table
SELECT
rn = ROW_NUMBER() OVER (ORDER BY adt_trl_dt_tm),
*
INTO #Temp
FROM dbo.EVNT_HSTRY
ORDER BY adt_trl_dt_tm DESC
--Searching for items that are deleted and have not been restored
SELECT *
FROM dbo.EVNT_HSTRY hstry
WHERE evnt_hstry_cd LIKE '3' and
adt_trl_dt_tm > (SELECT adt_trl_dt_tm FROM dbo.EVNT_HSTRY WHERE evnt_id = evnt_id
DROP TABLE #Temp
To clarify the code, evnt_id is a foreign key. The primary key is evnt_Hstry_id. The evnt_hstry_cd 3 means deleted. What I am trying to do is to see if the field adt_trl_dt_tm (lastest date modified) of the row being read is the latest by comparing it with all the adt_trl_dt_tm fields that have the same evnt_id.
The table I am doing the select on is the table where we store the history of the events. It is where we say when the event has been added, modified, deleted and or restored.
Sadly, I cannot do that into my application as this statement is being run in an SSIS.
Overall, I need to compare the adt_trl_dt_tm with the other adt_trl_dt_tm that have the same evnt_id and select the latest.
Can you test this with your data ?
SELECT *
FROM dbo.EVNT_HSTRY hstry
WHERE evnt_hstry_cd LIKE '3' and
not exists (select 1 from EVNT_HSTRY WHERE hstry.evnt_id = evnt_id
AND Hstry.adt_trl_dt_tm > adt_trl_dt_tm)
SELECT *
FROM dbo.EVNT_HSTRY hstry
WHERE evnt_hstry_cd = '3' and
adt_trl_dt_tm = (
SELECT max(adt_trl_dt_tm) FROM dbo.EVNT_HSTRY WHERE evnt_id = hstry.evnt_id
)
will result in a row read if the code 3 is the most recent entry in hstry and no row if there is a more recent row not having code 3
Change LIKE in = if it matches exactly

How to create SQL Server stored procedure returning an array?

Sorry for this maybe foolish question but i'm newbie to SQL Server. Here structure of my table of divisions at organization:
id int(16) -- simply unique id
sub_id int(16) -- this id of parent division in this table (if zero, than it don't have parent)
name VARCHAR(200) -- name of division
I need to create a simply procedure which return me all id of subdivisions in some division (with top division id too). I think i need array with subid's in loop, but i don't know how to create array in SQL Serve o_0 (omg.. array exist in (T)SQL language ? =). Help please. Or maybe another way to get it?
If I have understood your question correctly you need a recursive CTE.
CREATE PROCEDURE dbo.foo
#id int
AS
WITH divisions AS
(
SELECT id, sub_id, name
FROM YourTable
WHERE id = #id
UNION ALL
SELECT y.id, y.sub_id, y.name
FROM YourTable y
JOIN divisions d ON d.id = y.sub_id
)
SELECT id, sub_id, name
FROM divisions
SELECT id
FROM table
WHERE sub_id = #target
UNION ALL
SELECT #target