Trouble with UPDATE query - sql-server

I'm having problems trying to use the UPDATE query in SQL server.
this is my query:
UPDATE THEATER
SET SeatsAvailable = SeatsAvailable - 1
FROM THEATER
INNER JOIN CINEMA_SESSION ON CINEMA_SESSION.tid = THEATER.tid
WHERE THEATER.tid = 2 AND CINEMA_SESSION.sid = 2
-tid is the pk for THEATER
-sid is the pk for CINEMA_SESSION
When I use a SELECT statement to search for the SeatsAvailable in CINEMA_SESSION.sid = 3, which also has .tid=2, it also comes with the updated value for the CINEMA_SESSION.sid = 2.
The statement that I'm using is this simple Select statement:
SELECT THEATER.SeatsAvailable as SeatsAvailable FROM THEATER
INNER JOIN CINEMA_SESSION ON CINEMA_SESSION.tid = THEATER.tid
WHERE CINEMA_SESSION.sid = 3 AND THEATER.tid = 2
Here I want to specify the THEATER.tid which is the same in the UPDATE query but in a different CINEMA_SESSION.sid in which the SeatsAvailable should have remained the same.

The update statement is decrementing the value of SeatsAvailable on the Theatre table for tid=2 (the AND CINEMA_SESSION.sid = 2 is immaterial - you are updating the row on the THEATER table). Since tid is the primary key for theatre, there is only one record with that value, and that record is updated.
Your select for session sid=3 joins to theatre by the tid column, and it's matching the row where tid=2, which is why you're seeing the new value - it is matching the record that you just updated.
This may make more sense if you just look at the contents of the THEATER table (without any joins).
If you are trying to indicate that a seat has been booked for a particular session, then I would suggest you need to be updating a field on a different table. I'll leave that to you to work out. Hope this helps.

Related

How to do Multiple Joins with Merge Into?

I have these 3 tables
Company
id
Branch
id
Items
id
StockNumber
Company can have many branches and a branch can have many items.
Now I got to write a query that will either insert or update an item depending on conditions.
Some items can only appear once in the company and some items can appear in each branch.
The problem for me is the ones that can only appear once in the company. I think I am going to need to basically join all these tables together and do a check but I don't know how to do this join in a "Merge Into Sp"
I made a table type that looks like this
CREATE TYPE ItemTableType AS TABLE
(
BranchId INT,
CompanyId INT
Description nvarchar(Max),
StockNumber: INT
);
In my code I can pass the companyId into my tabletype
CREATE PROCEDURE dbo.Usp_upsert #Source ItemTableType readonly
AS
MERGE INTO items AS Target
using #Source AS Source
ON
// need to somehow look at the companyId so I can then find the right record reguardlesss of which branch it sits in.
Targert.CompanyId = source.CompanyId // can't do this just like this as Item doesn not have reference to company table.
Target.StockNumber = source.StockNumber
WHEN matched THEN
// update
WHEN NOT matched BY target THEN
// insert
Edit
Sample Data
Company
Id Name
1 'A'
2 'B'
Branch
Id name CompanyId
1 'A.1' 1
2 'A.2' 1
3 'B.1' 2
4 'B.2' 3
Item
Id Name StockNumber BranchId
1 Wrench 12345 1
2 Wrench 12345 3
3 Hammer 484814 2
4 Hammer 85285825 4
Now a bulk data is going to be sent into this SP via C# code and looks something like this
DataTable myTable = ...;
// Define the INSERT-SELECT statement.
string sqlInsert = "dbo.usp_InsertTvp"
// Configure the command and parameter.
SqlCommand mergeCommand = new SqlCommand(sqlInsert, connection);
mergeCommand.CommandType = CommandType.StoredProcedure;
SqlParameter tvpParam = mergeCommand.Parameters.AddWithValue("#Source", myTable);
tvpParam.SqlDbType = SqlDbType.Structured;
tvpParam.TypeName = "dbo.SourceTableType";
// Execute the command.
insertCommand.ExecuteNonQuery();
Now say when an import of records come in and the data looks like this
Wrench (Name), 12345 (StockNumber), 2 (BranchId..they are switching the branch of this item to another branch)
If I would just send this in then if I used BranchId + Stocknumber nothing would be updated and a new record would be inserted what would be wrong as now 2 branches have the same item(based on stockNumber)
If I would just use StockNumber then these 2 records would be updated.
1 Wrench 12345 1
2 Wrench 12345 3
Which is wrong as these records are from 2 different companies. Thus I need to also use the companyId, thus I need to also check the companyId.
EDIT (from comments):
I think I have to do Target Dot something. This is what I came up with so far:
MERGE INTO Items AS Target
using #Source AS Source
ON Source.CompanyID=(
SELECT TOP 1 Companies.Id
FROM Branches
INNER JOIN Companies
ON Branches.CompanyId = Companies.Id
INNER JOIN InventoryItems
ON Branches.Id = Target.BranchId
where Companies.Id = Source.CompanyId
and StockNumber = Source.StockNumber
)
The description of what you need to do is too vague for me to be specific, but you can simply do a query with JOINs as your source. I like to put it in a CTE to make it pretty like so:
WITH cte AS (SELECT query with JOINS)
MERGE INTO items AS Target
using cte AS Source
ON
EDIT: To also do a JOIN on the Target (items) you need to do it in the ON conditions:
WITH cte AS (SELECT query with JOINS)
MERGE INTO items AS Target
using cte AS Source
ON Source.CompanyID=(
SELECT TOP 1 CompanyId
FROM TableWithCompanyId
JOIN Target
ON JoinCondition=true
)...
I know yours involves two tables to get from items to company, but the example above shows you the technique that I believe you are missing.
EDIT 2, based on latest attempt:
Try it this way:
MERGE INTO Items AS Target
using #Source AS Source
ON Source.CompanyID=(
SELECT TOP 1 Companies.Id
FROM Branches
INNER JOIN Companies
ON Branches.CompanyId = Companies.Id
WHERE Branches.Id = Target.BranchId
)
and Target.StockNumber = Source.StockNumber

