SQL UPDATE only Duplicates - sql

I have a SQL Table like this:
+------+------------+---------+---------+--------+
| id | x | y | z | status |
+------+------------+---------+---------+--------+
| 1 | bla | ja | 1 | 0 |
| 2 | blaa | jaa | 2 | 0 |
| 3 | bla | ja | 1 | 0 |
| 4 | blaaa | jaaa | 3 | 0 |
| 5 | blaa | jaa | 2 | 0 |
+------+------------+---------+---------+--------+
I want to UPDATE only the status column of the duplicate rows and not the first one.
With that statement i update every duplicate also the first row of a duplicate row:
UPDATE table INNER JOIN
(SELECT x, y, z FROM table GROUP BY x,y,z HAVING COUNT(id) > 1)
dup
ON table.x = dup.x && table.y = dup.y && table.z = dup.z
SET status = '1'
But thats no right because the table has to look after the UPDATE Statement like this:
+------+------------+---------+---------+--------+
| id | x | y | z | status |
+------+------------+---------+---------+--------+
| 1 | bla | ja | 1 | 0 |
| 2 | blaa | jaa | 2 | 0 |
| 3 | bla | ja | 1 | 1 |
| 4 | blaaa | jaaa | 3 | 0 |
| 5 | blaa | jaa | 2 | 1 |
+------+------------+---------+---------+--------+
I hope you can help me.
Thanks a lot.

Just play with a select statment like the one below until you have a list of the duplicates then update as shown.
UPDATE table set status = '1'
WHERE ID in (select id from(Select ROW_NUMBER() OVER (Partition By x,y,z,status) as dup,id) where dup>1)
Didn't say RDBMS so this is for SQL Server

I believe this is what you want:
UPDATE table t INNER JOIN
(SELECT x, y, z, MIN(id) as minid
FROM table
GROUP BY x, y, z
HAVING COUNT(id) > 1 -- not strictly necessary, but why not?
) dup
ON t.x = dup.x AND t.y = dup.y AND t.z = dup.z AND
t.id > dup.minid
SET status = 1;
This calculates the minimum id for each group and then updates all the other rows.

Related

Sql join multiple tables, get count of certain rows, and also check some rows satisfy condition

I have a Zoo, each Zoo has many Cages, each Cage has many Animals.
Zoo:
+----+
| Id |
+----+
| 1 |
| 2 |
+----+
Cage:
+----+-------+
| Id | ZooId |
+----+-------+
| 1 | 1 |
| 2 | 1 |
| 3 | 2 |
| 4 | 2 |
| 5 | 2 |
+----+-------+
Animal:
+----+--------+----------+
| Id | CageId | IsHungry |
+----+--------+----------+
| 1 | 1 | 0 |
| 2 | 1 | 0 |
| 3 | 1 | 0 |
| 4 | 2 | 1 |
| 5 | 3 | 0 |
| 6 | 4 | 0 |
| 7 | 5 | 0 |
+----+--------+----------+
I'm trying to design a query to show each Zoo, the number of cages in that Zoo, and whether or not the Zoo has hungry Animals.
Here is the results I expect:
+-------+-----------+--------------+
| ZooID | CageCount | AnyoneHungry |
+-------+-----------+--------------+
| 1 | 2 | 1 |
| 2 | 3 | 0 |
+-------+-----------+--------------+
I can get the number of Cages in a Zoo:
SELECT
[c].[ZooId],
COUNT(*) AS [NumCages]
FROM [Cage] [c]
GROUP BY [c].[ZooId]
ORDER BY [NumCages] DESC
I can determine if a Cage has a hungry animal or not:
SELECT CASE WHEN EXISTS (
SELECT NULL
FROM [Animal] [a]
WHERE [a].[CageId] = #CageId AND [a].[IsHungry] = 1
) THEN 1 ELSE 0 END
But I'm having trouble combining these two into a single query that runs efficiently (in this universe zoos are very popular and have millions of cages and animals).
SELECT
[c].[ZooId],
COUNT(*) AS [CageCount],
MAX(CONVERT(INT, [x].[AnyoneHungry])) AS [AnyoneHungry]
FROM [Cage] [c]
INNER JOIN (
SELECT [a].[CageId], MAX(CONVERT(INT, [a].[IsHungry])) AS [AnyoneHungry]
FROM [Animal] [a]
GROUP BY [a].[CageId]
) [x] on [x].[CageId] = [c].[Id]
GROUP BY [c].[ZooId]
I feel like I'm missing something and it should be possible do run this query using a simpler statement.
This should do
SELECT
Z.Id,
COUNT(DISTINCT C.Id) AS CageCount,
COALESCE(MAX(CAST(A.IsHungry AS INT)), 0) AS AnyHungry /*The cast is only required if A.IsHungry is BIT and not INT*/
FROM Zoo Z
LEFT JOIN Cage C ON Z.Id = C.ZooId
LEFT JOIN Animal A ON C.Id = A.CageId
GROUP BY Z.Id
If you only need the zoo id and hungry animals:
SELECT c.zooid,
COUNT(DISTINCT C.Id) as CageCount,
COALESCE(MAX(CONVERT(int, a.IsHungry)), 0) AS AnyHungry
FROM Cage C LEFT JOIN
Animal A
ON c.Id = a.CageId AND a.IsHungry = 1
GROUP BY c.zooid;

