Using CASE outside SELECT in table-valued function - tsql

I'm trying to select the same columns from a different table/view depending on the value of an argument (#ruleset). As it is not possible to pass the name of the table as a parameter nor to construct the name inside the function, used CASE structure outside the select statements. However, I get an error:
"Only one expression can be specified in the select list when the subquery is not introduced with EXISTS."
[Hope I get this right, it is my first question here.]
CREATE FUNCTION app.fgProduct
(
#ruleset nvarchar(50),
#matno nvarchar(50),
#datarevision int
)
RETURNS TABLE
AS
RETURN
(
SELECT
CASE WHEN #ruleset = 'G1' THEN
(
SELECT
#matno AS ProductId
,#datarevision AS DataRevision
,[ProductName]
FROM [ruleset].[g1gxProduct]
WHERE ProductId = #matno
)
WHEN #ruleset = 'G2' THEN
(
SELECT
#matno AS ProductId
,#datarevision AS DataRevision
,[ProductName]
FROM [ruleset].[g2gxProduct]
WHERE ProductId = #matno
)
END
)
There's a bunch of other views, so this whole issue cannot be solved in one procedure. Above is an example of a function which is used to generate new records based on various rule sets (= sets of views).

CASE is an expression and not for conditional flow. Use IF/ELSE instead.

The error is related to the select statements that are returning more than one column in the sub query. You can only return one column in the statements:
SELECT
#matno AS ProductId
,#datarevision AS DataRevision
,[ProductName]

The solution is to declare a result table and insert into it, just like #EzLo suggested in the comment.
CREATE FUNCTION app.fgProduct
(
#ruleset nvarchar(50),
#matno nvarchar(50),
#datarevision int
)
RETURNS #ret TABLE (
[ProductID] [nvarchar](50) NOT NULL,
[DataRevision] [int] NOT NULL,
[ProductName] [nvarchar](50) NULL
)
AS
BEGIN
IF #ruleset = 'G1'
INSERT INTO #ret
SELECT
#matno AS ProductId
,#datarevision AS DataRevision
,[ProductName]
FROM [ruleset].[g1gxProduct]
WHERE ProductId = #matno
ELSE
IF #ruleset = 'G2'
INSERT INTO #ret
SELECT
#matno AS ProductId
,#datarevision AS DataRevision
,[ProductName]
FROM [ruleset].[g2gxProduct]
WHERE ProductId = #matno
RETURN
END

Related

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 cannot manage to insert multiple rows when having multiple fields to get data from

