Select records based on column priority - sql

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

Related

Find items in table with 2 specific sizes

I have items table where the item code repeats as it has different sizes, variants.
I want to find items which has 2 specific sizes, ie size in both M/Y and Euro.
Items table:
Id size
1 0
1 2Y
1 EU-15
2 2M
2 4M
3 0
3 2M-4M
3 EU-12
4 EU-11
4 EU-15
Required, I want to query for item id 1 and 3.
I was trying with SUM(), CASE but not able to figure it as it involves LIKE operator. (Size like '[^EU]%' and Size like 'EU%')
#Update:
With little hint, I could do it with 2 queries using temp table. Would be nice to see it in single query.
1st Query.
select id,
case when size like '[^EU]%' then 'S'
when size like 'EU%' then 'EU' END as size
into #t from table
2nd Query.
select id, size from table
where id in
( select id from #t
group by id
having count(distinct(size))>1)
order by id, size
Thanks.
I think you wanted Id with both EU% and non EU%
select t.Id
from tbl t
group by t.Id
having count(distinct case when size like 'EU%' then 1 else 2 end) = 2
You can use the analytical function as follows:
select * from
(select t.*,
count(case when Size like '%M' OR Size like '%Y' then 1 end)
over (partition by id) cnt1,
count(case when Size like 'EU%' then 1 end)
over (partition by id) cnt2
from your_Table t) t
where cnt1 > 0 AND cnt2 > 0

selecting details from the table based on the where condition on same column with different filtering option

I am having a table with below specified structure
From the table, I just want to retrieve the product id which is having Ram with value 12 and color with Blue. The expected result is 1.
I tried many queries and it's not sharing the expected result.
What will be the solution?
It's very difficult to manage the separate table for each feature as we have an undefined set of features.
You can use conditional aggregation:
select productid
from t
group by productid
having max(case when feature = 'Ram' then value end) = '12' and
max(case when feature = 'Color' then value end) = 'Blue';
use correlated subquery with not exists
select distinct product_id from tablename a
where not exists
(select 1 from tablename b where a.product_id=b.product_id and feature='Ram' and value<>12)
and not exists
(select 1 from tablename c where a.product_id=c.product_id and feature='Color' and value<>'blue')

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;

Simple SQL Query 1

I'm using PL/SQL if that matters.
Table = Stuff
ID: FRUIT:
100 Apple
100 Grape
200 Apple
200 Orange
550 Apple
700 Orange
800 Orange
900 Grape
... ...
I want to list all of the Apples and their IDs that do NOT share the same ID as Orange. How do I go about doing this?
The output should be:
100 Apple
550 Apple
You can do this with a subquery so you effectively pick all of the ID's for Oranges out in this subquery then pick all of the fruit which are Apples and ID's aren't in the subquery. Something like this;
SELECT *
FROM stuff
WHERE fruit = 'Apple'
AND ID NOT IN (SELECT ID FROM stuff WHERE fruit = 'Orange')
You can select only once from the table using CASE EXPRESSION and a GROUP BY WITH HAVING clause like this :
SELECT t.id,
MAX(CASE WHEN t.FRUIT = 'Apple' THEN t.FRUIT end) as fruit
FROM stuff t
GROUP BY t.id
HAVING MAX(CASE WHEN t.FRUIT = 'Orange' THEN 1 ELSE 0 END) = 0
You are subtracting one set of records from another and a subquery will do the job.
Edited for your new data set
select *
from stuff
where fruit = 'Apple'
and id not in (
select ID from stuff where fruit != 'Apple'
);
Or you could use a MINUS query as well.
There are no need to build up full list of Orange's IDs, just use not exist:
select *
from
stuff apple_list
where
fruit = 'Apple'
and
not exists (
select null
from stuff orange_instance
where orange_instance.id = apple_list.id
)
or do same thing with outer join:
select
id, fruit
from (
select
apple_list.id,
apple_list.fruit,
nvl2(orange_instance.id, 'orange_here', 'no_orange') orange_flag
from
stuff apple_list,
stuff orange_instance
where
apple_list.fruit = 'Apple'
and
orange_instance.id (+) = apple_list.id
and
orange_instance.fruit (+) = 'Orange'
)
where
orange_flag = 'no_orange'
Second variant needs distinct in select if there are possibility of having two Oranges with same id.
Or you could do it using the MINUS set operator:
SELECT a.ID, a.FRUIT
FROM STUFF a
WHERE a.FRUIT = 'Apple'
MINUS
SELECT b.ID, 'Apple' AS FRUIT
FROM STUFF b
WHERE b.FRUIT = 'Orange'
Best of luck.

SQL query fetching

Following is the data which is retrieved for a particular NO.
id color no
5939 Black 1
5959 Silver 1
7900 Blue 1
7593 Red 1
The table has Black so i have to return only id 5939. If my table doesn't have a record for Black then i have to return Silver ID 5959, same for Blue and Red.
Note : I should have only one input parameter to my SQL query and that is NO
A SQL Server answer
SELECT TOP 1 id,color,no
FROM YourTable
WHERE no=#no
ORDER BY CASE Color
WHEN 'Black' THEN 1
WHEN 'Silver' THEN 2
WHEN 'Blue' THEN 3
WHEN 'Red' THEN 4
END
Or a more portable answer
WITH T
As (SELECT id,
color,
no,
ROW_NUMBER() OVER ( ORDER BY CASE Color
WHEN 'Black' THEN 1
WHEN 'Silver' THEN 2
WHEN 'Blue' THEN 3
WHEN 'Red' THEN 4
END) AS RN
FROM YourTable
WHERE no = #no)
SELECT id,
color,
no
FROM T
WHERE RN = 1 ;
I would suggest something like "select * from table where no = ? limit 1". The keyword LIMIT depends on your database, for DB2 it would be "select * from table where no = ? fetch first 1 rows only".
Here is an option that will return the first Id for NO.
SELECT MIN(Id)
FROM Table a
WHERE No = ?