Get column value if it matches another column value in the same table

I have a SQLite table with comments like:
Id | replyId | commentID_parentID | usernameChannelId | channelId
1 | NULL | NULL | a | g
2 | NULL | NULL | b | k
NULL | 1.k | 1 | a | p
NULL | 1.p | 1 | c | i
3 | NULL | NULL | d | h
NULL | 2.k | 2 | g | g
and a table with channels like:
I want to know which user (userChannelId) replied to which user.
So I take a row with a comment and check if:
Id == NULL? Then it's a reply -> get userChannelId where commentID_parentID == Id
Id != NULL? Then it's a main comment -> userChannelId replied to channelId
And result should be:
userChannelId_Source | userChannelId_Target
a | g
b | k
a | a
c | a
g | b
Comment "d" has no entry where commentID_parentID == Id so it's left out.
How can I do that in SQL when I query in the same table?
It's a rather complicated requirement but I think that a conditional self join will do it:
select t.usernameChannelId userChannelId_Source,
case
when t.id is not null then tt.channelId
else tt.usernameChannelId
end userChannelId_Target
from tablename t inner join tablename tt
on tt.id = coalesce(t.id, t.commentID_parentID)
and exists (
select 1 from tablename
where commentID_parentID = t.id
or (commentID_parentID is null and t.id is null)
)
See the demo.
Results:
| userChannelId_Source | userChannelId_Target |
| -------------------- | -------------------- |
| a | g |
| a | a |
| c | a |
| b | k |
| g | b |

SQL Server - Better Solution for join between 2 tables pivoting rows into columns

