Sort out IDs when hitting a case - sql

How to sort out ID´s when i have a table like
CAR
ID COLOR
1 Red
1 Black
2 BLUE
2 Black
3 Yellow
3 Red
My goal is, that all IDs who have the color "Red" get thrown out, so that just ID = 2 is my result. With my SQL i can find the IDs which have the Color Red with Filter = 1 but they still appear because ID = 1 can also have Black(or ID = 3 can have yellow) so that Filter = 0 and gets passed.
with SORTOUT as
(
ID,
CASE WHEN
COLOR = RED THEN 1
ELSE 0
END AS FILTER
FROM TABLE
)
SELECT * FROM ID_TABLE T1
JOIN SORTOUT T2 on (T1.ID = T2.ID)
WHERE FILTER = 0

If you only want the id, use aggregation:
select id
from id_table
group by id
having sum(case when color = 'RED' then 1 else 0 end) = 0;
If you want the original rows, you can use not exists:
select i.*
from id_table i
where not exists (select 1
from id_table i2
where i2.id = i.id and i2.color = 'RED'
);

You can use simple select query to achieve this,
SELECT * FROM ID_TABLE WHERE COLOR != 'Red';
If you still need CASE statement, you can use below
SELECT ID, COLOR,
CASE WHEN COLOR = 'Red' THEN 1 ELSE 0 END AS Val
FROM ID_TABLE WHERE CASE WHEN COLOR = 'Red' THEN 1 ELSE 0 END = 0;
Or
SELECT ID, COLOR FROM
(SELECT ID, COLOR,
CASE WHEN COLOR = 'Red' THEN 1 ELSE 0 END AS Val
FROM ID_TABLE) qry
WHERE Val = 0;

Related

SQL match if not 0 or > 4

