Update column in set of records only if multiple rows exist with a given value in a different field? - sql

My_Table would be something like this:
user_id shared_field bool_field
------- ------------ ----------
1 abc null
2 def null
3 ghi Y
4 ghi null
5 ghi null
6 abc Y
7 jkl null
If the bool_field changes for a user who shares the same shared_field with other users (such as user_id 3, 4, and 5 above), only that one user should have a 'Y'. The rest should have null values in the bool_field column. For example, if user_id 4 should now have the 'Y', I have to change user_id 4's bool_field to 'Y', and ensure that user_id 3 and 5 have bool_field values of null.
If the user doesn't share a shared_field value with anyone else, then that bool_field should be null (as in user_id 1 and 2 above).
Update: added a couple of lines to show that multiple user_ids could share a given shared_field (eg, 1 and 6 both have 'abc'; 3, 4, and 5 all have 'ghi' - only one 'abc' user should have a 'Y', and only one 'ghi' user should have a 'Y' and so on, while the rest have null in their bool_field column; user_ids that don't share a shared_field value, such as user_ids 2 and 7, should all have null in their bool_field column.) Clear as mud, right? ;)
This statement works:
UPDATE my_table
SET bool_field = (CASE
WHEN user_id = 4 THEN 'Y'
ELSE NULL
END)
WHERE shared_field = 'ghi'
AND (SELECT COUNT(shared_field)
FROM my_table
WHERE shared_field = 'ghi') > 1;
The question: is there some way that I can accomplish this same thing without knowing the shared_field value in advance? For example (and this doesn't work, of course) - Update: "of course" means I know this doesn't work because it is not correct Oracle syntax! The point is to give an idea of what I'm trying to do.
UPDATE my_table
SET bool_field = (CASE
WHEN user_id = 4 THEN 'Y'
ELSE NULL
END)
WHERE shared_field = (SELECT shared_field FROM my_table WHERE user_id = 4) as sharedVal
AND (SELECT COUNT(shared_field)
FROM my_table
WHERE shared_field = sharedVal) > 1;
Update: this is a regular SQL statement - I can't use a stored procedure.

First, rather than saying that something "doesn't work", it is generally helpful to tell us how it doesn't work. The query you posted, for example, appears to have syntax errors (as sharedVal is invalid because you can't assign an alias to an expression you're computing in the SELECT list). But it's not clear if "doesn't work" means that you're getting syntax errors (which we can relatively easily debug with the error message) or whether it means that the query runs but doesn't do what you want (which I would expect the query to do if the syntax errors were corrected) in which case knowing how the query isn't doing what you want would be helpful.
I would expect something like
UPDATE my_table a
SET bool_field = (CASE WHEN user_id = 4
THEN 'Y'
ELSE NULL
END)
WHERE shared_field = (SELECT shared_field
FROM my_table b
WHERE b.user_id = 4)
AND EXISTS( SELECT 1
FROM my_table c
WHERE a.shared_field = c.shared_field
AND a.user_id != c.user_id )
to work assuming that user_id is the primary key.

It's been awhile since I've worked on Oracle, so check to make sure that this query returns the list of records you want to update:
SELECT *
FROM my_table m
WHERE EXISTS (SELECT 1
FROM my_table
WHERE shared_field = m.shared_field
HAVING COUNT(shared_field) > 1);
If so, then this should work:
UPDATE my_table
SET bool_field = (CASE
WHEN user_id = 4 THEN 'Y'
ELSE NULL
END)
WHERE EXISTS (SELECT 1
FROM my_table
WHERE shared_field = m.shared_field
HAVING COUNT(shared_field) > 1);
You could also try this WHERE clause:
WHERE shared_field IN (SELECT shared_field
FROM my_table
GROUP BY shared_field
HAVING COUNT(shared_field) > 1);

Related

Get a particular record based on a condition in SQL

My requirement is to get id for missing status from SQL table. I will get a list of status for each id, say A,B,C,D. In a scenario, I have to check status B exists or not. Table gets updated everyday and each time new Id will be created
Conditions,
If status A exists and other statuses such as C and D does not
exists, then don't need to get id.
If status A and B exists and other statuses such as C or D does not exists, then don't need to get id .
If status A exists and B not exists, other
statuses such as C or D exists, then I should get the id of that
record
If status A and B exists, other
statuses such as C or D exists (all status exists), then I don't need to get the id of that
record
Table1:
Id StatusCode
1 A
1 C
2 A
2 B
2 C
3 A
3 C
3 D
How do I get Id 1 and 3 using SQL query?, Seems simple but as I am new to SQL I could not able to get it in SQL.
select statement in this screenshot works fine when there is only one id, it fails on multiple id. I tried many other way, but no use
Try this
SELECT DISTINCT ID
FROM T1
WHERE Statuscode = 'A' AND ID NOT IN (SELECT ID FROM T1 WHERE Statuscode = 'B' )
AND (ID IN (SELECT ID FROM T1 WHERE Statuscode = 'C' ) OR ID IN (SELECT ID FROM T1 WHERE Statuscode = 'D' ))
FIDDLE DEMO
Also, To correct Gordon Linoff's answer, we need to add one more where criteria there
SELECT Id
FROM T1
GROUP BY Id
HAVING SUM(CASE WHEN Statuscode = 'A' THEN 1 ELSE 0 END) > 0 AND
SUM(CASE WHEN Statuscode = 'B' THEN 1 ELSE 0 END) = 0 AND
SUM(CASE WHEN Statuscode IN ('C', 'D') THEN 1 ELSE 0 END) > 0;
FIDDLE DEMO
This answers the original version of the question.
I think you can use aggregation:
select id
from t
group by id
having sum(case when status = 'A' then 1 else 0 end) > 0 and
sum(case when status in ('C', 'D') then 1 else 0 end) > 0;
SELECT id
FROM t
GROUP BY
Id
HAVING MAX(status) = CHAR(64 + COUNT(*))
--char(64+1) = A, char(64+2) = B etc
The logic behind this is that it will take all count the same types of id. So if you have 3 rows you will need abc. If you have an id with 4 rows you will have ABCD. Generally the max status should always be the same as the number of rows.
This is true of course if you have no duplicate between id and status code.
select distinct id from t where t.statuscode = 'C' or t.statuscode = 'D' group by t.id

Oracle SQL - A count that excludes completely a Key with the if not equal statement

Ok here goes:
MyTableName
KEY Value1
1 ABC
1 DEF
1 GHI
2 ABC
2 DEF
2 YUI
I have a child table that is linked via a Key (stated above)
I need to find records, linked to the parent table (i have no issues with my join) where value 1 meets conditions and does not meet certain conditions.
So, for my Example, I want to retrieve the value '1' from the "KEY" above, because my need is where value 1 = 'ABC' or value 1 = 'DEF' but value 1 != YUI,
so I would want '1' to come back, but not 2
SELECT KEY, count(*)
FROM MyTableName
WHERE (value1 = 'ABC'
OR value1 = 'DEF')
AND value1 != 'YUI'
GROUP BY KEY
HAVING count(KEY) = 2
The above statement, find both Key 1 and 2, where I need it only to find Key 1.
Can anyone help?
Use the HAVING clause with conditional counts:
select key
from mytablename
group by key
having count(case when value1 in ('ABC','DEF') then 1 end) > 0
and count(case when value1 = 'YUI' then 1 end) = 0;
(You can add where value1 in ('ABC','DEF','YUI'), which may speed up the query, because you are not interested in rows containing other values.)
(Some people prefer to include an explicit else branch: count(case when value1 = 'YUI' then 1 else null end) and others prefer sum(case when value1 = 'YUI' then 1 else 0 end). That's a matter of personal preference.)

How do I check if a certain value exists?

I have a historization table called CUR_VALID. This table looks something like this:
ID CUR_VALID
1 N
1 N
1 Y
2 N
2 Y
3 Y
For every ID there needs to be one Y. If there is no Y or multiple Y there is something wrong. The statment for checking if there are multiple Y I already got. Now I only need to check for every ID if there is one Y existing. Im just not sure how to do that. This is what I have so far. So how do I check if the Value 'Y' exists?
SELECT Count(1) [Number of N]
,MAX(CUR_VALID = 'N')
,[BILL_ID]
,[BILL_MONTH]
,[BILL_SRC_ID]
FROM db.dbo.table
GROUP BY [BILL_ID]
,[BILL_MONTH]
,[BILL_SRC_ID]
Having MAX(CUR_VALID = 'N') > 1
Why are you fiddling with 'N' when you are interested in 'Y'?
Use conditional aggregation to get the count of the value your are interested in.
SELECT
COUNT(*) AS number_of_all,
COUNT(CASE WHEN cur_valid = 'Y' THEN 1 END) AS number_of_y,
COUNT(CASE WHEN cur_valid = 'N' THEN 1 END) AS number_of_n,
bill_id,
bill_month,
bill_src_id,
FROM db.dbo.table
GROUP BY bill_id, bill_month, bill_src_id;
Add a HAVING clause in order to get only valid
HAVING COUNT(CASE WHEN cur_valid = 'Y' THEN 1 END) = 1
or invalid
HAVING COUNT(CASE WHEN cur_valid = 'Y' THEN 1 END) <> 1
bills.
The following query will give you the list of id for which your integrity condition is not met: For every ID there needs to be one Y. If there is no Y or multiple Y there is something wrong.
select T1.id from table T1 where (select count(*) from table T2 where T2.id=T1.id and T2.CUR_VALID='Y')!=1
This query returns both not having at least one 'Y' value and more than one 'Y' value ID's.
First, sum up the Y values and relate to each id, then select not 1 ones from that table.
select * from (
select ID, SUM(case when CUR_VALID = 'Y' then 1 else 0 end) as CNT
from table
group by ID
) b where b.CNT <> 1
DBFiddle
As I understand, you want to get all the id for which your integrity check passes. And integrity check for you means, there is only one row with CUR_VALID value equal to Y in the CUR_VALID table.
This can be achieved by a group by clause:
select id from CUR_VALID
where CUR_VALID.CUR_VALID = 'Y'
group by id
having count(CUR_VALID.CUR_VALID) = 1;

how can I add a new column where there may be matches on an id field

I currently have a table that contains an id, and a count of a criteria for that id field. For example my table looks like this:
ID Banana_count
1 13
2 23
3 56
The original counts came from a join and a query from other tables.
create FRUIT_TABLE as
select id, count (fruit)
from my_table a
where exists (select null from DATE_FED b
where a.id = b.id
and date = (2/11/17)
and fruit_type = 'banana')
group by id;
My question is, how can i add other attributes to this particular table so that it looks like:
ID Banana_count Apple_count Orange_count
1 13 35 22
2 23 44
3 56
4 33 55
5 11
I will have to add more ids to FRUIT_TABLE that may not already be in the current table, but for fruits that are currently associated with an id, i'd like to add them in the same row.
This is a classic use case for merge:
merge into fruit_table
using apple_table
on (fruit_table.id = apple_table.id)
when matched then update set
fruit_table.apples = apple_table.apples
when not matched then insert (id,apples)
values(
apple_table.id,
apple_table.apples
);
I have simplified the problem slightly so that you are inserting from a table that simply has ids and a count of apples, so that the structure of the merge is clearer. But you can insert a subquery instead into the using... section of the statement to meet your actual requirements.
I would look into something like the following [you didn't provide your table definitions, or other application or requirements constraints so an exact answer is not possible]:
create FRUIT_TABLE as
select id
, sum(case when fruit_type = 'banana' then 1 else 0 end ) Banana_count
, sum(case when fruit_type = 'apple' then 1 else 0 end ) apple_count
, sum(case when fruit_type = 'orange' then 1 else 0 end ) orange_count
from my_table a
group by id;

Sql query to find count with a difference condition and total count in the same query

Here is a sample table I have
Logs
user_id, session_id, search_query, action
1, 100, dog, A
1, 100, dog, B
2, 101, cat, A
3, 102, ball, A
3, 102, ball, B
3, 102, kite, A
4, 103, ball, A
5, 104, cat, A
where
miss = for the same user_id and same session id , if action A is not followed by action B its termed a miss.
Note: action B can happen only after action A has happened.
I am able to find the count of misses for each unique search_query across all users and sessions.
SELECT l1.search_query, count(l1.*) as misses
FROM logs l1
WHERE NOT EXISTS
(SELECT NULL FROM logs l2
WHERE l1.user_id = l2.user_id
AND l1.session_id = l2.session_id
AND l1.session_id != ''
AND l2.action = 'B'
AND l1.action = 'A')
AND l1.action='A'
AND l1.search_query != ''
GROUP BY v1.search_query
order by misses desc;
I am trying to find the value of miss_percentage=(number of misses/total number of rows)*100 for each unique search_query. I couldn't figure out how to find the count with a condition and count without that condition in the same query. Any help would be great.
expected output:
cat 100
kite 100
ball 50
One way to do it is to move the EXISTS into the count
SELECT l1.search_query, count(case when NOT EXISTS
(SELECT 1 FROM logs l2
WHERE l1.user_id = l2.user_id
AND l1.session_id = l2.session_id
AND l1.search_query = l2.search_query
AND l2.action = 'B'
AND l1.action = 'A') then 1 else null end
)*100.0/count(*) as misses
FROM logs l1
WHERE l1.action='A'
AND l1.search_query != ''
GROUP BY l1.search_query
order by misses desc;
This produces the desired results, but also zeros if no misses were found. This can be removed with a HAVING clause, or postprocessing.
Note I also added the clause l1.search_query = l2.search_query that was missing, since otherwise it was counting kite as succeeded, since there is a row with B in the same session.
I think you just need to use case statements here. If I have understood your problem correctly .. then the solution would be something like this -
WITH summary
AS (
SELECT user_id
,session_id
,search_query
,count(1) AS total_views
,sum(CASE
WHEN action = 'A'
THEN 1
ELSE 0
END) AS action_a
,sum(CASE
WHEN action = 'B'
THEN 1
ELSE 0
END) AS action_b
FROM logs l
GROUP BY user_id
,session_id
,search_query
)
SELECT search_query
,(sum(action_a - action_b) / sum(action_a)) * 100 AS miss_percentage
FROM summary
GROUP BY search_query;
You can allways create two queries, and combine them into one with a join. Then you can do the calculations in the bridging (or joining) SQL statement.
In MS-SQL compatible SQL this would be:
SELECT ActiontypeA,countedA,isNull(countedB,0) as countedB,
(countedA-isNull(countedB,0))*100/CountedA as missed
FROM (SELECT search_query as actionTypeA, count(*) as countedA
FROM logs WHERE Action='A' GROUP BY actionType
) as TpA
LEFT JOIN
(SELECT search_query as actionTypeB, count(*) as countedB
FROM logs WHERE Action='B' GROUP BY actionType
) as TpB
ON TpA.ActionTypeA = TpB.ActiontypeB
The LEFT JOIN is required to select all activities (search_query) from the 'A' results, and join them to only those from the 'B' results where a B is available.
Since this is very basic SQL (and well optimized by SQL engines) I'd suggest to prevent WHERE EXISTS as much as possible. The IsNull() function is an MS-SQL function to force a NULL value into the int(0) value which can be used in a calculation.
Finally you could filter on
WHERE missed>0
to get the final result.