Check for all NULLs within a group - sql

Find if a specific column in a group are all NULLs, then populate the target accordingly. I have a record-set as given below. My requirement is to populate the output column "total" based on -
1) Within a group (or Partition) if the "trans_dt" column in all the rows are NULLs, then populate "total" column in the output as zer0
2) If any of the records has a valid value (in trans_dt column) then populate the "total" with the max value of "items" for that that group and the trans_dt as the MAX trans_dt for that group
custid|transact_dt|items
------------------------
1234|05/01/2019|3
1234|10/02/2019|4
1234|Null|3
5678|Null|5
5678|Null|3
5678|Null|1
5678|Null|2
In the above record-set custid "1234" has valid values in trans_dt in 2 rows, hence the output column "total" should be populated as "4". However, for custid "5678", all trans_dt values are Nulls, hence "total" should be populated as 0.
custid|transact_dt|items
------------------------
1234|10/02/2019|4
5678|31/12/9999|0
select custid, max_trans_dt,
CASE WHEN max_trans_dt IS NULL then 0
ELSE total
END as total
from
( select custid, MAX(trans_dt) OVER (PARTITION BY custid) as max_trans_dt, MAX(items) OVER (PARTITION BY custid) as total,
ROW_NUMBER() OVER (PARTITION BY custid order by trans_dt desc, items desc) as rn ) tmp
WHERE tmp.rn = 1
Is there a smarter and cleaner solution to the above requirement ?
Thanks

Just use conditional aggregation:
select cust_id
max(case when trans_dt is not null then items else 0 end) as max_items
from t
group by cust_id;

Related

How to get records from table based on conditions

Select only latest amount, if null then before that.
table a
customer|amount|date
001|2 |20201101
001|null|20201102
001|3 |20201103
002|8.9 |20201101
002|7 |20201008
002|null|20201106
Result
001|null|20201101
001|null|20201102
001|3 |20201103
002|null|20201101
002|null|20201008
002|7 |20201106
amount data should be taken latest as per date , other record will be null, if amount is null for the latest date it should take the previous not null value.
My current attempt:
select top 1 [amount]
from table
where [amount] is not null
order by date desc
If you want to set all but the most recent value to NULL:
select customer_code, date,
(case when seqnum = 1 then amount end) as amount
from (select t.*,
row_number() over (partition by customer_code order by (amount is not null) desc, date desc) as seqnum
from table t
) t
where customer_code = '001'
order by date desc
Probably what you are looking for is a window function:
SELECT *
FROM (SELECT *,
row_number() over
(partition by customer
order by amount desc, date desc) as rn
FROM your_table
WHERE amount is not null)
WHERE rn = 1
You can use row_number or dense_rank depending on your needs
Create a view that returns all inserted values in descending order. Then select the first or second row according to the condition.

Sum having a condition

I've a table that has this information:
And need to get the following information:
If the country of the same person name (in this case Artur) is different, then I need to sum the two values of quantity from the max date (in this case 04/10) and return both person (Artur) and the qty (15k)
If the country of the same person name (in this case Joseph) is the same, then I need only the first row of the max date available.
I'm really struguling as I'm not sure how to implement the logic into my code:
Select
table.person,
table.quantity
From
(
Select
table.date,
table.person,
table.country,
table.quantity,
ROW_NUMBER () over (
PARTITION by table.code, table.person
ORDER by table.date DESC
) AS rn
FROM
table
WHERE table.date >= DATE '{2020-04-10}' -5
) a
WHERE a.RN IN (1,2)
Is it possible to create a rule to sum rows 1 and 2 when country is different (Artur case) and only return row number 1 when the country is the same for a name (Joseph case)?
Use dense_rank() or max() as a window function:
select person, sum(quantity)
from (select t.*,
max(date) over (partition by person) as max_date
from t
) t
where date = max_date
group by person;
EDIT:
Hmmm . . . I think you might want one row per country per person on the max date. If so:
select person, sum(quantity)
from (select t.*,
row_number() over (partition by person, country order by date desc) as seqnum_pc,
rank() over (partition by person order by date desc) as seqnum_p
from t
) t
where seqnum_p = 1 and seqnum_pc = 1
group by person;

sql : Select value based on values from other columns

