Case when exists then select - tsql

I have requirement to select the field from the table in case statement like instead of some static value.
WHEN EXISTS(SELECT c.customer_name FROM Sales.Customer AS c
WHERE c.PersonID = #BusinessEntityID)
THEN c.customer_name
How can this be achieved or is this possible . I have taken the following from msdn site. Need to tweak to fulfill my requirement.
USE AdventureWorks2008R2;
GO
CREATE FUNCTION dbo.GetContactInformation(#BusinessEntityID int)
RETURNS #retContactInformation TABLE
(
BusinessEntityID int NOT NULL,
FirstName nvarchar(50) NULL,
LastName nvarchar(50) NULL,
ContactType nvarchar(50) NULL,
PRIMARY KEY CLUSTERED (BusinessEntityID ASC)
)
AS
-- Returns the first name, last name and contact type for the specified contact.
BEGIN
DECLARE
#FirstName nvarchar(50),
#LastName nvarchar(50),
#ContactType nvarchar(50);
-- Get common contact information
SELECT
#BusinessEntityID = BusinessEntityID,
#FirstName = FirstName,
#LastName = LastName
FROM Person.Person
WHERE BusinessEntityID = #BusinessEntityID;
SET #ContactType =
CASE
-- Check for employee
WHEN EXISTS(SELECT * FROM HumanResources.Employee AS e
WHERE e.BusinessEntityID = #BusinessEntityID)
THEN 'Employee'
-- Check for vendor
WHEN EXISTS(SELECT * FROM Person.BusinessEntityContact AS bec
WHERE bec.BusinessEntityID = #BusinessEntityID)
THEN 'Vendor'
-- Check for store
WHEN EXISTS(SELECT * FROM Purchasing.Vendor AS v
WHERE v.BusinessEntityID = #BusinessEntityID)
THEN 'Store Contact'
-- Check for individual consumer
WHEN EXISTS(SELECT * FROM Sales.Customer AS c
WHERE c.PersonID = #BusinessEntityID)
THEN 'Consumer'
END;
-- Return the information to the caller
IF #BusinessEntityID IS NOT NULL
BEGIN
INSERT #retContactInformation
SELECT #BusinessEntityID, #FirstName, #LastName, #ContactType;
END;
RETURN;
END;
GO

No idea what the rest of your code looks like, but typically this would be:
SELECT name = COALESCE(c.customer_name, o.other_entity_name)
FROM dbo.MainEntity AS m
LEFT OUTER JOIN dbo.Customers AS c ON m.something = c.something
LEFT OUTER JOIN dbo.OtherTable AS o ON m.something = o.something;
But other than a general idea, you haven't given quite enough information to supply a complete answer.

Related

Prepared Statement Does Not Exists, PostgreSQL

I created the stored procedure with this code
CREATE PROCEDURE get_conferences_for_attendee
(
IN start_time TIMESTAMP,
IN end_time TIMESTAMP,
IN email VARCHAR(255),
IN deleted BOOLEAN
)
AS
$$
SELECT c.localuuid, c.title, i.id, i.start_time, i.end_time, i.status, a.email, a.deleted
FROM Conference c
INNER JOIN Instance i ON i.conference_localuuid = c.localuuid
INNER JOIN Conference_Attendees ca ON ca.conference_localuuid = c.localuuid
INNER JOIN Attendee a ON ca.attendees_localuuid = a.localuuid
WHERE i.start_time BETWEEN start_time AND end_time
AND a.email = email
AND a.deleted = deleted
$$ LANGUAGE SQL;
and this returned
CREATE PROCEDURE
I can see my procedure
SELECT proname, prorettype
FROM pg_proc
WHERE pronamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'public');
proname | prorettype
------------------------------+------------
get_conferences_for_attendee | 2278
When I try to execute, I get the error on the title.
EXECUTE get_conferences_for_attendee ('2022-12-26T00:00:00', '2023-01-01T23:59:59', 'yacs.demo2#abc.com', false);
ERROR: prepared statement "get_conferences_for_attendee" does not exist
Update
I found a solution but I'm not sure if it's the proper way to create this. It looks too complicated for me.
CREATE TYPE conference_record AS (
localuuid VARCHAR(255),
title VARCHAR(255),
id VARCHAR(255),
start_time TIMESTAMP,
end_time TIMESTAMP,
status VARCHAR(255),
email VARCHAR(255),
deleted BOOLEAN
);
CREATE FUNCTION get_conferences_for_attendee
(
IN start_time TIMESTAMP,
IN end_time TIMESTAMP,
IN email VARCHAR(255),
IN deleted BOOLEAN
)
RETURNS SETOF conference_record AS $$
BEGIN
RETURN QUERY
SELECT c.localuuid, c.title, i.id, i.start_time, i.end_time, i.status, a.email, a.deleted
FROM Conference c
INNER JOIN Instance i ON i.conference_localuuid = c.localuuid
INNER JOIN Conference_Attendees ca ON ca.conference_localuuid = c.localuuid
INNER JOIN Attendee a ON ca.attendees_localuuid = a.localuuid
WHERE i.start_time BETWEEN $1 AND $2
AND a.email = $3
AND a.deleted = $4;
END;
$$ LANGUAGE plpgsql;
SELECT * FROM get_conferences_for_attendee ('2022-12-26T00:00:00', '2023-01-01T23:59:59', 'yacs.demo1#abc.com', false);
As pointed out in the comments, to use a procedure, you need to CALL your_procedure();.
The code you presented looks like you're trying to get something from it, so a function is more suitable - procedures can return data through out and inout parameters or side-effects, like dumping them to an outside table.
The function and type definitions you later added in an edit look fine. If you're planning to feed it directly into a table, you don't need to define the custom type and instead specify RETURNS SETOF your_target_table_name or RETURNS TABLE (LIKE your_target_table_name).
You can also make it LANGUAGE sql - since you're not using anything plpgsql-specific, you don't need the additional overhead that comes with it. You'll just have to remove BEGIN RETURN QUERY and END, leaving just the bare-bones query.
You can also use a regular prepared statement for this:
PREPARE get_conferences_for_attendee(
TIMESTAMP,
TIMESTAMP,
VARCHAR(255),
BOOLEAN ) AS
SELECT
c.localuuid,
c.title,
i.id,
i.start_time,
i.end_time,
i.status,
a.email,
a.deleted
FROM Conference c
INNER JOIN Instance i ON i.conference_localuuid = c.localuuid
INNER JOIN Conference_Attendees ca ON ca.conference_localuuid = c.localuuid
INNER JOIN Attendee a ON ca.attendees_localuuid = a.localuuid
WHERE i.start_time BETWEEN $1 AND $2
AND a.email = $3
AND a.deleted = $4;
And use it exactly like you intially planned to, with an EXECUTE:
EXECUTE get_conferences_for_attendee(
'2022-12-26T00:00:00',
'2023-01-01T23:59:59',
'yacs.demo1#abc.com',
false);
Online demo
I found a solution but I'm not sure if it's the proper way to create this.
A function is the correct way to do this.
It looks too complicated for me.
You are indeed over-complicating the implementation. You don't need to create a type, this can be simplified by using returns table() instead.
You also don't need PL/pgSQL for this. A SQL function will be enough
CREATE FUNCTION get_conferences_for_attendee
(
p_start_time TIMESTAMP,
p_end_time TIMESTAMP,
p_email text,
p_deleted BOOLEAN
)
RETURNS table(localuuid text, title, text, id text, start_time timestamp, end_time timestamp, status text, email text, deleted boolean)
AS
$$
SELECT c.localuuid, c.title, i.id, i.start_time, i.end_time, i.status, a.email, a.deleted
FROM Conference c
INNER JOIN Instance i ON i.conference_localuuid = c.localuuid
INNER JOIN Conference_Attendees ca ON ca.conference_localuuid = c.localuuid
INNER JOIN Attendee a ON ca.attendees_localuuid = a.localuuid
WHERE i.start_time BETWEEN p_start_time AND p_end_time
AND a.email = p_email
AND a.deleted = p_deleted
$$
LANGUAGE sql
stable;
I renamed the parameters with a prefix to avoid a name clash with columns of the same name.
Note that using BETWEEN with timestamp values is usually a bad idea. It's better to use a range query using >= for the lower bound and < for the "next day" of the upper bound
e.g. start_time >= 2022-12-26 00:00:00' and end_time < '2023-01-02 00:00:00'
Your condition would not return rows where the end_time is e.g. 2023-01-01 23:59:59.999

Issue with PK violation on insert

I have a scenario where almost all of the tables have issues with the PK value as follows. This results is a database error or the violation of the PK insert.
When using the DBCC CheckIdent it displays an inconsistency between the next value and the current one.
Can anyone have a reason for the mismatch happening on several tables?
Since this database is then replicate, I'm afraid this error will propagate across the environment.
I adapted this script to fix it, but really trying to figure out the root of the problem.
/** Version 3.0 **/
if object_id('tempdb..#temp') is not null
drop table #temp
;
with cte as (
SELECT
distinct
A.TABLE_CATALOG AS CATALOG,
A.TABLE_SCHEMA AS "SCHEMA",
A.TABLE_NAME AS "TABLE",
B.COLUMN_NAME AS "COLUMN",
IDENT_SEED (A.TABLE_NAME) AS Seed,
IDENT_INCR (A.TABLE_NAME) AS Increment,
IDENT_CURRENT (A.TABLE_NAME) AS Curr_Value
, DBPS.row_count AS NumberOfRows
FROM INFORMATION_SCHEMA.TABLES A
inner join INFORMATION_SCHEMA.COLUMNS B on b.TABLE_NAME = a.TABLE_NAME and b.TABLE_SCHEMA = a.TABLE_SCHEMA
inner join sys.identity_columns IC on OBJECT_NAME (IC.object_id) = a.TABLE_NAME
inner join sys.dm_db_partition_stats DBPS ON DBPS.object_id =IC.object_id
inner join sys.indexes as IDX ON DBPS.index_id =IDX.index_id
WHERE A.TABLE_CATALOG = B.TABLE_CATALOG AND
A.TABLE_SCHEMA = B.TABLE_SCHEMA AND
A.TABLE_NAME = B.TABLE_NAME AND
COLUMNPROPERTY (OBJECT_ID (B.TABLE_NAME), B.COLUMN_NAME, 'IsIdentity') = 1 AND
OBJECTPROPERTY (OBJECT_ID (A.TABLE_NAME), 'TableHasIdentity') = 1 AND
A.TABLE_TYPE = 'BASE TABLE'
)
select 'DBCC CHECKIDENT ('''+A.[SCHEMA]+'.'+a.[TABLE]+''', reseed)' command
, ROW_NUMBER() OVER(ORDER BY a.[SCHEMA], a.[TABLE] asc) AS ID
, A.Curr_Value
, a.[TABLE]
into #temp
from cte A
ORDER BY A.[SCHEMA], A.[TABLE]
declare #i int = 1, #count int = (select max(ID) from #temp)
declare #text varchar(max) = ''
select #COUNT= count(1) FROM #temp
WHILE #I <= #COUNT
BEGIN
SET #text = (SELECT command from #temp where ID=#I)
EXEC (#text + ';')
print #text
select Curr_Value OldValue, ident_current([TABLE]) FixValue, [TABLE] from #temp where ID=#I
SET #I = #I + 1
SET #text='';
END
go
maybe someone or something with enough permissions made a mistake by reseeding?
As simple as this:
create table testid (
id int not null identity (1,1) primary key,
data varchar (3)
)
insert into testid (data) values ('abc'),('cde')
DBCC CHECKIDENT ('testid', RESEED, 1)
insert into testid (data) values ('bad')

DELETE WHERE NOT EXISTS Not Being Applied Correctly

I'm using NOT EXISTS during a DELETE statement in a stored procedure and the not exists is not being applied to the data.
Using the following example data:
CREATE TABLE Region
(
RegionID INT IDENTITY(1,1)
,RegionName VARCHAR(25)
)
GO
INSERT INTO Region(RegionName)
VALUES ('East Coast')
,('Mid West')
,('West Coast')
GO
CREATE TABLE Customer
(
CustomerID INT IDENTITY(1,1)
,FirstName VARCHAR(5)
,Region INT
)
GO
INSERT INTO Customer(FirstName,Region)
VALUES('Tom',1)
,('Mike',2)
,('Jean',3)
GO
CREATE TABLE Orders
(
OrderID INT IDENTITY(1,1)
,CustomerID INT
,OrderAmount INT
,OrderDate DATE
)
GO
INSERT INTO Orders(CustomerID,OrderAmount,OrderDate)
VALUES(1,10,'2018-11-30')
,(2,12,'2018-11-30')
,(2,15,'2018-12-01')
,(2,8,'2018-12-02')
,(2,11,'2018-12-03')
,(3,13,'2018-12-01')
,(3,20,'2018-12-03')
GO
Using that data I'm trying to create a procedure that does the following:
CREATE PROCEDURE udsp_GetOrdersOfXAmount #OrderAmount INT, #RegionID INT = 0
AS
BEGIN
DECLARE #ProcedureTemp TABLE
(
OrderID INT
,CustomerID INT
,OrderAmount INT
,OrderDate DATE
)
INSERT INTO #ProcedureTemp(OrderID,CustomerID,OrderAmount,OrderDate)
SELECT * FROM Orders WHERE OrderAmount >= #OrderAmount
--Do several other UPDATES/ DELETES to #ProcedureTemp
--This is where the issue lies
IF #RegionID > 0
BEGIN
DELETE T FROM #ProcedureTemp T
WHERE NOT EXISTS
(
SELECT *
FROM Customer C
JOIN #ProcedureTemp T ON T.CustomerID = C.CustomerID
WHERE C.Region = #RegionID
)
END
SELECT * FROM #ProcedureTemp
END
GO
If you execute the procedure with the #RegionID parameter populated, you will see the procedure is not honoring the filter by region.
E.G.
EXEC udsp_GetOrdersOfXAmount 10,3
However, if you run the sub query used in the DELETE statement as its own query, you will see the WHERE clause logic provided is working. I don't understand why the it isn't working when used with NOT EXISTS in the DELETE statement.
DECLARE #OrderAmount INT = 10, #RegionID INT = 3
DECLARE #ProcedureTemp TABLE
(
OrderID INT
,CustomerID INT
,OrderAmount INT
,OrderDate DATE
)
INSERT INTO #ProcedureTemp(OrderID,CustomerID,OrderAmount,OrderDate)
SELECT * FROM Orders WHERE OrderAmount >= #OrderAmount
SELECT *
FROM Customer C
JOIN #ProcedureTemp T ON T.CustomerID = C.CustomerID
WHERE C.Region = #RegionID
Thank you in advance for any help you can provide.
You don't need the join in the inner query.
The fact that you are using the same alias for the outer query and the inner one is confusing to me, I'm guessing SQL Server also should have a problem with it.
Try writing it like this:
DELETE T
FROM #ProcedureTemp T
WHERE NOT EXISTS
(
SELECT *
FROM Customer C
-- You already have the T from the outer statement
WHERE T.CustomerID = C.CustomerID
AND C.Region = #RegionID
)

I am getting Dollar sign unterminated

I want to create a function like below which inserts data as per the input given. But I keep on getting an error about undetermined dollar sign.
CREATE OR REPLACE FUNCTION test_generate
(
ref REFCURSOR,
_id INTEGER
)
RETURNS refcursor AS $$
DECLARE
BEGIN
DROP TABLE IF EXISTS test_1;
CREATE TEMP TABLE test_1
(
id int,
request_id int,
code text
);
IF _id IS NULL THEN
INSERT INTO test_1
SELECT
rd.id,
r.id,
rd.code
FROM
test_2 r
INNER JOIN
raw_table rd
ON
rd.test_2_id = r.id
LEFT JOIN
observe_test o
ON
o.raw_table_id = rd.id
WHERE o.id IS NULL
AND COALESCE(rd.processed, 0) = 0;
ELSE
INSERT INTO test_1
SELECT
rd.id,
r.id,
rd.code
FROM
test_2 r
INNER JOIN
raw_table rd
ON rd.test_2_id = r.id
WHERE r.id = _id;
END IF;
DROP TABLE IF EXISTS tmp_test_2_error;
CREATE TEMP TABLE tmp_test_2_error
(
raw_table_id int,
test_2_id int,
error text,
record_num int
);
INSERT INTO tmp_test_2_error
(
raw_table_id,
test_2_id,
error,
record_num
)
SELECT DISTINCT
test_1.id,
test_1.test_2_id,
'Error found ' || test_1.code,
0
FROM
test_1
WHERE 1 = 1
AND data_origin.id IS NULL;
INSERT INTO tmp_test_2_error
SELECT DISTINCT
test_1.id,
test_1.test_2_id,
'Error found ' || test_1.code,
0
FROM
test_1
INNER JOIN
data_origin
ON
data_origin.code = test_1.code
WHERE dop.id IS NULL;
DROP table IF EXISTS test_latest;
CREATE TEMP TABLE test_latest AS SELECT * FROM observe_test WHERE 1 = 2;
INSERT INTO test_latest
(
raw_table_id,
series_id,
timestamp
)
SELECT
test_1.id,
ds.id AS series_id,
now()
FROM
test_1
INNER JOIN data_origin ON data_origin.code = test_1.code
LEFT JOIN
observe_test o ON o.raw_table_id = test_1.id
WHERE o.id IS NULL;
CREATE TABLE latest_observe_test as Select * from test_latest where 1=0;
INSERT INTO latest_observe_test
(
raw_table_id,
series_id,
timestamp,
time
)
SELECT
t.id,
ds.id AS series_id,
now(),
t.time
FROM
test_latest t
WHERE t.series_id IS DISTINCT FROM observe_test.series_id;
DELETE FROM test_2_error re
USING t
WHERE t.test_2_id = re.test_2_id;
INSERT INTO test_2_error (test_2_id, error, record_num)
SELECT DISTINCT test_2_id, error, record_num FROM tmp_test_2_error ORDER BY error;
UPDATE raw_table AS rd1
SET processed = case WHEN tre.raw_table_id IS null THEN 2 ELSE 1 END
FROM test_1 tr
LEFT JOIN
tmp_test_2_error tre ON tre.raw_table_id = tr.id
WHERE rd1.id = tr.id;
OPEN ref FOR
SELECT 1;
RETURN ref;
OPEN ref for
SELECT o.* from observe_test o
;
RETURN ref;
OPEN ref FOR
SELECT
rd.id,
ds.id AS series_id,
now() AS timestamp,
rd.time
FROM test_2 r
INNER JOIN raw_table rd ON rd.test_2_id = r.id
INNER JOIN data_origin ON data_origin.code = rd.code
WHERE o.id IS NULL AND r.id = _id;
RETURN ref;
END;
$$ LANGUAGE plpgsql VOLATILE COST 100;
I am not able to run this procedure.
Can you please help me where I have done wrong?
I am using squirrel and face the same question as you.
until I found that:
-- Note that if you want to create the function under Squirrel SQL,
-- you must go to Sessions->Session Properties
-- then SQL tab and change the Statement Separator from ';' to something else
-- (for intance //). Otherwise Squirrel SQL sends one piece to the server
-- that stops at the first encountered ';', and the server cannot make
-- sense of it. With the separator changed as suggested, you type everything
-- as above and end with
-- ...
-- end;
-- $$ language plpgsql
-- //
--
-- You can then restore the default separator, or use the new one for
-- all queries ...
--

Update Manager Table Tree by Recursion

I have a table (see image) Employees (manager is manager of another and so on) with id, parentid, salary, totalsalary. The last one needs to be updated so that every employee had Sum of its Descendants salary. I have already written script, which gets total salary by id and than updates column in cursor, but it's heavy... any other ways?
DECLARE #id INT ;
DECLARE #s INT ;
DECLARE curs CURSOR FOR
SELECT personid FROM dbo.Employees
OPEN curs ;
FETCH NEXT FROM curs INTO #id ;
WHILE ##FETCH_STATUS = 0
BEGIN
WITH Xemps ( ID )
AS ( SELECT PersonID AS ID
FROM dbo.Employees
WHERE PersonID = #id
UNION ALL
SELECT e.PersonID AS ID
FROM dbo.Employees AS e
INNER JOIN Xemps AS x ON e.ManagerID = x.ID
)
SELECT #s = SUM(Salary)
FROM dbo.Employees
WHERE PersonID IN ( SELECT id
FROM Xemps )
UPDATE dbo.Employees
SET SalarySum = #s
WHERE PersonID = #id
FETCH NEXT FROM curs INTO #id
END
CLOSE curs ;
DEALLOCATE curs ;
The cursor is not necessary, this can be done using just a recursive common table expression:
WITH Emp AS
( SELECT EmployeeID, Salary, ManagerID
FROM dbo.Employee
UNION ALL
SELECT e.EmployeeID, e.Salary, Emp.ManagerID
FROM dbo.Employee e
INNER JOIN Emp
ON e.ManagerID = Emp.EmployeeID
)
UPDATE dbo.Employee
SET SalarySum = COALESCE(s.Salary, 0) + e.Salary
FROM dbo.Employee e
LEFT JOIN
( SELECT ManagerID, SUM(Salary) [Salary]
FROM Emp
GROUP BY ManagerID
) s
ON s.ManagerID = e.EmployeeID
Create a function that performs the sum for each employee where they are a manager:
create function dbo.fn_TotalSalary
{
#EmployeeId int
}
returns float
as
begin
declare #totalSalary float
select #totalSalary = sum(salary)
from dbo.employees
where employeeid = #employeeid or managerid = #employeeid
return #totalSalary
end
Then alter the Employees table, changing the TotalSalary column to a computed column using the function:
dbo.fn_TotalSalary(EmployeeId)
As employees earn more, the computed column will update automatically. Then you can simply call
select * from Employees
to get the details. Doing it this way would mean that your data is always 100% accurate and up to date rather than potentially retrieving stale data.