SSRS Textbox action is executed only once - sql-server

I am trying to create the report that would have action link in the text box that would update the record in database. First of all I've created datasource with the "Execute in single transaction" setting on. I've created dataset that calls the stored procedure with param:
Then specified the param:
I've created the table where the last column is "Action".
In the TextBox properties, in the Action menu set to go to the same report and set param value to the one from the table:
The thing is that it's working only once and if I click on the same field again then the procedure is not executed. I guess that param is not set, but I am not sure.
UPDATE:
After some playing I see, that variable is set so the problem is not in variable. Additionally if I click on another row then this row is updated as expected. After that I can updated the first row also, so the problem is that I can't update the same row twice in a roll.
CREATE PROCEDURE [dbo].[change_salesperson_status]
#code AS CHAR(5)
AS
BEGIN
INSERT INTO dbo.test VALUES (#code);
IF (#code = 'test') RETURN 1;
UPDATE dbo.salespersons
SET status_code = CASE WHEN status_code = 'I' THEN 'A' ELSE 'I' END
WHERE code = #code;
END;
GO
CREATE TABLE dbo.test
(
a VARCHAR(200)
)
UPDATE2:
I was able to workaround this. I've added internal datetime variable with default value =NOW() this forced SSRS to refresh report every time.

This isn't really an answer, it's a guide to how I would do this but too long to put as a comment.
I've done quite a few of these reports and never had any issues. I suspect your workflow is not quite right.
What I normally do is write a dataset query that accepts parameters that update the database unless the default values are passed.
I'm not sure what you are trying to do but if we take a simple example, creating a report called, say myReport which makes an employee inactive if a column is clicked then we might have a simple table
EmpID EmpName MakeInactive
1 Dave click
2 Bob click
The dataset would look something like this...
SET NOCOUNT ON
IF #EmployeeID > 0
BEGIN
UPDATE myTable SET Active = 0 WHERE EmpID = #EmployeeID
END
SELECT EmpID, EmpName FROM myTable
The myReport report would have a single parameter #EmployeeID in this simple case and the default value would be -1 so when we first run the report, no updates occur.
In the last column you would do as you have already, that is, set the action to call the same report (myReport) with the #EmployeeID parameter set to the EmpID from the first column.
That's all that I would do, I don't use single transactions or anything else, I just do as above and it works.

Related

Use Stored Procedure as Default Value of a column

I'm trying to a use stored procedure as a default value of a column. It's called "dbo.GetAdmin"
Basically, this stored procedure looks up for the user id "admin" and if it doesn't exist, the SP inserts it then returns it as a user record.
Now I want to use this SP as a default value for a column like this :
ALTER TABLE [dbo].[Transaction] ADD CONSTRAINT [DF_Transaction_From] DEFAULT ([dbo].[GetAdmin]) FOR [From]
But I get this error :
The name "dbo.GetAdmin" is not permitted in this context. Valid expressions are constants, constant expressions, and (in some contexts) variables. Column names are not permitted.
In this related post, an user told us that we have to use a function instead of a stored procedure : https://stackoverflow.com/a/2851783/5591761
Because user-defined functions are allowed as a default value for a column.
But according to the documentation of a function :
Functions are computed values and cannot perform permanent environmental changes to SQL Server (i.e., no INSERT or UPDATE statements allowed).
So, because I have to insert the user if it doesn't exist, I have to use a stored procedure instead of a function. How can I workaround this issue ?
An insert trigger would allow this if it had to be done. I'm assuming the Admin value does not change per record in the insert should somebody insert more than one record. Otherwise, a loop would be required to execute the procedure for each record. I would think hard about alternatives before doing this. It might be that this has to be done for consistency. Depending upon what the procedure is doing, it might be possible to do a set based query in the trigger by pulling the proc code into the trigger.
For example, trigger code might be something like
SET NOCOUNT ON
IF NOT EXISTS(SELECT * FROM inserted) RETURN -- no inserted records (where clause)
DECLARE #AdminId int -- the same value for all records inserted by statement
EXECUTE #AdminId = [dbo].[GetAdmin]
IF #AdminId IS NULL RETURN -- don't bother updating NULL to NULL
UPDATE t
SET [From] = #AdminId
FROM [dbo].[Transaction] t
INNER JOIN inserted i.record_key = t.record_key -- match each inserted to itself uniquely
WHERE i.[From] IS NULL -- no value was provided upon insert
RETURN
Also, error handling can be tricky calling a procedure from a trigger.
If you DBA can insert a record upon request in SSMS without supplying the ADMIN value, then you might not need this.

SSRS - Executing report when parameter is NULL

I'm creating a report in SSRS using a stored procedure. My report is simple, basically pulls basic information of a client in different programs.
In my stored-procedure I have two parameters: #StartDate DATETIME and #VendorId INT = NULL.
I'm setting #Vendorid to NULL because I would like to get all clients in every program.
I have two options in my sproc:
IF (#VendorID > 0) --Select Program
BEGIN
INSERT INTO #Report
SELECT * FROM table1 WHERE VendorId = #VendorId
END
IF (#VendorID IS NULL) --All Programs
BEGIN
INSERT INTO #Report
SELECT * FROM table1
END
This report gives me the flexibility to get clients from different programs from the day they were enrolled. Hence, if I want to get all clients in every program I simply execute the sproc with the #StartDate parameter. I would like to do the same in SSRS. I just can't figure how to set the #vendorid parameter function the same way in my sproc.
Any insight would be helpful!
AS you know generally in SSRS you can have parameters as Query string or selected value
so
Just add
SELECT * FROM table1 WHERE #VendorId=-1 OR VendorId = #VendorId
To specify a custom default value here -1
Switch to Design view.
In the Report Data pane, right-click #VendorId, and then click Parameter Properties.
Click Default Values > Specify values > Add. A new value row is added.
In Value, type -1.
Click OK.
Preview the report.
You have drop down and you can use -1 as none selected.
https://learn.microsoft.com/en-us/sql/reporting-services/tutorial-add-a-parameter-to-your-report-report-builder?view=sql-server-ver15

How to Update or Insert a record using SQL Merge

I have a windows forms application that needs to edit an existing record if it already exists and create it if it does not. I'm using SQL Server 2008 R2. My application reads data from various tables which includes an ID field for the output table if a record already exists.
The ID field is blank if a new record is being created. The ID field is the primary key and an Identity (auto increment) field for the destination table.
I have created a stored procedure using MERGE that I hope will create a new record or update the existing one. The update part is working but I can't figure out what to do with the ID field when creating.
When doing an update I pass in an ID Parameter and the existing record is located. Obviously if it is a new record I won't have an ID yet but I can't then leave that Parameter out or I get an unassigned variable error as you would expect.
Here is my stored procedure. Am I just barking up the wrong tree here
somewhere?
Should I just create two stored procedures and call Update if I have and ID and Call Create if I don't have and ID?
Thanks for any assistance.
USE [Insurance]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[CreateModifyValuation]
-- Add the parameters for the stored procedure here
#ValuationID int,
#OwnersCorporationID int,
#ValDate datetime,
#ValuerID int,
#Amount money,
#Printed bit,
#Approved bit,
#Notes varchar(max),
#MultiplierValue money,
#MultiplierClass char(10),
#Adjustment money,
#SubmittedDate datetime
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
Merge Valuation as Target
USING (Select
#ValuationID,
#OwnersCorporationID,
#ValDate,
#ValuerID,
#Amount,
#Printed,
#Approved,
#Notes,
#MultiplierValue,
#MultiplierClass,
#Adjustment,
#SubmittedDate
)
As Source(
ValuationID,
OwnersCorporationID,
ValDate,
ValuerID,
Amount,
Printed,
Approved,
Notes,
MultiplierValue,
MultiplierClass,
Adjustment,
SubmittedDate
)
ON Source.ValuationID = Target.ValuationID
WHEN MATCHED THEN
UPDATE SET
Target.OwnersCorporationID = Source.OwnersCorporationID,
Target.ValDate = Source.ValDate,
Target.ValuerID = Source.ValuerID,
Target.Amount = Source.Amount,
Target.Printed = Source.Printed,
Target.Approved = Source.Approved,
Target.Notes = Source.Notes,
Target.MultiplierValue = Source.MultiplierValue,
Target.MultiplierClass = Source.MultiplierClass,
Target.Adjustment = Source.Adjustment,
Target.SubmittedDate = Source.SubmittedDate
WHEN NOT MATCHED BY Target THEN
INSERT (
OwnerscorporationID,
ValDate,
ValuerID,
Amount,
Printed,
Approved,
Notes,
MultiplierValue,
MultiplierClass,
Adjustment,
SubmittedDate
)
VALUES (
Source.OwnersCorporationID,
Source.ValDate,
Source.ValuerID,
Source.Amount,
Source.Printed,
Source.Approved,
Source.Notes,
Source.MultiplierValue,
Source.MultiplierClass,
Source.Adjustment,
Source.SubmittedDate
);
END
I feel like I cheated but it's only one line so how bad can it be :)
In My SQL I added this line before the "SET NOCOUNT ON;"
(Funny if I put it after SET NOCOUNT ON I get a syntax error)
if (#ValuationID = 0) set #ValuationID = null
Then in my C# code I set the ID to 0 for a new record and it seems to work after a couple of tests. There may be a better way to do this but like most things in life you stop looking once it works.
Thanks again to those who commented.
David

How do I add a “last modified” and "created" column in a SQL Server table?

I'm design a new db schema for a SQL Server 2012 database.
Each table should get two extra columns called modified and created which should be automatically change as soon a row gets inserted or updated.
I don't know how rather the best way to get there.
I assuming that trigger are the best way to handle it.
I was trying to find examples with triggers.. but the tutorials which I found insert data in another table etc.
I assumed it's a quite common scenario but I couldn't find the answer yet.
The created column is simple - just a DATETIME2(3) column with a default constraint that gets set when a new row is inserted:
Created DATETIME2(3)
CONSTRAINT DF_YourTable_Created DEFAULT (SYSDATETIME())
So when you insert a row into YourTable and don't specify a value for Created, it will be set to the current date & time.
The modified is a bit more work, since you'll need to write a trigger for the AFTER UPDATE case and update it - you cannot declaratively tell SQL Server to do this for you....
Modified DATETIME2(3)
and then
CREATE TRIGGER updateModified
ON dbo.YourTable
AFTER UPDATE
AS
UPDATE dbo.YourTable
SET modified = SYSDATETIME()
FROM Inserted i
WHERE dbo.YourTable.PrimaryKey = i.PrimaryKey
You need to join the Inserted pseudo table which contains all rows that were updated with your base table on your primary key for that table.
And you'll have to create this AFTER UPDATE trigger for each table that you want to have a modified column in.
Generally, you can have the following columns:
LastModifiedBy
LastModifiedOn
CreatedBy
CreatedOn
where LastModifiedBy and CreatedBy are references to a users table (UserID) and the LastModifiedOn and CreatedOn columns are date and time columns.
You have the following options:
Solution without triggers - I have read somewhere that "The best way to write triggers is not to write such." and you should know that generally they are hurting the performance. So, if you can avoid them it is better to do so, even using triggers may look the easiest thing to do in some cases.
So, just edit all you INSERT and UPDATE statements to include the current UserID and current date and time. If such user ID can not be defined (anonymous user) you can use 0 instead and the default value of the columns (in case no user ID is specified will be NULL). When you see NULL values are inserted you should find the "guilty" statements and edit it.
Solution with triggers - you can created AFTER INSERT, UPDATE trigger and populated the users columns there. It's easy to get the current date and time in the context of the trigger (use GETUTCDATE() for example). The issue here is that the triggers do not allowed passing/accepting parameters. So, as you are not inserting the user ID value and you are not able to pass it to the trigger. How to find the current user?
You can use SET CONTEXT_INFO and CONTEXT_INFO. Before all you insert and update statements you must use the SET CONTEXT_INFO to add the current user ID to the current context and in the trigger you are using the CONTEXT_INFO function to extract it.
So, when using triggers you again need to edit all your INSERT and UPDATE clauses - that's why I prefer not to use them.
Anyway, if you need to have only date and time columns and not created/modified by columns, using triggers is more durable and easier as you are not going to edit any other statements now and in the future.
With SQL Server 2016 we can now use the SESSION_CONTEXT function to read session details. The details are set using sp_set_session_context (as read-only or read and write). The things are a little bit user-friendly:
EXEC sp_set_session_context 'user_id', 4;
SELECT SESSION_CONTEXT(N'user_id');
A nice example.
Attention, above works fine but not in all cases,
I lost a lot of time and found this helpfull:
create TRIGGER yourtable_update_insert
ON yourtable
AFTER UPDATE
as
begin
set nocount on;
update yourtable set modified=getdate(), modifiedby = suser_sname()
from yourtable t
inner join inserted i on t.uniqueid=i.uniqueid
end
go
set nocount on; is needed else you get the error:
Microsoft SQL Server Management Studio
No row was updated.
The data in row 5 was not committed.
Error Source: Microsoft.SqlServer.Management.DataTools.
Error Message: The row value(s) updated or deleted either do not make the row unique or they alter multiple rows(2 rows).
Correct the errors and retry or press ESC to cancel the change(s).
OK Help
CREATE TRIGGER [dbo].[updateModified]
ON [dbo].[Transaction_details]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
UPDATE dbo.Transaction_details
SET ModifedDate = GETDATE() FROM dbo.Transaction_details t JOIN inserted i ON
t.TransactionID = i.TransactionID--SYSDATETIME()
END
One important thing to consider is that you should always have the inserted / updated time for all of your tables and rows be from the same time source. There is a danger - if you do not use triggers - that different applications making direct updates to your tables will be on machines that have different times on their clocks, or that there will not be consistent use of local vs. UTC in the application layer.
Consider a case where the system making the insert or update query that directly sets the updated / modified time value has a clock that is 5 minutes behind (unlikely, but worth considering) or is using local time versus UTC. If another system is polling using an interval of 1 minute, it might miss the update.
For a number of reasons, I never expose my tables directly to applications. To handle this situation, I create a view on the table explicitly listing the fields to be accessed (including the updated / modified time field). I then use an INSTEAD OF UPDATE, INSERT trigger on the view and explicitly set the updatedAt time using the database server's clock. This way I can guarantee that the timebase for all records in the database is identical.
This has a few benefits:
It only makes one insert to the base table and you don't have to
worry about cascading triggers being called
It allows me to control at the field level what information I expose
to the business layer or to other consumers of my data
It allows me to secure the view independently from the base table
It works great on SQL Azure.
Take a look at this example of the trigger on the view:
ALTER TRIGGER [MR3W].[tgUpdateBuilding] ON [MR3W].[vwMrWebBuilding]
INSTEAD OF UPDATE, INSERT AS
BEGIN
SET NOCOUNT ON
IF EXISTS(SELECT * FROM DELETED)
BEGIN
UPDATE [dbo].[Building]
SET
,[BuildingName] = i.BuildingName
,[isActive] = i.isActive
,[updatedAt] = getdate()
FROM dbo.Building b
inner join inserted i on i.BuildingId = b.BuildingId
END
ELSE
BEGIN
INSERT INTO [dbo].[Building]
(
[BuildingName]
,[isActive]
,[updatedAt]
)
SELECT
[BuildingName]
,[isActive]
,getdate()
FROM INSERTED
END
END
I hope this helps, and I would welcome comments if there are reasons this is not the best solution.
This solution might not work for all use cases but wherever possible its a very clean way.
Create an stored procedure for inserting/updating row in table and only use this sp for modifying the table. In stored procedure you can always set created and updated column as required. e.g. setting updatedTime = GetUTCTime()

Upsert - Efficient Update or Insert in VB.Net, SQL Server

I'm trying to understand how to streamline the process of inserting a record if none exists or updating a record if it already exists. I'm not using stored procedures, although maybe that would be the most efficient way of doing this.
The actual scenario in which this is necessary is saving a user preference/setting to my SettingsUser table.
In MS Access I would typically pull a DAO recordset looking for the specified setting. If the recordset comes back empty then I know I need to add a new record which I can do with the same recordset object. On the other hand, if it isn't empty, I can just update the setting's value right away. In theory, this is only two database operations.
What is the recommended way of doing this in .NET?
A stored procedure is certainly an easy way to do that. There you can try to update the record, and if no record changes, you add it. Example:
create procedure UpateUserSetting
#UserId int,
#Setting int
as
set nocount on
update UserSetting
set Setting = #Setting
where UserId = #UserId
if (##rowcount = 0) begin
insert into UserSetting (
UserId, Setting
) values (
#UserId, #Setting
)
end
You can do the same without a stored procedure. Then you would first run the update, and check the number of affected rows, which you get returned from the ExecuteNonQuery method, and do the insert in another query if needed.

Resources