Getting latest values from different columns based on other column - sql

So I've a query like this
select
user_id,
MAX(
case when attribute = 'one' then newValue else 'No Update' end
) as one,
MAX(
case when attribute = 'two' then newValue else 'No Update' end
) as two,
MAX(
case when attribute = 'thre' then newValue else 'No Update' end
) as thre,
from table
group by user_id
This query returns result as max value for that particular attribute value in different column.
There is an updated_at column too in this table. Now instead of this, I want that the returned column should contain the latest value according to that updated_at field.
So basically the column one, two and thre should either contain latest values according to updated_at field. If no values are there, then the column should contain No Update string.
What could be the right way?
Example
user_id | attribute | newValue | updatedAt
1 | one | null | 2018-01-20
1 | one | b | 2018-01-21
1 | one | a | 2018-01-22
1 | two | null | 2018-01-23
1 | two | null | 2018-01-24
1 | two | null | 2018-01-25
So for above table the current query would return result as this coz b is the Max value for attribute=one
user_id | one | two
1 | b | No Update
But I want the result of column one to be latest one according to updatedAt column like this
user_id | one | two
1 | a | No Update

Postgres doesn't have first and last aggregation functions, but you can get similar functionality using arrays:
select user_id,
coalesce((array_agg(newvalue order by updatedAt desc) filter (where attribute = 'one'))[1], 'No update') as one,
coalesce((array_agg(newvalue order by updatedAt desc) filter (where attribute = 'two'))[1], 'No update') as two
from t
group by user_id;
Here is a db<>fiddle.

First use DISTINCT ON (user_id, attribute) to get the only the rows with the latest updatedAt for each attribute and then aggregate on the results:
select user_id,
coalesce(max(case when attribute = 'one' then newvalue end), 'No Update') one,
coalesce(max(case when attribute = 'two' then newvalue end), 'No Update') two
from (
select distinct on (user_id, attribute) *
from tablename
order by user_id, attribute, updatedAt desc
) t
group by user_id
See the demo.
Results:
> user_id | one | two
> ------: | :-- | :--------
> 1 | a | No Update

Related

SQL Select MAX value only when condition not match

I want to select max prices only when certain condition on the table are met. The table consist of this column
| id | product_item_code | distribution_channel | sap_no_shipto | valuation_type | price |
|----|-------------------|----------------------|---------------|----------------|-------|
| 1 | A | A | A | NULL | 200 |
| 2 | A | A | A | B | 2000 |
| 3 | A | A | A | C | 3000 |
I want to apply this logic
If the valuation_type are NULL use the price from that row
If the valuation_type are not null, use the highest price
Table have unique constrain combination
product_item_code | distribution_channel | sap_no_shipto | valuation_type
For now, I code it like this, but it doesn't satisfy the first condition.
Below are the subquery. Is there any way to apply the logic? Because this query only select the MAX price if there are null on valuation type, it will ignore it.
SELECT MAX(psp.price)
FROM product_special_prices psp
WHERE
psp.product_item_code = p.item_code AND
psp.distribution_channel = st.distribution_channel AND
psp.sap_no_shipto = st.sap_no_shipto
GROUP BY (
psp.product_item_code,
psp.distribution_channel,
psp.sap_no_shipto
)
You can order by price and take the highest:
SELECT DISTINCT ON (product_item_code, distribution_channel, sap_no_shipto)
price
FROM product_special_prices
ORDER BY product_item_code, distribution_channel, sap_no_shipto,
valuation_type IS NOT NULL, price DESC;
This will sort the rows with valuation_type of value NULL first, because FALSE < TRUE. DISTINCT ON will return the first row for each group.
Another option, you could use the coalesce and filtered max function as the following:
select product_item_code, distribution_channel, sap_no_shipto,
coalesce(max(price) filter (where valuation_type is null),
max(price) filter (where valuation_type is not null)) mx
from product_special_prices
group by product_item_code, distribution_channel, sap_no_shipto
Demo
Try with this
SELECT *
,NewColumn = CASE WHEN valuation_type IS NOT NULL THEN tb.price ELSE tb2.PriceMax END
FROM table tb
OUTER APPLY (
SELECT PriceMax = MAX(price) FROM table
) tb2

Conditional grouping of records

I have a problem with grouping records in PostgreSQL. I have a structure containing 3 columns, non unique id, name, group (it's old system and I can't change this structure).
Sample records:
id | name | group
-----+----------+------
1 | product1 | 0
1 | product1 | test
2 | product2 | test
3 | product3 | test123
I want the groups unequal 0 to be concatenated (get the id, name of the first record from the group).
The expected result:
id | name | group
-----+----------+------
1 | product1 | 0
1 | product1 | test
3 | product3 | test123
Currently count records in the following way:
SELECT
COUNT(CASE WHEN group = '0' THEN group END) +
COUNT(DISTINCT CASE WHEN group <> '0' THEN group END) AS count
FROM
table
Is it correct way? How can I convert it to retrieve records?
You can use row_number():
select id, name, group
from (select t.*, row_number() over (partition by group order by id) as seqnum
from t
) t
where seqnum = 1 or group = '0';
Note: group is a really bad name for a column. It is a SQL keyword, so you should escape the name. I am leaving it as is because your query uses it.

PosgtreSQL join tables on json field

