I have the following table:
SubjectID AttributeID ValueID
1 1 2
1 1 3
1 2 1
2 1 3
2 2 1
1 3 1
An attribute can have multiple values (multiple appearances in the above table for the same attribute).
There is no constraint of how many appearances for the same attribute (different value).
I wan't to Update the Subject with SubjectID=1, to change the ValueID to only 1 where the AttributeID is 1, so
Before:
Select * from Subject WHERE SubjectID=1 AND AttributeID=1
--returns:
SubjectID AttributeID ValueID
1 1 2
1 1 3
After:
Select * from Subject WHERE SubjectID=1 AND AttributeID=1
--returns:
SubjectID AttributeID ValueID
1 1 1
I am doing this with a stored procedure with optional parameters (all null and update only the attributes that were provided), now this is not an issue. My question is:
What is the best practice to update this rows? I see the following answers as viable:
Delete all the rows that contain the specified attribute, then insert the new ones;
If there is only one attribute of that type (for the specified subject) update that one (not a good solution if there are more than 1 for the same attribute)
Any other ideas?
You could update just one row and then delete the others like so:
set rowcount 1;
update Subject
set ValuedID = 1
where SubjectID = 1
and AttributeID = 1;
set rowcount 0;
delete Subject
where SubjectID = 1
and AttributeID = 1
and ValuedID <> 1;
Using set rowcount is deprecated, use top (n) instead.
Important
Using SET ROWCOUNT will not affect DELETE, INSERT, and UPDATE statements in a future release of SQL Server. Avoid using SET ROWCOUNT with DELETE, INSERT, and UPDATE statements in new development work, and plan to modify applications that currently use it. For a similar behavior, use the TOP syntax. For more information, see TOP (Transact-SQL).
update top (1) Subject
set ValueID = 1
where SubjectID = 1
and AttributeID = 1;
delete Subject
where SubjectID = 1
and AttributeID = 1
and ValueID <> 1;
rextester demo: http://rextester.com/ATDKI87027
returns:
+-----------+-------------+---------+
| SubjectID | AttributeID | ValueID |
+-----------+-------------+---------+
| 1 | 1 | 1 |
| 1 | 2 | 1 |
| 2 | 1 | 3 |
| 2 | 2 | 1 |
| 1 | 3 | 1 |
+-----------+-------------+---------+
Related
I have a table that contains tree-like data (hierarchic design). Here is a small sample:
+----+----------+-----------+-------+----------+---------+
| ID | ParentID | Hierarchy | Order | FullPath | Project |
+----+----------+-----------+-------+----------+---------+
| 1 | null | 1 | 1 | 1 | 1 |
| 2 | null | 2 | 2 | 2 | 1 |
| 3 | 1 | 1.1 | 1 | 1-3 | 1 |
| 4 | 1 | 1.2 | 2 | 1-4 | 1 |
| 5 | 4 | 1.2.1 | 1 | 1-4-5 | 1 |
| 6 | 2 | 2.1 | 1 | 2-6 | 1 |
| 7 | null | 3 | 1 | 1 | 2 |
+----+----------+-----------+-------+----------+---------+
Project indicates which project owns the hierarchic dataset
ParentID is the ID of the parent node, it has a foreign key on ID.
Order is the rank of the element in one branch. For example, IDs 1, 2 and 7 are on the same node while 3 and 4 are in another.
FullPath shows the order using the ID (it's for system use and performance reasons).
Hierarchy is the column displayed to the user, which displays the hierarchy to the UI. It auto calculates after every insert, update and delete, and it's the one I'm having issues.
I created a procedure for deletion elements in the table. It receives as input the ID of the element to delete and deletes it, along with it's children if any. Then, it recalculates the FullPath and the Order Column .That works.
Problems is when I try to update the Hierarchy column. I use this procedure:
SELECT T.ID,
T.ParentID,
CASE WHEN T.ParentID IS NOT NULL THEN
CONCAT(T1.Hierarchy, '.', CAST(T.Order AS NVARCHAR(255)))
ELSE
CAST(T.Order AS NVARCHAR(255))
END AS Hierarchy
INTO #tmp
FROM t_HierarchyTable T
LEFT JOIN t_HierarchyTable T1
ON T1.ID = T.ParentID
WHERE Project = #Project --Variable to only update the current project for performance
ORDER BY T.FullPath
--Update the table with ID as key on tmp table
This fails when I delete items that have lower order than others and they have children.
For example, if I delete the item 3, item 4 Hierachy will be corrected (1.1), BUT its child won't (it will stay at 1.2.1, while it should be 1.1.1). I added the order by to make sure parents where updated first, but no change.
What is my error, I really don't know how to fix this.
I managed to update the hierarchy with a CTE. Since I have the order, I can append it to Hierarchy, based on the previous branch (parent) who is already updated.
;WITH CODES(ID, sCode, iLevel) AS
(
SELECT
T.[ID] AS [ID],
CONVERT(VARCHAR(8000), T.[Order]) AS [Hierarchy],
1 AS [iLevel]
FROM
[dbo].[data] AS T
WHERE
T.[ParentID] IS NULL
UNION ALL
SELECT
T.[ID] AS [ID],
P.[Hierarchy] + IIF(RIGHT(P.[Hierarchy], 1) <> '-', '-', '') + CONVERT(VARCHAR(8000), T.[Order]) AS [Hierarchy],
P.[iLevel] + 1 AS [iLevel]
FROM
[dbo].[data] AS T
INNER JOIN CODES AS P ON
P.[ID] = T.[ParentID]
WHERE
P.[iLevel] < 100
)
SELECT
[ID], [Hierarchy], [iLevel]
INTO
#CODES
FROM
CODES
I have a source table that has data identical to my target table. When I try to run a merge statement, it fails with the error
merge can't update a target row multiple times.
So My Question is since they are identical why SQL did succeed but with 0 rows affected instead. Please help me understand this.
By the way, My syntax is correct because in my initial insert it succeeded, the problem is if re-run it again.
Thank you.
target table and the source table has the same data.
WHEN MATCHED AND ISNULL(T.VALUE,'') <> ISNULL(S.VALUE,'')
COL1 COL2 COL3 VALUE DATE
1 A TYPE 3 2019-01-02
2 B KIND 4 2019-01-03
1 A COLOR 0 2019-01-02
2 B KIND 0 2019-01-03
MERGE TargetTable T
USING
(
SELECT COL1,
COL2,
COL3,
VALUE,
DATE
FROM SourceTable S
) s
ON
(
S.COL1 = T.COL1
AND S.COL2 = T.COL2
AND S.COL3 = T.COL3
AND S.DATE = T.DATE
)
WHEN MATCHED AND
(
ISNULL(S.VALUE,'') <> ISNULL(T.VALUE,'')
)
THEN UPDATE
SET
T.VALUE = S.VALUE
WHEN NOT MATCHED
THEN INSERT VALUES
(
S.COL1
,S.COL2
,S.COL3
,S.VALUE
,S.DATE
);
For better Unserstanding of Merge :
MERGE is a DML statement (data manipulation language).
Also called UPSERT (Update-Insert).
It tries to match source (table / view / query) to a target (table / updatable view) based on your defined conditions and then based on the matching results it insert/update/delete rows to/in/of the target table.
MERGE (Transact-SQL)
create table src (i int, j int);
create table trg (i int, j int);
insert into src values (1,1),(2,2),(3,3);
insert into trg values (2,20),(3,30),(4,40);
merge into trg
using src
on src.i = trg.i
when not matched by target then insert (i,j) values (src.i,src.j)
when not matched by source then update set trg.j = -1
when matched then update set trg.j = trg.j + src.j
;
select * from trg order by i
+---+----+
| i | j |
+---+----+
| 1 | 1 |
+---+----+
| 2 | 22 |
+---+----+
| 3 | 33 |
+---+----+
| 4 | -1 |
+---+----+
Source : Stackoverflow SQL Merge
I couldn't reproduce the error, but found something interesting
SQL DEMO
As you mention the first merge run perfect, but in my case the second merge says update 2 rows.
So I modify the 2nd merge to detect what rows were updated.
WHEN MATCHED AND
(
ISNULL(S.VALUE,'') <> ISNULL(T.VALUE,'')
)
THEN UPDATE
SET T.VALUE = S.VALUE + 10
OUTPUT
+------+------+-------+-------+---------------------+
| COL1 | COL2 | COL3 | VALUE | DATE |
+------+------+-------+-------+---------------------+
| 1 | A | TYPE | 3 | 02/01/2019 00:00:00 |
| 2 | B | KIND | 10 | 03/01/2019 00:00:00 |
| 1 | A | COLOR | 0 | 02/01/2019 00:00:00 |
| 2 | B | KIND | 14 | 03/01/2019 00:00:00 |
+------+------+-------+-------+---------------------+
Because you have 2 rows with the exact match (COL1, COL2, COL3, DATE) the system is telling you don't know which one update with which row.
But that doesn't explain why on my demo work as expected.
So my suggestion is you have to add a PK to your table to make sure the merge happen on the right rows.
Lets say I have the following table:
PKID | UID | FKID
-----------------
1 | ABC | 1
2 | BCD | 2
3 | CDE | 2
4 | DEF | 1
5 | EFG | 3
What I want to do is block deletes with a trigger (or other way if there is a better way to do this) but only for the rows where FKID = 1 but still allow other rows to be deleted. So, if someone types DELETE FROM sampleTable I would want only rows 2, 3, and 5 to be deleted, and 1 and 4 should remain.
Try this.
CREATE TRIGGER dbo.trg_tablename_delete ON dbo.tablename
FOR DELETE
AS
SET NOCOUNT ON
IF EXISTS (SELECT * FROM deleted WHERE FKID = 1)
BEGIN
RAISERROR ('Cannot delete this record!', 0, 1) WITH NOWAIT
ROLLBACK
END
SET NOCOUNT OFF
GO
At the beginning I apologize for not being word-perfect in English.
I have two tables in my database, one contains questions, and second contains user answers for questions (for statistics).
TableA - questions
___________
| ID | Name |
TableB - Statistics
___________________________________
| ID | A_ID | U_ID| IsCorrect | Date|
User can answer one question several times, for example if we have question with ID = 1 and user (with id 2) which answered this question 4 times, we will add 4 rows to TableB:
___________________________________
| ID | A_ID | U_ID| IsCorrect | Date|
-------------------------------------
| 1 | 1 | 2 | True | Date|
| 2 | 1 | 2 | False | Date|
| 3 | 1 | 2 | False | Date|
| 4 | 1 | 2 | True | Date|
At the end, I have to query for questions (TableA) which user has not responded or answered but the smallest number of times (user is able to answer all questions).
My query (procedure) looks like:
Declare #max int
SET #max = (SELECT TOP 1 Count(A_ID) as QuestionCount FROM [TableB]
Where User_id = 1
GROUP BY A_ID
ORDER BY QuestionCount DESC)
SELECT TOP 40 ID
FROM [dbo].[TableA]
WHERE ID NOT IN (SELECT A_ID
FROM [dbo].[TableB]
WHERE User_id = 1
GROUP BY A_ID
HAVING Count(A_ID) = #max)
ORDER BY NewID()
At the beggining I query for max occurence of question - If user answered some question 4 time #max will be 4.
In second query I query for question which weren't answered yet (in this occurence).
Question is: How to optimize this query (or maybe I should change my tables)? TableB for now has almost one million rows and beacause of that it isn't fast enough.
With SQL-Server (>=2008) you can use the OVER clause (https://msdn.microsoft.com/en-us/library/ms189461.aspx) which gives you grouped aggregats.
EDIT: Just found your ORDER BY NewID() Why do you do this? NewID() is very bad to sort... 1 million is not so much in fact, but 1 million GUIDs without an index are a mass...
I have following Product table and ProductTag tables -
ID | Product
--------------
1 | Product_A
2 | Product_B
3 | Product_C
TagID | ProductID
----------------------
1 | 2
1 | 3
2 | 1
2 | 2
2 | 3
3 | 1
3 | 2
Now I need a SQL query that return all products list which are having both Tag 1 and 2. Result should be as given below -
ProductID | Product
------------------------
2 | Product_B
3 | Product_C
Please suggest how can i write a MS SQL query for this.
SELECT p.ID, p.Product
FROM Product p
INNER JOIN ProductTag pt
ON p.ID = pt.ProductID
WHERE pt.TagID IN (1, 2) -- <== Tags you want to find
GROUP BY p.ID, o.Product
HAVING COUNT(*) = 2 -- <== tag count on WHERE clause
however, if TagID is not unique on every Product, you need to count only the distinct product.
HAVING COUNT(DISTINCT pt.TagID) = 2
More on: SQL of Relational Division