PG Rule - Using NEW in subquery - sql

I have a rule that runs on update to a source table. The rule queries data across multiple other tables, formats the data, and inserts it into another transform table. Here is an example of what I have so far.
CREATE OR REPLACE RULE
value_insert
AS ON UPDATE TO
source_table
DO ALSO INSERT INTO transform_table(
username
,status
,section
)
SELECT
username
,MAX(status)
,MAX(section)
FROM
(
SELECT
username
,CASE
WHEN item = status
THEN value
ELSE NULL
END AS status
,CASE
WHEN item = section
THEN value
ELSE NULL
END AS section
FROM
(
SELECT
username
,item
,value
FROM
table1
,table2
WHERE
item = status
OR item = section
AND source_table.username = NEW.username
)
)
GROUP BY
username
I am trying to pass the NEW value into the subquery, but I receive the error "ERROR: subquery in FROM cannot refer to other relations of same query level". Using NEW in the outermost where statement works, but the query take a long time due to the large amount of data in the tables.
Is it possible to pass the NEW value into the subquery of this rule? I am using PG 8.3 and PGAdmin 1.12

Solved this by implementing a trigger function. As far as I can tell, you cannot pass the NEW value to a subquery in a rule (PG 8.3).
The script below will collect only data from table1 and table2 that corresponds to updated record in old_table, reformat the data, and insert it into new_table. By switching to a trigger and inserting the argument into the base query, the processing time has dropped from ~2 seconds to ~50 ms.
The function:
CREATE OR REPLACE FUNCTION get_data()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO new_table(
username
,status
,section
)
SELECT
username
,MAX(status)
,MAX(section)
FROM
(
SELECT
username
,CASE
WHEN item = status
THEN value
ELSE NULL
END AS status
,CASE
WHEN item = section
THEN value
ELSE NULL
END AS section
FROM
(
SELECT
username
,item
,value
FROM
table1
,table2
WHERE
(item = status OR item = section)
AND table1.username = table2.username
AND table2.username = old_table.username
AND old_table.username = NEW.username
)
)
GROUP BY
username;
RETURN NEW;
END;
$$
LANGUAGE plpgSQL;
The trigger:
CREATE TRIGGER
old_table_trigger
BEFORE UPDATE ON
old_table
FOR EACH ROW EXECUTE PROCEDURE get_data();

Related

COUNT vs SELECT in SQL

