Select all rows if #Parameter = NULL - sql-server

I have a stored procedure with a parameter, #PolicySymbol, which can accept multiple values. For that I am using a split string function: StringOfStringsToTable
If user selects #ControlNo, then I want to bring data for that controlNo no matter what value is set for #PolicySymbol.
If #PolicySymbol = NULL I want to bring all data for controlNo no matter what policy symbol it related to.
Here is the code sample:
CREATE TABLE #Test (ControlNo int, policy_symbol varchar(50))
INSERT INTO #Test
VALUES (1111, 'CSE'), (2222, 'PRE'), (3333, 'CSE'), (4444, 'GLG'),
(4444, 'PRE'), (4444, 'GLS')
DECLARE
#ControlNo int = 1111,
#PolicySymbol varchar(50) = NULL
SELECT DISTINCT ControlNo, policy_symbol
FROM #Test
WHERE ControlNo = COALESCE(#ControlNo, ControlNo)
-- here, if parameter #PolicySymbol IS NULL I want to include all policy symbols
AND policy_symbol IN (SELECT String FROM [dbo].[StringOfStringsToTable] (#PolicySymbol, ','))
DROP TABLE #Test

A simple, logical OR does the trick.
WHERE ControlNo = COALESCE(#ControlNo, ControlNo)
-- If parameter #PolicySymbol IS NULL I want to include all policy symbols
AND (
#PolicySymbol IS NULL
OR policy_symbol IN (SELECT String FROM [dbo].[StringOfStringsToTable] (#PolicySymbol,','))
)

Related

How do I use update multiple rows in a stored procedure

I get batches of inventory items to update and I would like to eliminate calling the stored procedure multiple times and instead call it once with multiple values. I have done similar in oracle with the parameters as an array trick. I would like to do something similar for SQL Server.
I have a comma separated list of Sku
I have a comma separated list of Quantity.
I have a comma separated list of StoreIds.
The standard update is
Update Inventory
set quantity = #Quantity
where sku = #Sku and StoreId = #StoreId;
Table definition
CREATE TABLE Inventory
(
[Sku] NVARCHAR(50) NOT NULL,
[Quantity] DECIMAL NULL DEFAULT 0.0,
[StoreId] INT NOT NULL
}
My bad attempt at doing this
ALTER PROCEDURE UpdateList
(#Sku varchar(max),
#Quantity varchar(max),
#StoreId varchar(max))
AS
BEGIN
DECLARE #n int = 0;
DECLARE #skuTable TABLE = SELECT CONVERT(value) FROM STRING_SPLIT(#Sku, ',');
DECLARE #quantityTable = SELECT CONVERT(value) FROM STRING_SPLIT(#Quantity, ',');
DECLARE #StoreIdTable = SELECT CONVERT(value) FROM STRING_SPLIT(#StoreId , ',');
WHILE #n < #skuTable.Count
BEGIN
UPDATE inventoryItem
SET Quantity = #quantityTable
WHERE Sku = #skuTable AND StoreId = #StoreIdTable;
SELECT #n = #n + 1;
END
END
I am open to using temp tables as parameters instead of comma separated. This is being called from an Entity Framework 6 context object from the front end system.
It's a bad practice to pass tabular values in this way.
Best solution is to pass it as a "user defined table type", if possible,
otherwise, it's better to get JSON/XML parameter
and then you can update your table like this:
--[ Parameters ]--
DECLARE #json AS NVARCHAR(MAX) = '[{"Sku":"A","Quantity":1.4,"StoreId":1},{"Sku":"B","Quantity":2.5,"StoreId":2},{"Sku":"C","Quantity":3.6,"StoreId":3}]';
--[ Bulk Update ]--
UPDATE inventoryItem
SET Quantity = I.Quantity
FROM inventoryItem AS T
JOIN OPENJSON(#json) WITH (Sku NVARCHAR(50), Quantity DECIMAL(5,1),StoreId INT) AS I
ON I.Sku = T.Sku
AND I.StoreId = T.StoreId
It's a bad practice to pass tabular values as varchar columns parameters,
but if you still want to go this way, here is a working code:
--[ Parameters ]--
DECLARE #Sku VARCHAR(max) = 'A,B,C',
#Quantity VARCHAR(max) = '1.4,2.5,3.6',
#StoreId VARCHAR(max) = '1,2,3'
--[ Converting VARCHAR Parameters to Table #Inventory ]--
DROP TABLE IF EXISTS #Sku
SELECT IDENTITY(int, 1,1) AS RowNum,
T.value
INTO #Sku
FROM STRING_SPLIT(#Sku, ',') AS T
DROP TABLE IF EXISTS #Quantity
SELECT IDENTITY(int, 1,1) AS RowNum,
T.value
INTO #Quantity
FROM STRING_SPLIT(#Quantity, ',') AS T
DROP TABLE IF EXISTS #StoreId
SELECT IDENTITY(int, 1,1) AS RowNum,
T.value
INTO #StoreId
FROM STRING_SPLIT(#StoreId, ',') AS T
DROP TABLE IF EXISTS #Inventory
SELECT Sku.value AS Sku,
Quantity.value AS Quantity,
StoreId.value AS StoreId
INTO #Inventory
FROM #Sku AS Sku
JOIN #Quantity AS Quantity ON Quantity.RowNum = Sku.RowNum
JOIN #StoreId AS StoreId ON StoreId.RowNum = Sku.RowNum
--[ Bulk Update ]--
UPDATE inventoryItem
SET Quantity = I.Quantity
FROM inventoryItem AS T
JOIN #Inventory AS I
ON I.Sku = T.Sku
AND I.StoreId = T.StoreId
The above answers are correct for updates and answered my question. But I wanted to add the insert here as I am sure someone will be looking for both. Maybe I will come back an make a new question and answer it myself.
I think the JSON version is best for my issue because I am doing entity framework and serializing an object to JSON is a trivial task. The basic process is to create an inline temp table from the json string. Calling out the objects via a simple dot notation string. I would suggest making the object passed in as simple as possible and preferably one level of properties.
create or alter Procedure bulkInventoryInsert( #json AS NVARCHAR(MAX))
AS
BEGIN
INSERT into inventory
SELECT Sku, Quantity, StoreId FROM
OPENJSON(#json)
WITH(Sku varchar(200) '$.Sku',
Quantity decimal(5,1) '$.Quantity',
StoreId INT '$.StoreId');
END
DECLARE #json AS NVARCHAR(MAX) = '[{"Sku":"A","Quantity":1.4,"StoreId":2},{"Sku":"B","Quantity":2.5,"StoreId":3},{"Sku":"C","Quantity":3.6,"StoreId":2}]';
EXECUTE bulkInventoryInsert #json;
The key part to recognize is this section here:
SELECT Sku, Quantity, StoreId FROM
OPENJSON(#json)
WITH(Sku varchar(200) '$.Sku',
Quantity decimal(5,1) '$.Quantity',
StoreId INT '$.StoreId');
This is creating a temp table with columns that match the table that it will be inserted into. The "WITH" portion specifies the column name, type, and where in the Json string to get the value.
I hope this will help. Maybe when I get time I will do a question and answer for this.

SQLServer : Grouping and Replacing a COLUMN value with the DATA from other table, without UDF

I would like to replace the numbers in #CommentsTable column "Comments" with the equivalent text from #ModTable table, without using UDF in a single SELECT. May with a CTE. Tried STUFF with REPLACE, but no luck.
Any suggestions would be a great help!
Sample:
DECLARE #ModTable TABLE
(
ID INT,
ModName VARCHAR(10),
ModPos VARCHAR(10)
)
DECLARE #CommentsTable TABLE
(
ID INT,
Comments VARCHAR(100)
)
INSERT INTO #CommentsTable
VALUES (1, 'MyFirst 5 Comments with 6'),
(2, 'MySecond comments'),
(3, 'MyThird comments 5')
INSERT INTO #ModTABLE
VALUES (1, '[FIVE]', '5'),
(1, '[SIX]', '6'),
(1, '[ONE]', '1'),
(1, '[TWO]', '2')
SELECT T1.ID, <<REPLACED COMMENTS>>
FROM #CommentsTable T1
GROUP BY T1.ID, T1.Comments
**Expected Result:**
ID Comments
1 MyFirst [FIVE] Comments with [SIX]
2 MySecond comments
3 MyThird comments [FIVE]
Create a cursor, span over the #ModTable and do each replacement a time
DECLARE replcursor FOR SELECT ModPos, ModName FROM #ModTable;
OPEN replcursor;
DECLARE modpos varchar(100) DEFAULT "";
DECLARE modname varchar(100) DEFAULT "";
get_loop: LOOP
FETCH replcursor INTO #modpos, #modname
SELECT T1.ID, REPLACE(T1.Comments, #modpos, #modname)
FROM #CommentsTable T1
GROUP BY T1.ID, T1.Comments
END LOOP get_loop;
Of course, you can store the results in a temp table and get the results altogether in the end of loop.
You can use a while loop to iterate over the records and the mods. I slightly modified your #ModTable to have unique values for ID. If this is not your data structure, then you can use a window function like ROW_NUMBER() to get a unique value over which you can iterate.
Revised script example:
DECLARE #ModTable TABLE
(
ID INT,
ModName VARCHAR(10),
ModPos VARCHAR(10)
)
DECLARE #CommentsTable TABLE
(
ID INT,
Comments VARCHAR(100)
)
INSERT INTO #CommentsTable
VALUES (1, 'MyFirst 5 Comments with 6'),
(2, 'MySecond comments'),
(3, 'MyThird comments 5')
INSERT INTO #ModTABLE
VALUES (1, '[FIVE]', '5'),
(2, '[SIX]', '6'),
(3, '[ONE]', '1'),
(4, '[TWO]', '2')
declare #revisedTable table (id int, comments varchar(100))
declare #modcount int = (select count(*) from #ModTable)
declare #commentcount int = (select count(*) from #CommentsTable)
declare #currentcomment varchar(100) = ''
while #commentcount > 0
begin
set #modcount = (select count(*) from #ModTable)
set #currentcomment = (select Comments from #CommentsTable where ID = #commentcount)
while #modcount > 0
begin
set #currentcomment = REPLACE( #currentcomment,
(SELECT TOP 1 ModPos FROM #ModTable WHERE ID = #modcount),
(SELECT TOP 1 ModName FROM #ModTable WHERE ID = #modcount))
set #modcount = #modcount - 1
end
INSERT INTO #revisedTable (id, comments)
SELECT #commentcount, #currentcomment
set #commentcount = #commentcount - 1
end
SELECT *
FROM #revisedTable
order by id
I think the will work even though I generally avoid recursive queries. It assumes that you have consecutive ids though:
with Comments as
(
select ID, Comments, 0 as ConnectID
from #CommentsTable
union all
select ID, replace(c.Comments, m.ModPos, m.ModName), m.ConnectID
from Comments c inner join #ModTable m on m.ConnectID = c.ConnectID + 1
)
select * from Comments
where ConnectID = (select max(ID) from #ModTable)
=> CLR Function()
As I have lot of records in "CommentsTable" and the "ModTable" would have multiple ModName for each comments, finally decided to go with CLR Function. Thanks all of you for the suggestions and pointers.

How to check if a table valued parameter is empty or not inside of a where clause?

I am writing a function where I am passing a table valued parameter.
In some of the cases table valued parameter can be empty.
So, my function looks like below-
CREATE FUNCTION [dbo].[testTableValueParam]
(
#created_date datetime = null
,#Ids dbo.IdList readonly
)
RETURNS TABLE
AS
RETURN
(
SELECT top 10
name
,scores
,mgr_name
from dbo.employee
where
created_date = #created_date
and
employeeId in (select empid from #Ids) --ignore this condition when #Ids is empty.
)
My table type is created as below-
CREATE TYPE [dbo].[IdList] AS TABLE(
[empid] [nvarchar](11) NULL
)
I am calling my function from a c# code.
There are cases when the table value parameter will be empty and in those cases, when the table value parameter is empty, i want to ignore the condition in where clause.
I went through some of the links while searching my answer and the answers suggested in earlier posts didn't fix my problem.
So, right now, when #Ids parameter is empty, it gives me no record.
In some of the post they suggested not to pass a parameter for table value at all, and it will automatically treat it as an empty table.
But I have cases when I need to pass the parameter with data.
Some of the answers suggested, using if exist(select 1 from #Ids)
But, I can not use if exist in my where clause.
Please provide any suggestions.
Your responses are much appreciated.
Thanks.
You can use the NOT EXISTS operator something like....
CREATE FUNCTION [dbo].[testTableValueParam]
(
#created_date datetime = null
,#Ids dbo.IdList readonly
)
RETURNS TABLE
AS
RETURN
(
SELECT top 10
name
,scores
,mgr_name
from dbo.employee
where
(#created_date IS NULL OR created_date = #created_date)
and
(
NOT EXISTS (SELECT * FROM #Ids)
OR
employeeId in (select empid from #Ids)
)
)
CREATE FUNCTION [dbo].[testTableValueParam]
(
#created_date datetime = null
,#Ids dbo.IdList readonly
)
RETURNS TABLE
AS
RETURN
(
SELECT top 10
name
,scores
,mgr_name
from dbo.employee
where
created_date = #created_date
and
((select count(*) from #Ids) < 1 or employeeId in (select empid from #Ids))
employeeId in (select empid from #Ids) --ignore this condition when #Ids is empty.
)
Try this.
CREATE FUNCTION [dbo].[testTableValueParam]
(
#created_date datetime = null
,#Ids dbo.IdList readonly
)
RETURNS TABLE
AS
RETURN
(
IF EXISTS (SELECT 1 FROM #Ids)
BEGIN
SELECT TOP 10
name
,scores
,mgr_name
FROM dbo.employee
WHERE created_date = #created_date
AND employeeId in (select empid from #Ids) --ignore this condition when #Ids is empty.
END
ELSE
BEGIN
SELECT TOP 10
name
,scores
,mgr_name
FROM dbo.employee
WHERE created_date = #created_date
END
)

Insert from single table into multiple tables, invalid column name error

I am trying to do the following but getting an "Invalid Column Name {column}" error. Can someone please help me see the error of my ways? We recently split a transaction table into 2 tables, one containing the often updated report column names and the other containing the unchanging transactions. This leave me trying to change what was a simple insert into 1 table to a complex insert into 2 tables with unique columns. I attempted to do that like so:
INSERT INTO dbo.ReportColumns
(
FullName
,Type
,Classification
)
OUTPUT INSERTED.Date, INSERTED.Amount, INSERTED.Id INTO dbo.Transactions
SELECT
[Date]
,Amount
,FullName
,Type
,Classification
FROM {multiple tables}
The "INSERTED.Date, INSERTED.Amount" are the source of the errors, with or without the "INSERTED." in front.
-----------------UPDATE------------------
Aaron was correct and it was impossible to manage with an insert but I was able to vastly improve the functionality of the insert and add some other business rules with the Merge functionality. My final solution resembles the following:
DECLARE #TransactionsTemp TABLE
(
[Date] DATE NOT NULL,
Amount MONEY NOT NULL,
ReportColumnsId INT NOT NULL
)
MERGE INTO dbo.ReportColumns AS Trgt
USING ( SELECT
{FK}
,[Date]
,Amount
,FullName
,Type
,Classification
FROM {multiple tables}) AS Src
ON Src.{FK} = Trgt.{FK}
WHEN MATCHED THEN
UPDATE SET
Trgt.FullName = Src.FullName,
Trgt.Type= Src.Type,
Trgt.Classification = Src.Classification
WHEN NOT MATCHED BY TARGET THEN
INSERT
(
FullName,
Type,
Classification
)
VALUES
(
Src.FullName,
Src.Type,
Src.Classification
)
OUTPUT Src.[Date], Src.Amount, INSERTED.Id INTO #TransactionsTemp;
MERGE INTO dbo.FinancialReport AS Trgt
USING (SELECT
[Date] ,
Amount ,
ReportColumnsId
FROM #TransactionsTemp) AS Src
ON Src.[Date] = Trgt.[Date] AND Src.ReportColumnsId = Trgt.ReportColumnsId
WHEN NOT MATCHED BY TARGET And Src.Amount <> 0 THEN
INSERT
(
[Date],
Amount,
ReportColumnsId
)
VALUES
(
Src.[Date],
Src.Amount,
Src.ReportColumnsId
)
WHEN MATCHED And Src.Amount <> 0 THEN
UPDATE SET Trgt.Amount = Src.Amount
WHEN MATCHED And Src.Amount = 0 THEN
DELETE;
Hope that helps someone else in the future. :)
Output clause will return values you are inserting into a table, you need multiple inserts, you can try something like following
declare #staging table (datecolumn date, amount decimal(18,2),
fullname varchar(50), type varchar(10),
Classification varchar(255));
INSERT INTO #staging
SELECT
[Date]
,Amount
,FullName
,Type
,Classification
FROM {multiple tables}
Declare #temp table (id int, fullname varchar(50), type varchar(10));
INSERT INTO dbo.ReportColumns
(
FullName
,Type
,Classification
)
OUTPUT INSERTED.id, INSERTED.fullname, INSERTED.type INTO #temp
SELECT
FullName
,Type
,Classification
FROM #stage
INSERT into dbo.transacrions (id, date, amount)
select t.id, s.datecolumn, s.amount from #temp t
inner join #stage s on t.fullname = s.fullname and t.type = s.type
I am fairly certain you will need to have two inserts (or create a view and use an instead of insert trigger). You can only use the OUTPUT clause to send variables or actual inserted values ti another table. You can't use it to split up a select into two destination tables during an insert.
If you provide more information (like how the table has been split up and how the rows are related) we can probably provide a more specific answer.

Can I use #table variable in SQL Server Report Builder?

Using SQL Server 2008 Reporting services:
I'm trying to write a report that displays some correlated data so I thought to use a #table variable like so
DECLARE #Results TABLE (Number int
,Name nvarchar(250)
,Total1 money
,Total2 money
)
insert into #Results(Number, Name, Total1)
select number, name, sum(total)
from table1
group by number, name
update #Results
set total2 = total
from
(select number, sum(total) from table2) s
where s.number = number
select from #results
However, Report Builder keeps asking to enter a value for the variable #Results. It this at all possible?
EDIT: As suggested by KM I've used a stored procedure to solve my immediate problem, but the original question still stands: can I use #table variables in Report Builder?
No.
ReportBuilder will
2nd guess you
treats #Results as a parameter
Put all of that in a stored procedure and have report builder call that procedure. If you have many rows to process you might be better off (performance wise) with a #temp table where you create a clustered primary key on Number (or would it be Number+Name, not sure of your example code).
EDIT
you could try to do everything in one SELECT and send that to report builder, this should be the fastest (no temp tables):
select
dt.number, dt.name, dt.total1, s.total2
from (select
number, name, sum(total) AS total1
from table1
group by number, name
) dt
LEFT OUTER JOIN (select
number, sum(total) AS total2
from table2
GROUP BY number --<<OP code didn't have this, but is it needed??
) s ON dt.number=s.number
I've seen this problem as well. It seems SQLRS is a bit case-sensitive. If you ensure that your table variable is declared and referenced everywhere with the same letter case, you will clear up the prompt for parameter.
You can use Table Variables in SSRS dataset query like in my code where I am adding needed "empty" records for keep group footer in fixed postion (sample use pubs database):
DECLARE #NumberOfLines INT
DECLARE #RowsToProcess INT
DECLARE #CurrentRow INT
DECLARE #CurRow INT
DECLARE #cntMax INT
DECLARE #NumberOfRecords INT
DECLARE #SelectedType char(12)
DECLARE #varTable TABLE (# int, type char(12), ord int)
DECLARE #table1 TABLE (type char(12), title varchar(80), ord int )
DECLARE #table2 TABLE (type char(12), title varchar(80), ord int )
INSERT INTO #varTable
SELECT count(type) as '#', type, count(type) FROM titles GROUP BY type ORDER BY type
SELECT #cntMax = max(#) from #varTable
INSERT into #table1 (type, title, ord) SELECT type, N'', 1 FROM titles
INSERT into #table2 (type, title, ord) SELECT type, title, 1 FROM titles
SET #CurrentRow = 0
SET #SelectedType = N''
SET #NumberOfLines = #RowsPerPage
SELECT #RowsToProcess = COUNT(*) from #varTable
WHILE #CurrentRow < #RowsToProcess
BEGIN
SET #CurrentRow = #CurrentRow + 1
SELECT TOP 1 #NumberOfRecords = ord, #SelectedType = type
FROM #varTable WHERE type > #SelectedType
SET #CurRow = 0
WHILE #CurRow < (#NumberOfLines - #NumberOfRecords % #NumberOfLines) % #NumberOfLines
BEGIN
SET #CurRow = #CurRow + 1
INSERT into #table2 (type, title, ord)
SELECT type, '' , 2
FROM #varTable WHERE type = #SelectedType
END
END
SELECT type, title FROM #table2 ORDER BY type ASC, ord ASC, title ASC
Why can't you just UNION the two resultsets?
How about using a table valued function rather than a stored proc?
It's possible, only declare your table with '##'. Example:
DECLARE ##results TABLE (Number int
,Name nvarchar(250)
,Total1 money
,Total2 money
)
insert into ##results (Number, Name, Total1)
select number, name, sum(total)
from table1
group by number, name
update ##results
set total2 = total
from
(select number, sum(total) from table2) s
where s.number = number
select * from ##results

Resources