SQL join conditional either or not both?

I have 3 tables that I'm joining and 2 variables that I'm using in one of the joins.
What I'm trying to do is figure out how to join based on either of the statements but not both.
Here's the current query:
SELECT DISTINCT
WR.Id,
CAL.Id as 'CalendarId',
T.[First Of Month],
T.[Last of Month],
WR.Supervisor,
WR.cd_Manager as [Manager], --Added to search by the Manager--
WR.[Shift] as 'ShiftId'
INTO #Workers
FROM #T T
--Calendar
RIGHT JOIN [dbo].[Calendar] CAL
ON CAL.StartDate <= T.[Last of Month]
AND CAL.EndDate >= T.[First of Month]
--Workers
--This is the problem join
RIGHT JOIN [dbo].[Worker_Filtered]WR
ON WR.Supervisor IN (SELECT Id FROM [dbo].[User] WHERE FullName IN(#Supervisors))
or (WR.Supervisor IN (SELECT Id FROM [dbo].[User] WHERE FullName IN(#Supervisors))
AND WR.cd_Manager IN(SELECT Id FROM [dbo].[User] WHERE FullNameIN(#Manager))) --Added to search by the Manager--
AND WR.[Type] = '333E7907-EB80-4021-8CDB-5380F0EC89FF' --internal
WHERE CAL.Id = WR.Calendar
AND WR.[Shift] IS NOT NULL
What I want to do is either have the result based on the Worker_Filtered table matching the #Supervisor or (but not both) have it matching both the #Supervisor and #Manager.
The way it is now if it matches either condition it will be returned. This should be limiting the returned results to Workers that have both the Supervisor and Manager which would be a smaller data set than if they only match the Supervisor.
UPDATE
The query that I have above is part of a greater whole that pulls data for a supervisor's workers.
I want to also limit it to managers that are under a particular supervisor.
For example, if #Supervisor = John Doe and #Manager = Jane Doe and John has 9 workers 8 of which are under Jane's management then I would expect the end result to show that there are only 8 workers for each month. With the current query, it is still showing all 9 for each month.
If I change part of the RIGHT JOIN to:
WR.Supervisor IN (SELECT Id FROM [dbo].[User] WHERE FullName IN (#Supervisors))
AND WR.cd_Manager IN(SELECT Id FROM [dbo].[User] WHERE FullName IN(#Manager))
Then it just returns 12 rows of NULL.
UPDATE 2
Sorry, this has taken so long to get a sample up. I could not get SQL Fiddle to work for SQL Server 2008/2014 so I am using rextester instead:
Sample
This shows the results as 108 lines. But what I want to show is just the first 96 lines.
UPDATE 3
I have made a slight update to the Sample. this does get the results that I want. I can set #Manager to NULL and it will pull all 108 records, or I can have the correct Manager name in there and it'll only pull those that match both Supervisor and Manager.
However, I'm doing this with an IF ELSE and I was hoping to avoid doing that as it duplicates code for the insert into the Worker table.
The description of expected results in update 3 makes it all clear now, thanks. Your 'problem' join needs to be:
RIGHT JOIN Worker_Filtered wr on (wr.Supervisor in(#Supervisors)
and case when #Manager is null then 1
else case when wr.Manager in(#Manager) then 1 else 0 end
end = 1)
By the way, I don't know what you are expecting the in(#Supervisors) to achieve, but if you're hoping to supply a comma separated list of supervisors as a single string and have wr.Supervisor match any one of them then you're going to be disappointed. This query works exactly the same if you have = #Supervisors instead.

Updating column based on three tables

I know it's very unprofessional, but it's our business system so I can't change it.
I have three tables: t_posList, t_url, t_type. The table t_posList has a column named URL which is also stored in the table t_url (the ID of the table t_url is not saved in t_posList so I have to find it like posList.Url = t_url.Url).
The column t_posList.status of every data row should be updated to 'non-customer' (it will be a status id but lets keep it simple) if: the ID of t_url can NOT be found in t_type.url_id.
So the query has like two steps: first I have to get all of the data rows where t_posList.Url = t_url.Url. After this I have to check which ID's of the found t_url rows can NOT be found in t_type.url_id.
I really hope you know what I mean. Because our system is very unprofessional and my SQL knowledge is not that good I'm not able to make this query.
EDIT: I tried this:
UPDATE t_poslist SET status = (
SELECT 'non-customer'
FROM t_url, t_type
WHERE url in
(select url from t_url
LEFT JOIN t_type ON t_url.ID = t_type.url_id
WHERE t_type.url_id is null)
)
What about this?
UPDATE p
SET status = 'non-customer'
FROM t_poslist p
INNER JOIN t_url u ON u.url = p.url
WHERE NOT EXISTS
(
SELECT * FROM t_type t WHERE t.url_id = u.ID
)

sql server trigger to increment a column when another column decrements

I have a movies table with columns id, title, inventory and I added a column rental_count. I've found solutions online to increment rental_count after the row is updated but would it be possible to increment the rental count only when the inventory gets decremented (when someone rents something)?
Create trigger trigUpdateRentalCount
On Movies for Update
As
if Exists( Select * from inserted I
Join deleted d
On d.pk = i.pk
Where i.inventory = d.inventory - 1)
Update m Set rental_count += 1
From Movies m
join (inserted i Join deleted d
on i.pk = d.Pk
and i.inventory = d.inventory - 1)
on i.PK = m.PK
EDIT to explain trigger. In any trigger, the user has access to the set of rows being deleted or updated(with the old values) by the sql statement that caused the trigger to fire. This set can be accessed using the keyword deleted.
It also has access to all the rows being inserted or updated (with the new updated values), using the keyword inserted.
So if there exists any rows in the deleted table which match to a row in the inserted table, where the new inserted value for inventory is one less than the old value, then, for each such row, you want to update(increment) the rental_count field.
The trigger relies on an If Exists statement to determine if any such rows exist in inserted and deleted. Then if there are any, it increments the rental_count value in the matching row in the Movies table.

Computed column expression

I have a specific need for a computed column called ProductCode
ProductId | SellerId | ProductCode
1 1 000001
2 1 000002
3 2 000001
4 1 000003
ProductId is identity, increments by 1.
SellerId is a foreign key.
So my computed column ProductCode must look how many products does Seller have and be in format 000000. The problem here is how to know which Sellers products to look for?
I've written have a TSQL which doesn't look how many products does a seller have
ALTER TABLE dbo.Product
ADD ProductCode AS RIGHT('000000' + CAST(ProductId AS VARCHAR(6)) , 6) PERSISTED
You cannot have a computed column based on data outside of the current row that is being updated. The best you can do to make this automatic is to create an after-trigger that queries the entire table to find the next value for the product code. But in order to make this work you'd have to use an exclusive table lock, which will utterly destroy concurrency, so it's not a good idea.
I also don't recommend using a view because it would have to calculate the ProductCode every time you read the table. This would be a huge performance-killer as well. By not saving the value in the database never to be touched again, your product codes would be subject to spurious changes (as in the case of perhaps deleting an erroneously-entered and never-used product).
Here's what I recommend instead. Create a new table:
dbo.SellerProductCode
SellerID LastProductCode
-------- ---------------
1 3
2 1
This table reliably records the last-used product code for each seller. On INSERT to your Product table, a trigger will update the LastProductCode in this table appropriately for all affected SellerIDs, and then update all the newly-inserted rows in the Product table with appropriate values. It might look something like the below.
See this trigger working in a Sql Fiddle
CREATE TRIGGER TR_Product_I ON dbo.Product FOR INSERT
AS
SET NOCOUNT ON;
SET XACT_ABORT ON;
DECLARE #LastProductCode TABLE (
SellerID int NOT NULL PRIMARY KEY CLUSTERED,
LastProductCode int NOT NULL
);
WITH ItemCounts AS (
SELECT
I.SellerID,
ItemCount = Count(*)
FROM
Inserted I
GROUP BY
I.SellerID
)
MERGE dbo.SellerProductCode C
USING ItemCounts I
ON C.SellerID = I.SellerID
WHEN NOT MATCHED BY TARGET THEN
INSERT (SellerID, LastProductCode)
VALUES (I.SellerID, I.ItemCount)
WHEN MATCHED THEN
UPDATE SET C.LastProductCode = C.LastProductCode + I.ItemCount
OUTPUT
Inserted.SellerID,
Inserted.LastProductCode
INTO #LastProductCode;
WITH P AS (
SELECT
NewProductCode =
L.LastProductCode + 1
- Row_Number() OVER (PARTITION BY I.SellerID ORDER BY P.ProductID DESC),
P.*
FROM
Inserted I
INNER JOIN dbo.Product P
ON I.ProductID = P.ProductID
INNER JOIN #LastProductCode L
ON P.SellerID = L.SellerID
)
UPDATE P
SET P.ProductCode = Right('00000' + Convert(varchar(6), P.NewProductCode), 6);
Note that this trigger works even if multiple rows are inserted. There is no need to preload the SellerProductCode table, either--new sellers will automatically be added. This will handle concurrency with few problems. If concurrency problems are encountered, proper locking hints can be added without deleterious effect as the table will remain very small and ROWLOCK can be used (except for the INSERT which will require a range lock).
Please do see the Sql Fiddle for working, tested code demonstrating the technique. Now you have real product codes that have no reason to ever change and will be reliable.
I would normally recommend using a view to do this type of calculation. The view could even be indexed if select performance is the most important factor (I see you're using persisted).
You cannot have a subquery in a computed column, which essentially means that you can only access the data in the current row. The only ways to get this count would be to use a user-defined function in your computed column, or triggers to update a non-computed column.
A view might look like the following:
create view ProductCodes as
select p.ProductId, p.SellerId,
(
select right('000000' + cast(count(*) as varchar(6)), 6)
from Product
where SellerID = p.SellerID
and ProductID <= p.ProductID
) as ProductCode
from Product p
One big caveat to your product numbering scheme, and a downfall for both the view and UDF options, is that we're relying upon a count of rows with a lower ProductId. This means that if a Product is inserted in the middle of the sequence, it would actually change the ProductCodes of existing Products with a higher ProductId. At that point, you must either:
Guarantee the sequencing of ProductId (identity alone does not do this)
Rely upon a different column that has a guaranteed sequence (still dubious, but maybe CreateDate?)
Use a trigger to get a count at insert which is then never changed.

Resources