I have a column for price. I need to select the price based on another column called status. If status is p then select that price first else select price from other status h. I need to make sure that query selects the price if status is p first when both status P & h are available.
You can use Rank function (if your DBMS supports), which will group the rows based on product and rank the sub group rows based on status. then you can apply where clause to select first rank rows from each sub-group. the query will look like below for MS sql-server.
select price
, [status]
, product
from
(select price
, [status]
, product
, RANK() over (PARTITION BY product
order by case
when [status]='p' then 1
else 0
end desc
) as rnk
from #tableA) Q
where rnk =1
Sample Input
Output

SQL QUERY to count repeats with 2 conditions

To find repeated items only when when it satisfies two conditions. In this example count repeats of item type for each customer_id only when it has order size "Big" and its corresponding date is before other instances. This first condition and repeats can be achieved by using this code.
Select Customer_id, Item_Type, COUNT(*)
from table
group by Customer_id, Item_Type
having count(*) > 1 and sum(case when Order_Size = 'Big' then 1 else 0 end) > 0;
how do I include date aspect as well to this?
I would do this as:
select t.customer_id, t.item_type, count(*)
from (select t.*,
min(case when OrderSize = 'Big' then date end) over (partition by customer_id, item_type) as min_big
from t
) t
where date > min_big
group by t.customer_id, t.item_type;
I believe you could use a window function in a subquery to decide which rows to count, then count them in your main query. Something like:
Select
customer_id, item_type, sum(count_pass) as Count
FROM
(
Select Customer_id,
Item_Type,
CASE
WHEN Order_Size = 'Big' THEN 0
WHEN MIN(Order_Size) OVER (PARTITION BY Customer_ID, Item_Type ORDER BY DateField ASC ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) = 'BIG' THEN 1
ELSE 0
END as count_pass
FROM table
) subqry
GROUP BY 1,2
That big case statement breaks down like:
If this record is 'Big' then ignore it
If you order all the records by date for each group of customer_id/item_type and look at all the records that precede this record, and the min(order_size) in that group of records (sorted lexicographically) is 'Big' then you have a preceding date with big and can count this record
Otherwise... you can't count it. Which would just be records with order_size='small' without a preceding 'big'.

reset a running total in same partition based on a condition

To make running total over groups is very easy today with sum over partition.
I have a need to reset the total in the same partition based on a condition, if some field in a row is false, the sum should be reset and begin from this row.
In code this is very easy, just loop over the rows and check for the condition. but how can we achieve this in SQL?
Here is a sample, it contains a table with four fields, and a query to sum the running amounts. the sum should be reset if the ResetSum field is true.
CREATE TABLE dbo.Table_1
(
PersonID int NOT NULL,
Amount money NOT NULL,
PayDate date NOT NULL,
ResetSum bit NOT NULL
)
INSERT INTO Table_1 (PersonID, Amount, PayDate, ResetSum)
VALUES (1, 100, '2015-1-1', 0)
,(1,200,'2015-1-2',0)
,(1,180,'2015-1-3',0)
,(1,200,'2015-1-4',1)
,(1,200,'2015-1-5',0)
,(1,360,'2015-1-6',0)
SELECT *,SUM(Amount) over(PARTITION BY PersonID ORDER BY PayDate) as SumAmount
FROM Table_1
Desired result should be 760, not 1140.
The records cannot be grouped by the ResetSum field, because if it is true, all the fields below this should be reset though the ResetField in this row is false.
here is a sample of my .net code, it is very simple:
Public Function SumTest() As Decimal
Dim lst As New List(Of TestRecords)
Dim sum As Decimal = 0
For Each tst As TestRecords In lst
If tst.ResetSum = true Then
sum = fcf.Amount
Else
sum += fcf.Amount
End If
Next
Return sum
End Function
Do a running total on ResetSum in a derived table and use that as a partition column in the running total on Amount.
select T.PersonID,
T.Amount,
T.PayDate,
sum(T.Amount) over(partition by T.PersonID, T.ResetSum
order by T.PayDate rows unbounded preceding) as SumAmount
from (
select T1.PersonID,
T1.Amount,
T1.PayDate,
sum(case T1.ResetSum
when 1 then 1
else 0
end) over(partition by T1.PersonID
order by T1.PayDate rows unbounded preceding) as ResetSum
from dbo.Table_1 as T1
) as T;
SQL Fiddle