Here is my issue.
I have 4 tables as example (Shape -> ShapeDetails -> ShapeSize -> ShapeColor)
Basically each time I create a new Shape there is new line ShapeColor created( and for each colors I selected is created new line in the table ShapeColor) Inconvenient I know but the software I have to use has been designed like this.
So , a Shape can have a lot of colors but I would like to match based on the "name" of the color those which have color "red", "pink","blue" BUT if one of them have "red","pink","blue" and "yellow" in this case this one should not be matched.
I would like to match those between 1 and/or 3 colors.
Example (Colors: red,pink,blue,yellow) :
0 0 0 0 (none of these colors = NOK) not in the resulstSet
1 0 0 0 (only red but not pink blue yellow = OK) Match in the resultSet
0 1 0 0 (only pink but not red blue yellow = OK) Match
1 1 0 0 (only red pink but not blue and yellow = OK) Match
1 0 1 0 ...
1 1 1 1 (all of these colors = NOK) not in the resultsSet
btw, I use some joins to get to ShapeColor
I don't know how can I solve this problem with one SQL query, any kind of help will be very appreciated
SELECT s.id
FROM shape s INNER JOIN shape_color sc ON s.id=sc.id
GROUP BY s.id
HAVING
SUM(CASE WHEN sc.color='red' OR sc.color='pink' OR sc.color='blue' 1 ELSE 0 END) >0
AND
HAVING SUM(CASE WHEN sc.color='yellow' 1 ELSE 0 END) = 0
HAVING is applied to grouped results
if sum of valid colors is >0 that means one of the 3 valid colors exists for the shape
if sum of invalid color is >0 thah means yellow color exist
So you get shapes where one (or more) of valid colors exists and invalid color does not exist
UPDATE:
SELECT s.id
FROM shape s INNER JOIN shape_color sc ON s.id=sc.id
GROUP BY s.id
HAVING
SUM(CASE WHEN sc.color='red' OR sc.color='pink' OR sc.color='blue' OR sc.color='yellow' 1 ELSE 0 END) >0
AND
SUM(CASE WHEN sc.color='red' OR sc.color='pink' OR sc.color='blue' OR sc.color='yellow' 1 ELSE 0 END) <=3
Assuming the colors are independent, you can do:
SELECT sc.shape_id
FROM shape_color sc join
color c
ON sc.color_id = c.color_id
WHERE c.color in ('red', 'pink', 'blue', 'yellow')
GROUP BY sc.shape_id
HAVING COUNT(*) < 4;
(Your column names aren't clear but you should get the idea.)
By looking in shape_color, you are eliminating the first condition -- a color is needed. This then just checks that there are fewer than 4 colors. Implicitly, there is at least one, but you could add HAVING COUNT(*) > 0 AND COUNT(*) < 4.
If you find the solution with GROUP BY and HAVING could be confusing, simple first concatenate the colors in a string using LISTAGG.
Note that an INNER JOIN is used eliminate the shapes without colors (here 4 - see sample data below).
Also the WHERE predicate limits only the relevant colors.
select s.shape_id,
LISTAGG(c.color, ',') WITHIN GROUP (ORDER BY c.color) color_lst
from shape s
join shape_color c
on s.shape_id = c.shape_id
where c.color in ('red', 'pink', 'blue', 'yellow')
group by s.shape_id
;
SHAPE_ID COLOR_LST
---------- ---------------------------
1 blue,pink,red,yellow
2 red
3 blue,pink,yellow
You task is than as simple as eliminate the only negative case of all four colors:
with colors as (
select s.shape_id,
LISTAGG(c.color, ',') WITHIN GROUP (ORDER BY c.color) color_lst
from shape s
join shape_color c
on s.shape_id = c.shape_id
where c.color in ('red', 'pink', 'blue', 'yellow')
group by s.shape_id
)
select shape_id
from colors
where color_lst != 'blue,pink,red,yellow'
SHAPE_ID
----------
2
3
You shoud take some care to check, that the colors within a shape are unique. If not you must add a subquery that distincts the colors within shape. The same is valid also for the HAVING based solutions.
Sample Data
create table shape as
select 1 shape_id from dual union all
select 2 shape_id from dual union all
select 3 shape_id from dual union all
select 4 shape_id from dual;
create table shape_color as
select 1 shape_id, 'red' color from dual union all
select 1 shape_id, 'pink' color from dual union all
select 1 shape_id, 'blue' color from dual union all
select 1 shape_id, 'yellow' color from dual union all
select 2 shape_id, 'red' color from dual union all
select 3 shape_id, 'pink' color from dual union all
select 3 shape_id, 'blue' color from dual union all
select 3 shape_id, 'yellow' color from dual;

Returning only id's of records that meet criteria

I need to return distinct ID's of records which meet following conditions :
must have records with field reason_of_creation = 1
and must NOT have records with field reason_of_creation = 0 or null
in the same time.
While i was able to do it, i keep wondering is there more elegant (even recommended) way of doing it.
Here is anonymized version of what i have :
select distinct st.some_id from (
select st.some_id, wanted.wanted_count as wanted, unwanted.unwanted_count as unwanted
from some_table st
left join (
select st.some_id, count(st.reason_of_creation) as wanted_count
from some_table st
where st.reason_of_creation=1
group by st.some_id
) wanted on wanted.some_id = st.some_id
left join (
select st.some_id, count(st.reason_of_creation) as unwanted_count
from some_table st
where st.reason_of_creation=0
group by st.some_id
) unwanted on unwanted.some_id = st.some_id
where wanted.wanted_count >0 and (unwanted.unwanted_count = 0 or unwanted.unwanted_count is null)
) st;
Sample data :
some_id reason_of_creation
1 1
1 0
2 1
3 null
4 0
4 1
5 1
desired result would be list of records with some_id = 2, 5
It seems to me your query is overkill,all you need is some post aggregation filtering
SELECT some_id FROM t
GROUP BY some_id
HAVING SUM(CASE WHEN reason_of_creation = 1 THEN 1 ELSE 0 END)>0
AND SUM(CASE WHEN reason_of_creation = 0 OR reason_of_creation IS NULL THEN 1 ELSE 0 END)=0
I think that more elegant query exists and it is based on assumption what reasoson_of_crdeation field is integer, so minimal possible it's value, which greater than 0 is 1
This is for possible negative values for reasoson_of_crdeation:
select someid from st
where reasoson_of_crdeation != -1
group by someid
having(min(nvl(abs(reasoson_of_crdeation), 0)) = 1)
or
select someid from st
group by someid
having(min(nvl(abs(case when reasoson_of_crdeation = -1 then -2 else reasoson_of_crdeation end), 0)) = 1)
And this one in a case if reasoson_of_crdeation is non-negative integer:
select someid from st
group by someid
having(min(nvl(reasoson_of_crdeation, 0)) = 1)

SQL: selecting a value based on values from a number of rows in another table

I have a table NETWORKS where each network can have multiple CIRCUITS. Each network has an over-all status (red/yellow/green) and each circuit has an individual status (red/green). The circuit's statuses are each set manually. The network's status is as such:
If all its circuits are green --> Green
If all its circuits are red --> Red
If at least 1, but not all, circuits are green --> Yellow
If there are no circuits --> NULL(no status)
I am trying to select all of the networks with their statuses being determined dynamically by the SELECT rather than having to save and manage the status as a column in the table. I cannot figure out an efficient way to do this. What I have now works (both are small tables, < 100, rows with a relatively static amount of data), but is wildly inefficient and I'm hoping there is a better way.
SELECT
(CASE
WHEN (
SELECT COUNT(*)
FROM NETWORK_CIRCUITS
WHERE network_id = N.network_id
) = 0 THEN 'noStatus'
WHEN (
SELECT COUNT(*)
FROM NETWORK_CIRCUITS
WHERE network_id = N.network_id
AND [status] = 'greenStatus'
) = (
SELECT COUNT(*)
FROM NETWORK_CIRCUITS
WHERE network_id = SSN.network_id
) THEN 'greenStatus'
WHEN (
SELECT COUNT(*)
FROM NETWORK_CIRCUITS
WHERE network_id = N.network_id
AND [status] = 'redStatus'
) = (
SELECT COUNT(*)
FROM NETWORK_CIRCUITS
WHERE network_id = N.network_id
) THEN 'redStatus'
ELSE 'yellowStatus'
END) network_status
FROM NETWORKS N
Here's one way:
SELECT
CASE
WHEN CircuitCount IS NULL THEN 'noStatus'
WHEN GreenCount = CircuitCount THEN 'greenStatus'
WHEN GreenCount = 0 THEN 'redStatus'
ELSE 'yellowStatus'
END As network_status
FROM NETWORKS As N
LEFT JOIN
( SELECT COUNT(*) As CircuitCount,
COUNT(NULLIF([status],'redStatus')) As GreenCount,
network_id
FROM NETWORK_CIRCUITS
GROUP BY network_id
) As C ON N.network_id = C.network_id
My first thought is:
select network_id, case when green=0 and red=0 then null when green=0 then 'red' else when red=0 then 'green' else 'yellow' end as status
from
(select n.network_id,
sum(case when c.status='green' then 1 else 0 end) as green,
sum(case when c.status='red' then 1 else 0 end) as red
from network n
join circuit c on c.network_id=n.network_id
group by n.network_id)
I think this should do what you want. It should also be faster than using a bunch of subqueries.
SELECT CASE
WHEN circuits.network_id is NULL THEN 'No Status'
WHEN circuits.greenCount = circuits.totalCircuits THEN 'Green'
WHEN circuits.greenCount >= 1 and circuits.redCount >= 1 THEN 'Yellow'
WHEN circuits.redCount = circuits.totalCircuits THEN 'Red'
END as network_status
, N.network_id
FROM NETWORKS N
LEFT JOIN
(SELECT network_id
, sum(CASE WHEN [status] = 'redStatus' THEN 1 ELSE 0 END) as redCount
, sum(CASE WHEN [status] = 'greenStatus' THEN 1 ELSE 0 END) as greenCount
, count(*) as totalCircuits
FROM NETWORK_CIRCUITS
GROUP BY network_id) as circuits ON circuits.network_id = N.network_id

SQL display summation of data in row

I have a table like this
No.
--
b
r
g
g
r
b
r
g
I want resultset like below
Type of color | Ocurrence
Blue 2
green 3
red 3
TOTAL 8
Please help
Sounds like CASE and GROUP BY would be what you need;
SELECT
CASE WHEN color = 'r' THEN 'red'
WHEN color = 'g' THEN 'green'
WHEN color = 'b' THEN 'blue'
END "Type of color", COUNT(color) "Occurrence"
FROM Table1
GROUP BY color
ORDER BY color;
An SQLfiddle to test with.
To get a total, one (not necessarily the simplest) way is to just UNION with the total;
WITH cte AS (
SELECT
CASE WHEN color = 'r' THEN 'red'
WHEN color = 'g' THEN 'green'
WHEN color = 'b' THEN 'blue'
END "Type of color", COUNT(color) "Occurrence"
FROM Table1
GROUP BY color
UNION
SELECT 'TOTAL',COUNT(*)
FROM Table1
)
SELECT * FROM cte
ORDER BY CASE WHEN "Type of color" = 'TOTAL' THEN 1 END;
Another SQLfiddle.
Joachim's answer is fine, except there is an easier way to get the total using rollup:
SELECT
CASE WHEN color = 'r' THEN 'red'
WHEN color = 'g' THEN 'green'
WHEN color = 'b' THEN 'blue'
when color is NULL then 'Total'
END "Type of color", COUNT(*) "Occurrence"
FROM Table1
GROUP BY color with rollup
ORDER BY (case when color is null then 1 else 0 end), color

Select records based on column priority

First of all, the title of this question is horrible, but I didn't find a better way to describe my issue.
There's probably a very easy way to do this, but I couldn't figure it out. This is very similar to this question, but I'm running on sqlite3 (iOS) so I suspect my options are much more limited.
I have a table with product records. All records have an ID (note: I'm not talking about the row ID, but rather an identification number unique to each product). Some products may have two entries in the table (both with the same ID). The only difference would be in a special column (let's say column COLOUR can be either RED or GREEN).
What I want to do is create a list of unique products based on the value of COLOUR, with priority to GREEN if both GREEN and RED records exist for the same product.
In short, if I have the following case:
id PRODUCT ID COLOUR
1 1001 GREEN
2 1002 GREEN
3 1002 RED
4 1003 RED
I would like my SELECT to return the rows 1, 2 and 4. How can I achieve this?
My current approach is to have separate tables, and do the join manually, but obviously this is a very bad idea..
Note: I've tried to use the same approach from here:
SELECT *
FROM xx
WHERE f_COLOUR = "GREEN"
UNION ALL
SELECT *
FROM xx
WHERE id not in (SELECT distinct id
FROM xx
WHERE f_COLOUR = "GREEN");
But the result I'm getting is rows 1,2,3,4 instead of just 1,2,4. What am I missing?
Edit: One more question please: how can this be made to work with a subset of records, ie. if instead of the entire table I wanted to filter some records?
For example, if I had something like SELECT * FROM table WHERE productID LIKE "1%" ... how can I retrieve each unique product, but still respecting the colour priority (GREEN>RED)?
Your query is nearly correct. Just use PRODUCTID and not ID.
SELECT *
FROM xx
WHERE f_COLOUR = "GREEN"
UNION
SELECT *
FROM xx
WHERE PRODUCTID not in
(SELECT PRODUCTID
FROM xx
WHERE f_COLOUR = "GREEN");
SQLFiddle Demo
Try this
SELECT *
FROM xx
WHERE COLOUR = 'GREEN'
UNION
SELECT *
FROM xx WHERE P_Id not in
(SELECT P_Id
FROM Persons
WHERE COLOUR = 'GREEN');
See ALSO SQL FIDDLE DEMO
I just want to offer that you can do this with a group by:
select (case when sum(case when colour = 'Green' then 1 else 0 end) > 0
then max(case when colour = 'Green' then id end)
else max(case when colour = 'Red' then id end)
end) as id,
product_id
(case when sum(case when colour = 'Green' then 1 else 0 end) > 0 then 'Green'
else 'Red'
end) as colour
from t
group by product_id
You can have it like this
WITH PriorityTable
AS
(
SELECT T.*,
ROW_NUMBER() OVER (PARTITION BY T.ID
ORDER BY PT.ColorPriority ) PriorityColumn
FROM XX AS T
INNER JOIN (
SELECT 'RED' AS f_COLOUR , 1 AS ColorPriority
UNION
SELECT 'GREEN' AS f_COLOUR , 2 AS ColorPriority
) AS PT
ON T.f_COLOUR = PT.f_COLOUR
)
SELECT * FROM PriorityTable
WHERE PriorityColumn = 1