Hi everyone I am using SQL Server 2016, I have a table called support_event_log that looks like this:
| event_nr | data |
|--------------|-------------|
| 1 | x |
| 2 | x |
And a table called support_event_log_params that looks like this:
| event_nr | msg_param_nr | msg_param_value |
|-----------------|----------------|------------------|
| 1 | 1 | x |
| 2 | 1 | x |
| 2 | 2 | y |
| 2 | 3 | z |
I want to join both tables by the column Event_nr and pivot the column msg_param_nr into 3 different columns depending on the number with the value of the column msg_param_value, like this:
| event_nr | msg1 | msg2 | msg3 | data |
|-----------------|------|------|------| x |
| 1 | x | null | null | x |
| 2 | x | y | z | x |
I first tried the following query:
SELECT A.event_nr
,A.data
,CASE WHEN B.msg_param_nr = 1 THEN B.msg_param_value END AS msg1
,CASE WHEN B.msg_param_nr = 2 THEN B.msg_param_value END AS msg2
,CASE WHEN B.msg_param_nr = 3 THEN B.msg_param_value END AS msg3
FROM support_event_log A LEFT JOIN support_event_log_params B
on A.event_nr=B.event_nr
but I was getting the following result with repeated rows:
| event_nr | msg1 | msg2 | msg3 | data |
|-----------------|------|------|------| x |
| 1 | x | null | null | x |
| 2 | x | null | null | x |
| 2 | null | y | null | x |
| 2 | null | null | z | x |
Finally after a lot of thinking I got a working solution with the following query:
WITH col1 AS (
SELECT A.event_nr, A.msg_param_value
FROM support_event_log_params A
WHERE A.msg_param_nr=1
)
, col2 AS (
SELECT A.event_nr, A.msg_param_value
FROM support_event_log_params A
WHERE A.msg_param_nr=2
)
,col3 AS (
SELECT A.event_nr, A.msg_param_value
FROM support_event_log_params A
WHERE A.msg_param_nr=3
)
SELECT A.event_nr
,A.data
,B.msg_param_value as msg1
,C.msg_param_value as msg2
,D.msg_param_value as msg3
FROM support_event_log A
LEFT JOIN col1 B on A.event_nr=B.event_nr
LEFT JOIN col2 C on A.event_nr=C.event_nr
LEFT JOIN col3 D on A.event_nr=D.event_nr
but it seems very inefficient doing the 3 withs to the same table, is there a better solution to this problem ? I can't seem to find one that works
You just need aggregation on your first query:
SELECT el.event_nr, el.data,
MAX(CASE WHEN elp.msg_param_nr = 1 THEN elp.msg_param_value END) AS msg1,
MAX(CASE WHEN elp.msg_param_nr = 2 THEN elp.msg_param_value END) AS msg2,
MAX(CASE WHEN elp.msg_param_nr = 3 THEN elp.msg_param_value END) AS msg3
FROM support_event_log el LEFT JOIN
support_event_log_params elp
ON el.event_nr = elp.event_nr
GROUP BY el.event_nr, el.data;
Notice that I also changed the table aliases to be abbreviations for the table names, rather than meaningless letters such as A and B.

Oracle - Select row where desired column contains only one specific type of data

I've two Table
Table 1
+--------+--------+
| LC | STATUS |
+--------+--------+
| 010051 | 6 |
+--------+--------+
| 010071 | 2 |
+--------+--------+
| 010048 | 2 |
+--------+--------+
| 010113 | 2 |
+--------+--------+
| 010125 | 2 |
+--------+--------+
Table 2
+--------+-------------+-----------+------------+--------+
| LC | BILL | LAST_BILL | PAYMENT_BY | STATUS |
+--------+-------------+-----------+------------+--------+
| 010125 | BILL/17/001 | 0 | C | 6 |
+--------+-------------+-----------+------------+--------+
| 010125 | BILL/17/002 | 0 | I | 1 |
+--------+-------------+-----------+------------+--------+
| 010125 | BILL/17/003 | 0 | F | 1 |
+--------+-------------+-----------+------------+--------+
| 010125 | BILL/17/004 | 0 | C | 6 |
+--------+-------------+-----------+------------+--------+
| 010113 | BILL/17/005 | 0 | C | 6 |
+--------+-------------+-----------+------------+--------+
| 010113 | BILL/17/006 | 0 | I | 1 |
+--------+-------------+-----------+------------+--------+
| 010048 | BILL/17/007 | 0 | C | 6 |
+--------+-------------+-----------+------------+--------+
| 010071 | BILL/17/008 | 0 | C | 6 |
+--------+-------------+-----------+------------+--------+
Where I just want to get the LC whose PAYMENT_BY is 'C', but others who have 'C' value and other than 'C' value, I don't want to get this LC.
I've try following query, but I think there's have expert who can done it in better way or most tuning way.
SELECT LC
FROM (SELECT T1.LC
FROM TABLE1 T1, TABLE2 T2
WHERE T1.STATUS = 2
AND T1.LC = T2.LC
AND T2.PAYMENT_BY = 'C'
AND LAST_BILL = 0
AND T2.STATUS = 6
MINUS
SELECT T1.LC
FROM TABLE1 T1, TABLE2 T2
WHERE T1.STATUS = 2
AND T1.LC = T2.LC
AND T2.PAYMENT_BY = 'I'
AND LAST_BILL = 0)
Query/Expected Result:
+--------+
| LC |
+--------+
| 010048 |
+--------+
| 010071 |
+--------+
You can do it with NOT EXISTS:
select t2.lc from table2 t2
where
t2.payment_by = 'C'
and
not exists (
select lc from table2
where lc = t2.lc and payment_by <> 'C'
)
If you want all the columns of table2, then:
select t2.* from table2 t2
..........................
select t.lc,
count(case when t.payment_by = 'C' THEN 1 else NULL end ) as count_c,
count(case when t.payment_by <> 'C' THEN 1 else NULL end ) as count_not_c
from table2 t
group by t.lc
having count(case when t.payment_by <> 'C' THEN 1 else NULL end ) < 1
demo
If I understand correctly, I think group by and having is the simplest query:
select t2.lc
from table2 t2
group by t2.lc
having min(t2.payment_by) = 'C' and max(t2.payment_by) = 'C';
This also has the advantage of returning each lc exactly once.

