Stored Procedure - delete from table with joins - sql

I'm trying to learn SQL and I've run into a problem with stored procedures. I've got a tables structure like in the picture, where Repair.RepairerId and Repair.CarId are foreign keys for the appropriate tables.
What I need to do is to create a stored procedure that allows a user to delete entities from table Repair where user can select the car model and the repairer name in Microsoft SQL Server 2017.
For now I have the next code:
CREATE PROCEDURE [dbo].[DeleteRepairInfo]
#Name nvarchar(MAX),
#Model nvarchar(MAX)
AS
DELETE Repair.*
FROM Repair INNER JOIN Repairer ON Repair.RepairerId = Repairer.Id
INNER JOIN Car ON Repair.CarId = Car.Id
WHERE Car.Model LIKE #Model AND Repairer.Name LIKE #Name
GO
However SQL Editor in Visual Studio 2017 gives me the error:
SQL46010: Incorrect syntax near ..
Also all the INNER JOIN statements and their = signs are greyed out as well as words LIKE, AND, and the final LIKE. (I'm not sure if this is okay).
You can see this on the next picture:

I would write the logic as:
CREATE PROCEDURE [dbo].[DeleteRepairInfo] (
#Name nvarchar(MAX),
#Model nvarchar(MAX)
) AS
BEGIN
DELETE r
FROM Repair r INNER JOIN
Repairer rr
ON r.RepairerId = rr.Id INNER JOIN
Car c
ON r.CarId = c.Id
WHERE c.Model LIKE #Model AND rr.Name LIKE #Name;
END; -- DeleteRepairInfo
The issues with your query is the syntax Repair.*. That is not valid. Note some other things:
This introduces table aliases so the query is easier to write and to read.
The body of the stored procedure is surrounded by BEGIN/END so it is easy to see.
The END is commented with the name of the stored procedure.
The arguments are surrounded by parentheses.
These are all "optional". But writing clear code is a good habit to learn.

How are you?
I think the problem is that you're not using the DELETE properly
The follow example will help you!
CREATE PROCEDURE [dbo].[DeleteRepairInfo]
#Name nvarchar(MAX),
#Model nvarchar(MAX)
AS
DELETE FROM Repair as R
WHERE R.CarId in (select CarId from Car where Model = #Model)
and R.RepairerId in (select RepairerId from Repairer where Name = #Name)
GO
Assuming that you can have more than 1 car with the same model and more than one repairer with the same name!
Good luck :)

Related

How to apply DRY principle to SELECT in SQL procedures?

I have a number of SQL Stored Procedures querying the same table with different arguments and WHERE clauses. How can I define the columns in SELECT in a single place so they are shared across all similar procedures? Can you also share the performance or maintainability implication please?
CREATE PROCEDURE [dbo].[stp_Metadata_GetById]
#Id int = 0
AS
SELECT M.Id,
M.Name
-- A long list of column selections
FROM dbo.Metadata M
WHERE M.Id = #Id;
CREATE PROCEDURE [dbo].[stp_Metadata_GetByName]
#Name nvarchar(200)
AS
SELECT M.Id,
M.Name
-- A long list of column selections
FROM dbo.Metadata M
WHERE M.Name = #Name;
How can I define the columns in SELECT in a single place so they are shared across all similar procedures?
In short, you can't. SQL is not a procedural programming language, but a set based declarative one. Consequently, principles like DRY don't really hold up in the SQL world.
Your only option here is to follow #jarlh's advice and to make a single procedure that allows passing of several parameters to filter on different columns all within the same procedure. This will come with it's own issues though, as you now have a monolith procedure that you will need to monitor and maintain as your database grows and develops.
You just need to create one sp with 2 params along with default value is null.
After that, you can use boolean logic like #Id is null or M.Id = #Id
CREATE PROCEDURE [dbo].[stp_Metadata_GetBy]
#Id int = null,
#Name nvarchar(200) = null
AS
SELECT M.Id,
M.Name
-- A long list of column selections
FROM dbo.Metadata M
WHERE (#Id is null or M.Id = #Id)
and (#Name is null or M.Name = #Name);
How to use
exec stp_Metadata_GetBy #Id = 1 -- if you want to query by ID
exec stp_Metadata_GetBy #Name = 'abc' -- if you want to query by Name
Looks like this one 'CASE' expression whether to apply a WHERE condition to a query or not

Send an email with trigger, when I got the email the values were wrong

My question is same as in question heading and below is the code what I have tried.
ALTER TRIGGER [dbo].[Entrega_Insert]
ON [dbo].[Entrega]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #DataEntrega DATETIME, #IdEncomenda INT, #QTDEncomenda INT
DECLARE #IdVisita INT
SELECT #DataEntrega = DataEntrega, #IdEncomenda = b.IdEncomenda
FROM [dbo].[Entrega] AS a
INNER JOIN [dbo].[Encomenda] AS b ON a.[IdEncomenda] = b.[IdEncomenda]
INNER JOIN [dbo].[Visita] AS c ON b.[IdVisita] = c.[IdVisita]
--INNER JOIN
DECLARE #BigBody VARCHAR(500) = CAST(#DataEntrega AS VARCHAR(100)) + ' ' + CAST(#IdEncomenda AS VARCHAR(100))
EXEC msdb.dbo.sp_send_dbmail
#profile_name = 'Dc'
,#recipients = 'danny17kx#gmail.com'
,#subject = 'A sua encomenda foi processada e aceite.'
,#body = #BigBody
,#importance ='HIGH'
,#body_format='HTML'
END
I would strongly discourage your from attempting to send email through a trigger. Just the fact that you don't even know to use inserted suggests that you are not familiar enough with how SQL works. You are going to be locking the table (or part of it) while the email is attempted.
What can you do? The simplest is to write a stored procedure to send email and do the insert at the same time. This gives you more control over email failures.
The more "professional" solution would probably involve message queues. You would do the insert, then insert a message into a queue. At the other end, a listener would send the email and handle any issues with email failures.
You got the wrong values because your query is targeting the main table without any filtering, so you'll get a random values from the table.
You should use inserted table instead, which will store the last inserted values.
so your query should be like this :
SELECT TOP 1 #DataEntrega = DataEntrega, #IdEncomenda=b.IdEncomenda
FROM inserted AS a
INNER JOIN [dbo].[Encomenda] AS b ON a.[IdEncomenda]= b.[IdEncomenda]
INNER JOIN [dbo].[Visita] AS c ON b.[IdVisita] = c.[IdVisita]
Not sure if the INNER JOINs are useful here, but if IdEncomenda is already defined in Entrega table, then I think you'll be better off these joins.
Remember your method will only get one row, so if you insert multiple rows, you won't get all of them via email. So, you'll need to use other methods such as XML, COALESCE, or STUFF to concrete the results into #BigBody.

Passing the results of a query to a parameter in a stored procedure

I am trying to get the results of a stored procedure by passing the results of the query to my parameter.
ALTER PROCEDURE [DWH].[spAMBSiteAssetCountReport]
#Areaname, #SiteType, #EquipmentClass, #AssetStatus
#MaintenanceLocation varchar(Max),
#FLClassDescription varchar(max),
#EquipmentClass varchar(max),
#AssetStatus varchar(max)
As
Begin
SET NOCOUNT ON;
SET ANSI_WARNINGS OFF -- to suppress warning "string or binary data would be truncated"
SELECT
EC.ClassDescription, FL.Site,
SUBSTRING(FL.Site, CHARINDEX('-',FL.Site)+1,LEN(FL.Site)) SiteNo,
FL.SiteDesc, FL.FunctionalLocation, FL.Parish,
ST.SiteTypeName FLClassDescription, FL.MaintenanceLocationDesc,
Count(1) AS AssetCount
FROM
DWH.DimFunctionalLocation AS FL
INNER JOIN
DWH.DimEquipment AS EQ ON EQ.FunctionalLocationKey = FL.DW_FunctionalLocation_Key
INNER JOIN
DWH.DimEquipmentDetail AS EQD ON EQ.DW_Equipment_Key = EQD.EquipmentKey
INNER JOIN
DWH.DimEquipmentClass AS EC ON EC.DW_EquipmentClass_Key = EQD.DW_EquipmentDetail_Key
INNER JOIN
DWH.DimSiteType ST ON FL.SiteTypeKey = ST.DW_SiteType_Key
INNER JOIN
(select Item
from DWH.fnSplit(#FLClassDescription,',')) AS DNO ON (FL.SiteTypeKey = DNO.Item OR #FLClassDescription ='-1')
INNER JOIN
(select Item from DWH.fnSplit(#MaintenanceLocation,',')) AS ML ON (FL.MaintenanceLocationKey = ML.Item OR #MaintenanceLocation ='-1')
INNER JOIN
(select Item from DWH.fnSplit(#EquipmentClass,',')) AS FLC ON (EC.DW_EquipmentClass_Key = FLC.Item OR #EquipmentClass ='-1')
INNER JOIN
(select Item from DWH.fnSplit(#AssetStatus,',')) AS EQC ON ((EQD.CharacteristicName = EQC.Item AND ISNULL(LTRIM(RTRIM(EQD.CHARCharacteristicValue)),'') <> '') OR #AssetStatus ='All')
WHERE
FL.FLClassDescription IS NOT NULL
GROUP BY
EC.ClassDescription, FL.Site, FL.SiteDesc,
FL.FunctionalLocation, FL.Parish, ST.SiteTypeName,
FL.MaintenanceLocationDesc
END
I get results when I run the query but I need to pass the results to my parameter to get only distinct values for Equipment Class. The exec statement is below:
Exec DWH.spAMBSiteAssetCountReport #MaintenanceLocation=N'366,367,332,362,3,360,331,365,361,364,357,396,2,406,371,4,368,369,370,333,394,358,359,395,355,353,354,335,363,356,397,352,349,348,351,350,347,372,373,374,377,375,376,382,386,383,387,384,389,381,391,378,385,379,380,388,390',#FLClassDescription=N'3,4,5,2,1',#EquipmentClass = AssetCount,#AssetStatus=N'All'
I get a syntax error when trying to execute the stored procedure using the Asset Count as a valued parameter. Any help on this would be appreciated.
Thanks.
First, you declare the #AssetStatus parameter twice.
Second, you alias the count column as 'AssetCount', but don't use it anywhere else, so I'm not sure why you think that is causing a syntax error.
Lastly, you ask:
"I am trying to get the results of a stored procedure by passing the results of the query to my parameter."
What does this mean? Are you trying to recursively call a stored procedure from itself? Or are you trying to pass the results of another query INTO this stored procedure? More detail will help! If you respond with a comment I will try to help you along with this one - whatever your problem is, I don't think it'll be difficult to solve.

Entity Framework generated representation of a stored procedure signature is an Int instead of a record set

My stored procedure implementation is simple:
create Procedure [dbo].[GetNegotiationInfo] (
#negotiationId uniqueidentifier
) As
select NegotiationId, SellerId from NegotiationMaster where negotiationId = #negotiationId
Then I expected to be to write the following code after updating my model.
using (var db = new NegotiationContext())
{
var query = db.GetNegotiationInfo(NegotiationId).FirstOrDefault();
}
But I am getting the error "int does not contain a definition for FirstOrDefault()". I know that Entity Framework can have trouble generating complex stored procedures with temp tables etc but obviously my stored procedure doesn't get any simpler than this. Please note my stored procedure is this basic for my StackOverflow question and is not the actual stored procedure I will be using.
Stored procedures always return an integer. This is the status of the stored procedure call and will be set to NULL if not specified in the code. The general approach is that 0 indicates success and any other value indicates an error. Here is some documentation on the subject.
If you want to return a value from a stored procedure, use output parameters.
Alternatively, you might want a user defined function which returns a value to the caller. This can be a scalar or a table.
EDIT:
I would suggest that you look into user defined functions.
create function [dbo].[GetNegotiationInfo] (
#negotiationId uniqueidentifier
)
returns table
As
return(select NegotiationId, SellerId
from NegotiationMaster
where negotiationId = #negotiationId
);
In SQL, you would call this as:
select NegotiationId, SellerId
from dbo.GetNegotiationInfo(NegotiationId);
EDIT (in response to Aaron's comment):
This discussion is about SQL-only stored procedures. Entity Framework wraps stuff around stored procedures. The documentation mentioned in the comment below strongly suggests that EF should be returning the data from the last select in the stored procedure, but that you should be using ExecuteFunction -- confusingly even though this is a stored procedure. (Search for "Importing Stored Procedures that Return Types Other than Entities" in the document.)
Use SET NOCOUNT ON at the top of your procedure, the integer being returned is almost certainly the row count.
Like so:
ALTER Procedure [dbo].[GetNegotiationInfo] (
#negotiationId uniqueidentifier
) As
SET NOCOUNT ON;
select n.*,
s.firstname + ' ' + s.lastname sellername,
b.firstname + ' ' + b.lastname buyername,
substring(s.firstname, 1, 1) + substring(s.lastname, 1, 1) as sellerinit,
substring(b.firstname, 1, 1) + substring(b.lastname, 1, 1) as buyerinit,
s.email selleremail, b.email buyeremail,
a.ADDRESS1
from negotiationMaster n
inner join usermaster s on s.userid = n.sellerid
inner join usermaster b on b.userid = n.buyerid
inner join PropertyNegotiation p on p.NegotiationId = n.NegotiationId
inner join MyOtherDb..PROPERTIES a on a.property_id = p.PropertyId
where n.negotiationId = #negotiationId
return;
Its all about editing the "Function Import" in the Visual Studio Model Browser and creating a complex type for the return record set. Note, temp Tables in your stored procedure will be difficult for Entity Framework to inspect. see MSDN

Stored procedures and the tables used by them

Is there a way to know what are the tables used by one stored procedure by doing an SQL query?
Best regards, and thanks for the help.
P.S.: I'm using SQL Server 2005.
This article on TechRepublic
Finding dependencies in SQL Server 2005
describes a way to do that:
This tutorial will show how you can
write a procedure that will look up
all of the objects that are dependent
upon other objects.
Here is the code to create the system stored procedure for finding object dependencies:
USE master
GO
CREATE PROCEDURE sp_FindDependencies
(
#ObjectName SYSNAME,
#ObjectType VARCHAR(5) = NULL
)
AS
BEGIN
DECLARE #ObjectID AS BIGINT
SELECT TOP(1) #ObjectID = object_id
FROM sys.objects
WHERE name = #ObjectName
AND type = ISNULL(#ObjectType, type)
SET NOCOUNT ON ;
WITH DependentObjectCTE (DependentObjectID, DependentObjectName, ReferencedObjectName, ReferencedObjectID)
AS
(
SELECT DISTINCT
sd.object_id,
OBJECT_NAME(sd.object_id),
ReferencedObject = OBJECT_NAME(sd.referenced_major_id),
ReferencedObjectID = sd.referenced_major_id
FROM
sys.sql_dependencies sd
JOIN sys.objects so ON sd.referenced_major_id = so.object_id
WHERE
sd.referenced_major_id = #ObjectID
UNION ALL
SELECT
sd.object_id,
OBJECT_NAME(sd.object_id),
OBJECT_NAME(referenced_major_id),
object_id
FROM
sys.sql_dependencies sd
JOIN DependentObjectCTE do ON sd.referenced_major_id = do.DependentObjectID
WHERE
sd.referenced_major_id <> sd.object_id
)
SELECT DISTINCT
DependentObjectName
FROM
DependentObjectCTE c
END
This procedure uses a Common Table
Expression (CTE) with recursion to
walk down the dependency chain to get
to all of the objects that are
dependent on the object passed into
the procedure. The main source of data
comes from the system view
sys.sql_dependencies, which contains
dependency information for all of your
objects in the database.
Try sp_depends, although you should probably recompile the stored procedure to update the statistics in the database.
Look up sp_depends system stored proc.
I think that as long as the stored procedure and the tables are all in the same database then you can right click on the procedure in SSMS and click "View Dependencies". I don't know the query behind the dialog though...
As others indicated you can use the Dependancies stored procedures; however, in my experience and this was back on SQL Server 2000, the depandancies were not always reliable. In some cases they weren't being updated. You can always go to the sysComments table assuming your schema is not encrypted.
declare #crlfSearch varchar(max),#objectSearch varchar(max),#escapeSearch varchar(max)
set #crlfSearch=('%bid' + char(13)+'%')
set #objectSearch='%bid %'
set #escapeSearch ='%[[]Bid]%'
select distinct so.name
from syscomments sc
inner join sysobjects so
on sc.id=so.id
where text like #objectSearch or text like #crlfSearch
or text like #escapesearch
This query looks for three common cases you might have to add some but basically we find where the table name has a space after it, (This helps to limit cases where the table name is part of another table name), Has a return at the end of it, or is escaped within brackets.