How to use SQL to label each row based on certain criteria? - sql

I need to correctly label each row based on certain criteria. For example the data I have is like this:
Table Product
Group product_id product_name category
1 123 Egg A
1 456 Egg A
1 456 Milk A
1 789 Milk A
2 135 Apple B
2. 137 Orange B
2. 137 Banana B
2. 139 Strawberry B
3. 235 Egg A
3. 237 Apple B
3. 237 Egg B
3. 239 Orange B
3. 239 Egg B
Since product egg can be found in more than 1 product IDs and milk can be found in more than 1 product IDs, 123,456 and 789 should be marked as A. Basically if a product name appears more than once in a group, then it is marked as A, otherwise B.
I was trying to use array functions and compare them, but it doesn't work for this scenario. For example,
select product_id,array_agg(product_name) as p1 from product
Then compare p1 with another array (p2) from the self inner join.
Any hints or help would be greatly appreciated!

Have you considered using a Case When statement?
Case
when product_name = 'egg' and category = 'a' then label = 'egg1'
when product_name = 'egg' and category = 'b' then label = 'egg2'
else 'no label'
End
I am referencing this post https://dba.stackexchange.com/questions/82487/case-with-multiple-conditions for clarity. - J

I am confused with your requirement. You state product name appears only once in a group, then it is marked as A, otherwise B. However, the data you show contains the exact opposite. The following produces what you said you wanted, not the values you posted. It will be correct or exactly the reverse. (See demo)
-- if a product name appears only once in a group, then it is marked as A, otherwise B.
with prod_group (group_id, product_name, cnt) as
( select group_id, product_name, count(*)
from products
group by group_id, product_name
) -- select * from prod_group ;
update products p
set category = case when grp.cnt = 1 then 'A' else 'B' end
from prod_group grp
where ( p.group_id, p.product_name) = ( grp.group_id, grp.product_name);
How it works: The prod_group CTE simply counts the number of times a product name appears in a group. The main "query" then uses this result to update category. Contrary to to your statement case isn't really going to help the CASE expression is exactly what you need.
Note: GROUP is an extremely poor choice for a column name as it is both a Postgres (conditional) and a SQL Standard reserved word.

Related

Select rows with same id but different values in another column grouped by a third column

I have a table like this:
ID
CODE
VALUE
1
A
123
1
A
456
1
C
123
1
B
789
2
A
344
2
B
344
I would like to see all cases that for the same ID we have different values for different codes.
Result:
ID 1, CODE B VALUE 789 NOT FOUND
ID 1, CODE A VALUE 456 NOT FOUND
In summary I need to see all diferences the I didn't find on column VALUE based in the ID key BUT NOT FOR THE SAME "CODE" COLUMN.
Anyone know how to achive that?
Thanks a lot!
Provided no duplicate rows exist, i.e. (ID, CODE, VALUE) is unique you can find VALUEs associated with exactly one CODE each with
select ID, max(CODE) CODE, VALUE
from tbl
group by ID, VALUE
having count(*) = 1

Pull column value from second to last row

I'm stuck in a loop of figuring out a game plan for this in SQL. Below is my sample data. I'm trying to create another column called "Starting Balance" which would be the amount in "Ending Balance" for the previous LINE. When I have that, I would only like to display where reason = count and forget about the rest.
I can't even fathom what approach to take and any advice would be appreciated.
Sample Data:
ITEM ID
ITEM
LAST UPDATED
REASON
ENDING BALANCE
LINE
123
Pencil
9/1/2020
Correction
400
1
123
Pencil
9/2/2020
Correction
450
2
123
Pencil
9/3/2020
Count
500
3
Expected Output:
ITEM ID
ITEM
LAST UPDATED
REASON
Starting Balance
ENDING BALANCE
123
Pencil
9/3/2020
Count
450
500
if "previous LINE" means the row with Lastupdated before current row:
select * from (
select * , lag(ENDING_BALANCE,1,0) over (partition by ItemId order by LASTUPDATED) as Starting Balance
from table
) t where t.REASON = 'count'
I can't bugtest this, but maybe something like:
SELECT
a.ITEM_ID,
a.ITEM,
a.LAST_UPDATED,
"Count" AS REASON,
b.ENDING_BALANCE AS Starting_Balance,
a.ENDING_BALANCE AS ENDING_BALANCE,
FROM table a
LEFT JOIN table b
ON a.ITEM_ID = b.ITEM_ID, a.LINE = b.LINE + 1
Note that we're joining two copies of the same table together here and labeling them a and b.
No clue if you can do ONs like that, but you could also make the join table have a LINE +1 column which you then use to join.

SQL statement, check if other rows have the same value