What is better approach to check existence of an object in database?
select count(id) as count from my_table where name="searchedName";
OR
select id from my_table where name="searchedName";
And then check if count > 0 or the object is not null (ORM logic)
EDIT:
select id to be valid for Oracle.
The idea should be to that we only need to find one record in order to say that such record exists. This can be done with an EXISTS clause in standard SQL.
select exists (select * from mytable where name = 'searchedName');
returns true if the table contains a record with 'searchedName' and false otherwise.
If you want 0 for false and 1 for true instead (e.g. if the DBMS does not support booleans):
select case when exists (select * from mytable where name = 'searchedName')
then 1 else 0 end as does_exist;
You say you want this for Oracle. In Oracle you can use above query, but you'd have to select from the table dual:
select case when exists (select * from mytable where name = 'searchedName')
then 1 else 0 end as does_exist
from dual;
But for Oracle we'd usually use rownum instead:
select count(*) as does_exist
from mytable
where name = 'searchedName'
and rownum = 1; -- to find one record suffices and we'd stop then
This also returns 1 if the table contains a record with 'searchedName' and 0 otherwise. This is a very typical way in Oracle to limit lookups and the query is very readable (in my opinion).
I'd just call:
select id from my_table where name='searchedName';
Making sure there is an index for the name column.
And then check whether or not the result is empty.
Try with IF EXISTS (
if exists (select 1 from my_table where name = "searchedName")
begin
....
end

SQL - SELECT with inner IF or CASE

Trying to have a row that will display 'YES' or 'NO' depending on values found (if a tree has been treated before the date given in argument say YES else NO).
Here's my function:
CREATE OR REPLACE FUNCTION tree_care(care_date DATE)
RETURNS TABLE(name VARCHAR(32), type VARCHAR(32), treated TEXT) AS
$$
BEGIN
RETURN QUERY
SELECT tree.name,
tree.type,
IF EXISTS (SELECT * FROM treatment
JOIN tree ON tree.name = treatment.tree_name
WHERE treatment.date < care_date) THEN
'YES'::text
ELSE
'NO'::text
END IF
FROM tree;
END;
$$
And I get the following error:
ERROR: syntax error at or near "EXISTS"
LINE 8: IF EXISTS (SELECT * FROM treatment
How does one implement an IF statement inside a SELECT?
PS: Using postgresql 9.4
IF is control flow. Use CASE because this is within a SELECT statement:
SELECT tree.name,
tree.type,
(CASE WHEN EXISTS (SELECT 1
FROM treatment
WHERE tree.name = treatment.tree_name AND
treatment.date < care_date
) THEN
THEN 'YES'::text
ELSE 'NO'::text
END) as Flag
FROM tree;
I am also guessing that you intend a correlated subquery, rather than an independent subquery.

Single Select SQL Statement for 2 different Values

I have a SQL Server table with a column called Category. In my user interface, I have a dropdown list of the category. User can select a category and click on a Search button to filter the results by the category. In the dropdown, the first option is blank. Means if the user wants to see all records from all categories, he can select blank.
In my SQL Select I have 2 statements for this
IF #Catg IS NULL
Begin
Select *
From Table
End
Else
Begin
Select *
From Table
Where Catg = #Catg
End
The Catg column in the table will have either a NULL or a category. Is this possible to do in a single SQL statement?
You can use an OR statement to join the clauses together:
Select *
From Table
Where Catg = #Catg OR #Catg IS NULL
How about
SELECT *
FROM yourtable
WHERE (Catg = #Catg) OR (Catg IS NULL)
might be ISNULL solve your problem this will check if values is NULL then '' else your VALUE
select * from tblName
where ISNULL(Catg , '')= ISNULL(#Catg, ISNULL(Catg , ''))

If value return the value. If a record not exists or when column is null, return 0 in Sql Server - different ways

I want to return 0 if there is no record or if the Column1 is null.
select #var = Column1
from myschema.mytable
where Id = #suppliedId;
select isnull(#var, 0);
The above code outputs 0 if if Column1 is null. Or if a row is not found
Whereas I tried to save some keystrokes but it resulted in,
select isnull(Column1, 0)
from myschema.mytable
where Id = #suppliedId;
The above code outputs null if Column1 is null or when there is no row
Any ideas what is wrong here ? Or is there any shorter way of writing the first code ?
You can do
SELECT #var = ISNULL(MAX(Column1), 0)
FROM myschema.mytable
WHERE Id = #suppliedId;
A scalar aggregate always returns a single row even if the underlying query returns zero rows.
Not really saving key strokes, but something like this could help :-)
SELECT TOP 1 tbl.field
FROM
(
SELECT 0 AS inx, 'no record' AS field
--if only one row is possible, than set '1' literally
UNION SELECT ROW_NUMBER() OVER(ORDER BY mytable.orderfield), ISNULL(mytable.Land,'is null')
FROM mytable
WHERE IDENTITY = #suppliedID
) AS tbl
ORDER BY tbl.inx DESC

Retrieve and update the top row in a stored proc - accessed by multiple process

I'm looking at creating a stored proc which is being accessed by multiple processes and wondering, between the two solutions, which is a better one or is there a third solution?
The goal is find the record that has the maximum value in the field status. The field status contains decimal values but defined as a varchar. Once the appropriate record is found, then update the same record (only one record can be updated by the stored proc).
The stored proc will return the cusip in the form of select statement.
Solution:01
Declare #Cusiptable(Cusip varchar(100));
UPDATE cusiptbl
SET processingdate = GetDate() ,
Status = 'In Progress',
batchid = #batchid_given,
Output Inserted.Cusip into #Cusiptable
From
(
select top(1) Cusip from cusiptbl where batchid = -1
order by cast(status as decimal(18,4)) desc
) Seconds
where cusiptbl.cusip = Seconds.cusip
select Cusip from #Cusiptable
Solution 02:
select top(1) #CusipToBeUpdated = Cusip from cusiptbl
with (UPDLOCK)
where batchid = -1
order by
(case when isnumeric(status) = 1 then
cast(status as decimal(18,7)) end)
desc
-- Update the status to given batchid and status as 'In Progress'
UPDATE cusiptbl
SET batchid = #batchid_given,
processingdate = GetDate() ,
Status = 'In Progress'
where cusiptbl.cusip= #CusipToBeUpdated
and batchid = -1
Select #CusipToBeUpdated Cusip
The first query will fail while evaluating order by cast(status as decimal(18,4)) for non-numeric values.
Rewrite it using isnumeric(status) = 1 to prevent the error:
From
(
select top(1) Cusip from cusiptbl
where batchid = -1 AND isnumeric(status) = 1
order by cast(status as decimal(18,4)) desc
) Seconds
Both solution should work assuming the transaction isolation level is read committed or higher.
Casting from varchar to numeric prevents from using an index (if is any), if your table is huge then you might consider adding a virtual column to the table, for example:
create table cusiptbl(
......
status varchar(50),
status_numeric as case when isnumeric(status) = 1 then
cast(status as decimal(18,7)) else null end
)
and create an index on this virtual column:
create index num_status on cusiptbl( status_numeric )
or maybe a composite index (since your queries filter rows using batchid=-1 condition and then order selected rows by status):
create index num_status_batch on cusiptbl( batchid, status_numeric )
and then rewrite queries and use the virtual column in them, for example:
From
(
select top(1) Cusip from cusiptbl
where batchid = -1 and status_numeric is not null
order by status_numeric desc
) Seconds