I ran into a problem with regards to trying to insert multiple rows in a table at once. I know it sounds easy, but here is the twist. The procedure itself gets data from a trigger, and the trigger returns a number of rows. So i need to make 1 insert statement to insert those rows and some other data. here is the code:
CREATE PROCEDURE [a01].[usp_raiseFriendAlerts]
(#AccountA UNIQUEIDENTIFIER, #AccountB UNIQUEIDENTIFIER)
AS
BEGIN
DECLARE #typeID TINYINT;
DECLARE #notificationID UNIQUEIDENTIFIER = NEWID();
DECLARE #accountAName NVARCHAR(356);
DECLARE #accountBName NVARCHAR(356);
SET #typeID = ( SELECT typeID
FROM [a01].[tbl_notificationTypes]
WHERE typeName = 'Added friend');
SET #accountAName = ( SELECT accountUsername
FROM [a01].[tbl_userAccounts]
WHERE accountID = #AccountA);
SET #accountBName = ( SELECT accountUsername
FROM [a01].[tbl_userAccounts]
WHERE accountID = #AccountB);
DECLARE #AccountIDZZ UNIQUEIDENTIFIER;
SET #AccountIDZZ = (SELECT friendAccountID
FROM [a01].[udf_getAddedFriendContacts](#AccountA, #AccountB)
EXCEPT
SELECT targetAccountID
FROM [a01].[tbl_blockedAccounts]);
INSERT INTO [a01].[tbl_notificationsInbox] (notificationID, notificationMessage, notificationDate, accountID, typeId)
VALUES (#notificationID, #accountAName + ' is now friends with ' + #accountBName, SYSDATETIMEOFFSET(), #AccountIDZZ , #typeID)
END;
GO
Try this:
CREATE PROCEDURE [a01].[usp_raiseFriendAlerts]
(
#AccountA UNIQUEIDENTIFIER
, #AccountB UNIQUEIDENTIFIER
)
AS
BEGIN
DECLARE #typeID TINYINT
, #notificationID UNIQUEIDENTIFIER = NEWID()
, #accountAName NVARCHAR(356)
, #accountBName NVARCHAR(356)
, #AccountIDZZ UNIQUEIDENTIFIER;
SELECT #typeID = typeID
FROM [a01].[tbl_notificationTypes]
WHERE typeName = 'Added friend';
SELECT #accountAName = accountUsername
FROM [a01].[tbl_userAccounts]
WHERE accountID = #AccountA;
SELECT #accountBName = accountUsername
FROM [a01].[tbl_userAccounts]
WHERE accountID = #AccountB;
INSERT INTO [a01].[tbl_notificationsInbox]
(
notificationID
, notificationMessage
, notificationDate
, accountID
, typeId
)
SELECT #notificationID
, #accountAName + ' is now friends with ' + #accountBName
, SYSDATETIMEOFFSET()
, AIDZZ.accountID
, #typeID
FROM (
SELECT friendAccountID AS 'accountID'
FROM [a01].[udf_getAddedFriendContacts](#AccountA, #AccountB)
EXCEPT
SELECT targetAccountID AS 'accountID'
FROM [a01].[tbl_blockedAccounts]
) AS AIDZZ
END;
GO

Call function for every row of a table without cross apply

I have this tsql function which inserts into a table variable:
create function fnListInfo(#id int) returns #res table(
[itemId] INT,
[name] NVARCHAR(255),
[type] NVARCHAR(20),
[unit] INT,
[order] INT
)
BEGIN
INSERT INTO #res
SELECT category.stockcat_id, category.stockcat_name, category.type, NULL /*unit*/, category.order
FROM tblStock stock
JOIN dbo.Map category on stock.id = category.itemId
WHERE stock.id = #id
insert into #res
SELECT t.*
FROM #res r
CROSS APPLY dbo.anotherFunction(r.itemId) AS t
WHERE r.type = 'parent'
RETURN
END
GO
In the end of fnListInfo, I want to add some more rows to the res table. If the row in #res is of type 'parent', I want to call another function (let's call it anotherFunction) which has the same return type as this one, and its input parameter is int (itemId from fnListInfo), and then I want to add the result from anotherFunction to #res in fnListInfo.
So basically I want to call anotherFunction for every row in #res which is of type 'parent' and append the result to the already existing #res.
I tried doing this:
insert into #res
SELECT t.*
FROM #res r
CROSS APPLY dbo.anotherFunction(r.itemId) AS t
WHERE r.type = 'parent'
and it works. The problem is that it's inefficient. Is there a better way?
I don't like using cursors.
Try changing your function to inline table valued function:
CREATE FUNCTION dbo.fnListInfo(#id int)
RETURNS TABLE
AS
RETURN (
SELECT t.*
FROM (
SELECT category.stockcat_id AS ItemId, category.stockcat_name AS name,
category.type AS [type], NULL AS [unit] , category.[order] AS [order]
FROM tblStock stock
JOIN dbo.Map category
ON stock.id = category.itemId
WHERE stock.id = #id) AS r
CROSS APPLY dbo.anotherFunction(r.itemId) AS t
WHERE r.type = 'parent'
UNION ALL
SELECT category.stockcat_id AS ItemId, category.stockcat_name AS name,
category.type AS [type], NULL AS [unit] , category.[order] AS [order]
FROM tblStock stock
JOIN dbo.Map category
ON stock.id = category.itemId
WHERE stock.id = #id
);
You should change dbo.anotherFunction to inline table valued function too if possible.
I suggest also reading about Inline vs Multistatement Table Function

Common Table Expression Select where last observation was at a location

I have the following tables
Location table
[ID] [int] IDENTITY(1,1) NOT NULL
Package table
[ID] [int] IDENTITY(1,1) NOT NULL
PackageObservation table
[PackageID] int
[LocationID] int
[Date] datetime
[Quantity] int
For a given location I want to select packages where the last observation of the package was at the location
What is the Transact SQL?
I think it involves a common table expression but I cant figure it out.
More information.
The following almost does it, but I don't really want to assume that the identity field is in date order
select max(id) ,packageid
from packageobservation o1
where not exists (
select o2.id from packageobservation o2
where o2.[date] > o1.[date] )
group by packageid
You can use following SQL statement:
DECLARE #locationID int = 1
SELECT po.PackageID, MAX(po.[Date]) AS DateAtLocation
FROM PackageObservation po
WHERE po.LocationID=#locationID
AND NOT EXISTS (SELECT * FROM PackageObservation po2
WHERE po2.PackageID = po.PackageID AND
po2.LocationID <> po.LocationID AND
po2.[Date] >= po.[Date] )
GROUP BY po.PackageID
For better speed you can also add combined index on [LocationID],[PackageID] and [Date].
Seems to me that using CTE is not necessary here.

Case when exists then select

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.