Check if data for update is same as before in SQL Server

I have a table Table1:
ID | RefID | Answer | Points |
----+-------+---------+--------+
1 | 1 | 1 | 5 |
2 | 1 | 2 | 0 |
3 | 1 | 3 | 3 |
4 | 2 | 1 | 4 |
I have a result set in temp table Temp1 with same structure and have update Table1 only if for refID answer and points have changed, otherwise there should be deletion for this record.
I tried:
update table1
set table1.answer = temp1.answer,
table1.points = temp1.points
from table1
join temp1 on table1.refid = temp1.refid
where table1.answer != temp1.answer or table1.points != temp1.points
Here is a fiddle http://sqlfiddle.com/#!18/60424/1/1
However this is not working and don't know how to add the delete condition.
Desired result should be if tables not the same ex. (second row answer 2 points3):
ID | RefID | Answer | Points |
----+-------+---------+--------+
1 | 1 | 1 | 5 |
2 | 1 | 2 | 3 |
3 | 1 | 3 | 3 |
4 | 2 | 1 | 4 |
if they are same all records with refID are deleted.
Explanation when temp1 has this data
ID | RefID | Answer | Points |
----+-------+---------+--------+
12 | 1 | 1 | 5 |
13 | 1 | 2 | 0 |
14 | 1 | 3 | 3 |
EDIT: adding another id column questionid solved the update by adding this also in join.
Table structure is now:
ID | RefID | Qid |Answer | Points |
----+-------+------+-------+--------+
1 | 1 | 10 | 1 | 5 |
2 | 1 | 11 | 2 | 0 |
3 | 1 | 12 | 3 | 3 |
4 | 2 | 11 | 1 | 4 |
SQL for update is: (fiddle http://sqlfiddle.com/#!18/00f87/1/1) :
update table1
set table1.answer = temp1.answer,
table1.points = temp1.points
from table1
join temp1 on table1.refid = temp1.refid and table1.qid = temp1.qid
where table1.answer != temp1.answer or table1.points != temp1.points;
SELECT ID, refid, answer, points
FROM table1
How can I make the deletion case, if data is same ?
You need to add one more condition in the join to exactly match the column.Try this one.
update table1
set table1.answer=temp1.answer,
table1.points=temp1.points
from
table1 join temp1 on table1.refid=temp1.refid and **table1.ID=temp1.ID**
where table1.answer!=temp1.answer or table1.points!=temp1.points
I would first do the delete, and only then the update.
The reason for this is that once you've deleted all the records where the three columns are the same, your update statement becomes simpler - you only need the join, and no where clause:
DELETE t1
FROM table1 AS t1
JOIN temp1 ON t1.refid = temp1.refid
AND t1.qid = temp1.qid
AND t1.answer=temp1.answer
AND t1.points=temp1.points
UPDATE t1
SET answer = temp1.answer,
points = temp1.points
FROM table1 AS t1
JOIN temp1 ON t1.refid=temp1.refid
AND t1.qid = temp1.qid
I think from what i understood that you need to use id instead of refid or both if id is unique