I have a SQL statement that imports my product inventory from a Access.MDB file. The select statement is below. (well a portion of it)
SELECT
Brand, DESCRIPTION AS Model,
SECONDDESCRIPTION AS Description,
PRODUCT AS [Product Code], TYPE AS Batch, INACTIVE,
CORE AS [Core Range],
IIF([CUSTORD] IS NULL, ROUND(ON_HAND), (IIF(TYPE = 'DISP',ROUND(ON_HAND),ROUND(ON_HAND)-CUSTORD))) AS SOH
You may notice that the select statement will minus any items that are on a customer order from the SOH values. for clarity below is the line that does just that.
IIF([CUSTORD] IS NULL, ROUND(ON_HAND), (IIF(TYPE = 'DISP',ROUND(ON_HAND),ROUND(ON_HAND)-CUSTORD))) AS SOH
The problem i have is, that 1 product code, can have multiple batches, and if an item only has a qty of 1 in each batch, and then the customer order column also contains a 1, this results in 1 - 1 = 0.
However the customer orders column is really indicating that only 1 of the product codes in on a customer order, not that specific batch.
Is there a way to check if that product code has already been "Selected" and has a Customer Order Qty against it and if it does then ignore the customer order qty against this next batch in the table?
To help explain it a little here is a rough idea of the table that would be imported.
Product
Batch_Number
ON_HAND
CUSTORD
Apples
123456
5
1
Apples
234567
1
1
Apples
587554
1
1
Bananas
1548777
1
0
so in the table above with my existing select statement, my results would be
Apples 4 in batch 123456
Bananas 1 in batch 1548777
As the next two lines of apples would actually end up with a value of 0 in batches 234567 and 587554
my program is set to then only return to the user values of items they can sell with a SOH qty > 0
so i need the final datatable in my program to look like this:
Product
Batch_Number
ON_HAND
CUSTORD
Apples
123456
5
1
Apples
234567
1
0
Apples
587554
1
0
Bananas
1548777
1
0
In my table Batch Number is the unique identifier and does not occur twice in the table.
Im working in VB.NET so if it could not be done in the SQL select statement i could be open to the idea of adjusting the values in the dataset datatable, however that would probably be made harder by the fact that the SQL Select statement i'm using never actually imports the CUSTORD column of data into my datatable. As i was trying to handle the SOH values directly at the select statement level.
Hope i have not confused anyone, and explained it as simple as possible.
I have no idea what your initial code has to do with the question. But let me assume that you have a table in the format shown in the question and you want to set on_hand to 0 for all but the first row for each product. You can use:
select product, batch_number, custord,
iif( t.batch_number = (select top 1 t2.batch_number
from t as t2
where t2.product = t.product
order by t2.on_hand desc, t2.batch_number
),
t.on_hand, 0
) as adjusted_on_hand
from t
order by product, on_hand desc, batch_number

Select unique record based on column value priority

This is a continuation of my previous question here.
In the following example:
id PRODUCT ID COLOUR
1 1001 GREEN
2 1002 GREEN
3 1002 RED
4 1003 RED
Given a product ID, I want to retrieve only one record - that with GREEN colour, if one exists, or the RED one otherwise. It sounds like I need to employ DISTINCT somehow, but I don't know how to supply the priority rule.
Pretty basic I'm sure, but my SQL skills are more than rusty..
Edit: Thank you everybody. One more question please: how can this be made to work with multiple records, ie. if the WHERE clause returns more than just one record? The LIMIT 1 would limit across the entire set, while what I'd want would be to limit just within each product.
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)?
try this:
SELECT top 1 *
FROM <table>
WHERE ProductID = <id>
ORDER BY case when colour ='GREEN' then 1
when colour ='RED' then 2 end
If you want to order it based on another color, you can give it in the case statement
SELECT *
FROM yourtable
WHERE ProductID = (your id)
ORDER BY colour
LIMIT 1
(Green will come before Red, you see. The LIMIT clause returns only one record)
For your subsequent edit, you can do this
select yourtable.*
from
yourtable
inner join
(select productid, min(colour) mincolour
from yourtable
where productid like '10%'
group by productid) v
on yourtable.productid=v.productid
and yourtable.colour=v.mincolour

SQL Joins Excluding Data

Say I have three tables:
Fruit (Table 1)
------
Apple
Orange
Pear
Banana
Produce Store A (Table 2 - 2 columns: Fruit for sale => Price)
-------------------------
Apple => 1.00
Orange => 1.50
Pear => 2.00
Produce Store B (Table 3 - 2 columns: Fruit for sale => Price)
------------------------
Apple => 1.10
Pear => 2.50
Banana => 1.00
If I would like to write a query with Column 1: the set of fruit offered at Produce Store A UNION Produce Store B, Column 2: Price of the fruit at Produce Store A (or null if that fruit is not offered), Column 3: Price of the fruit at Produce Store B (or null if that fruit is not offered), how would I go about joining the tables?
I am facing a similar problem (with more complex tables), and no matter what I try, if the "fruit" is not at "produce store a" but is at "produce store b", it is excluded (since I am joining produce store a first). I have even written a subquery to generate a full list of fruits, then left join Produce Store A, but it is still eliminating the fruits not offered at A. Any Ideas?
The key is to use a left join. This will include columns from the left side table even if there is no matching row in the right side table.
For example:
select f.name, a.price, b.price
from Fruit f
left join ProduceStoreA a on a.Name = f.Name
left join ProduceStoreB b on b.Name = f.Name
If a fruit is not found in Store A, the second column will be null. If it's not found in Store B, the third column will be null. If neither sells the fruit, both column two and three will be null.