Let's say we have 2 tables-
Employee:
id integer (pk)
name char
code char
Status:
id integer (pk)
key char
data jsnob
Now here is the sample data of above tables:
Employee
+----+--------+------+
| id | name | code |
+----+--------+------+
| 1 | Brian | BR1 |
| 2 | Andrew | AN1 |
| 3 | Anil | AN2 |
| 4 | Kethi | KE1 |
| 5 | Smith | SM1 |
+----+--------+------+
Status
+----+---------+---------------------------------------+
| id | key | data |
+----+---------+---------------------------------------+
| 1 | Admin | {'BR1':true, 'AN1':true,'KE1':false} |
| 2 | Staff | {'SM1':true, 'AN2':true,'KE1':false} |
| 3 | Member | {'AN2':false, 'AN1':true,'KE1':false} |
| 4 | Parking | {'BR1':true, 'AN1':true,'KE1':false, |
| | | 'AN2':true,'SM1':true} |
| 5 | System | {'AN2':false, 'AN1':true,'KE1':true} |
| 6 | Ticket | {'AN2':false, 'AN1':true,'KE1':false} |
+----+---------+---------------------------------------+
Now my goal is to get status and name of failure keys, employee code wise. For ex:-
I am not an expert in sql complex queries, so any help is much appreciate.
Note: Above are just sample tables (name and data changed), but design is similar to original tables.
you can use the function jsonb_each_text yo get key and value from jsonb type, and if we mix with sql and ... voilĂ , the following query is a example for you case :
select employee.code,
case
when dat2.count is null then 'TRUE'
else
'FALSE'
end as status,
case
when dat2.count is null then 0
else
dat2.count
end as failures, string_agg as key from employee left join
(
select key, count(*), string_agg(code,',') from (
select key code , (jsonb_each_text(data)).key,(jsonb_each_text(data)).value
from status) as dat
where value='false'
group by 1 ) dat2 on (employee.code=dat2.key)
As far as I can tell, you don't need the employee table for this (because you only want the code column which is also present in the JSON values of the status table). It's enough to unnest the JSON value and then aggregate on the code from that.
select d.code,
bool_and(d.flag::boolean) as status_flag,
count(*) filter (where not d.flag::boolean) as failures,
coalesce(string_agg(key, ', ') filter (where not d.flag::boolean), 'N/A') as keys
from status st
join lateral jsonb_each_text(st.data) as d(code, flag) on true
group by d.code
order by d.code;
The filter() option is used to only include rows in the aggregate that comply with the where condition. In this case those where the value for the code is false.
bool_and is an aggregate function for boolean values that returns true if all input values are true (and false otherwise)
Online example: https://rextester.com/PEKCZ52605
Use a left-join query between those tables, and apply jsonb_each_text() function for jsonb type column.
The trick is to use conditionals as case when (js).value = 'false' then .. else .. end for the aggregated columns :
select e.id, e.code,
min(case when (js).value = 'false' then 'FALSE' else 'TRUE' end ) as status,
count(case when (js).value = 'false' then 1 end) as failures,
coalesce(
string_agg(case when (js).value = 'false' then s.key end, ',' ORDER BY s.id),'NA'
) as key
from Employee e
left join
(
select *, jsonb_each_text(data) as js
from Status
) s on e.code = (js).key
group by e.id, e.code
order by e.id;
where (js).value is extracted from jsonb type Status.data column
Demo

Sum two rows with different IDs

I have the following data:
ID | Field A | Quantity
1 | A | 10
2 | B | 20
3 | C | 30
I would like to sum the fields with ids 1 and 3 in a way that result will be:
ID | Field A | Quantity
1 | A | 40
2 | B | 20
Sounds to me more like code manipulation rather than SQL, but still want to try it.
My DMBS is sql-server.
you can try by using case when
select case when id in(1,3) then 'A' else 'B' end as field,sum(Quantity) as Quantity
from tablename group by case when id in(1,3) then 'A' else 'B' end
I think simple aggregation does what you want:
select min(id) as id, min(fieldA) as fieldA, sum(quantity) as quantity
from t
group by (case when id in (1, 3) then 1 else id end);
Not sure what method to use to combine 1,3 ID but you could try case:
select
case when id in (1,3) then 1 else id end
, min("Field A") "Field A"
, sum(quantity) quantity from myTable
group by case when id in (1,3) then 1 else id end
The above uses group by to aggregate the data. In this case it organizes the ID field using some logic to combine 1,3. All other unique ID will have its own group.
Aggregate functions take care of the other fields, including logic to take the min() value for Field A which seems to fit your requirement

SQL Server - group by ID if column contains a value

I have following table:
ID | NR | Status
1000 | 1 | A
1000 | 2 | A
1001 | 3 | A
1002 | 4 | A
1002 | 5 | N
1003 | 6 | N
I need to an output which groups these by ID's. The NR column can be ignored. If one of the records with those ID's contains Status A, That status will be given as result.
So my output would be:
ID | Status
1000 | A
1001 | A
1002 | A
1003 | N
Any suggestions/ideas?
Although min() is the simplest method, it is not easily generalizable. Another method is:
select id
(case when sum(case when status = 'A' then 1 else 0 end) > 0
then 'A'
else 'N' -- or whatever
end) as status
from t
group by id;
Or, if you have a table with one row per id, then I would use exists:
select ids.id,
(case when exists (select 1 from t where t.id = ids.id and t.status = 'A')
then 'A' else 'N'
end) as status
from ids;
This saves on the group by aggregation and can use an index on (id, status) for optimal performance.
Do a GROUP BY, use MIN() to pick minimum status value for each id, and A < N!
select id, min(status)
from tablename
group by id
You want exactly the records that match the predicate "If one of the records with those ID's contains Status A, that status will be given as result." ?
The query can be written simply as:
Select distinct ID, STATUS from [your working TABLE] where STATUS = 'A'.
Hope this can help.