How can I do SQL query count based on certain criteria including row order - sql

I've come across certain logic that I need for my SQL query. Given that I have a table as such:
+----------+-------+------------+
| product | valid | Date |
+----------+-------+------------+
| 1 | null | 2016-05-10 |
| 1 | null | 2016-05-09 |
| 1 | yes | 2016-05-08 |
+----------+-------+------------+
This table is produced by a simple query:
SELECT * FROM products WHERE product = 1 ORDER BY date desc
Now what I need to do is create a query to count the number of nulls for certain products by order of date until there is a yes value. So the above example the count would be 2 as there are 2 nulls until a yes.
+----------+-------+------------+
| product | valid | Date |
+----------+-------+------------+
| 2 | null | 2016-05-10 |
| 2 | yes | 2016-05-09 |
| 2 | null | 2016-05-08 |
+----------+-------+------------+
Above would return 1 as there is 1 null until a yes.
+----------+-------+------------+
| product | valid | Date |
+----------+-------+------------+
| 3 | yes | 2016-05-10 |
| 3 | yes | 2016-05-09 |
| 3 | null | 2016-05-08 |
+----------+-------+------------+
Above would return 0.

You need a Correlated Subquery like this:
SELECT COUNT(*)
FROM products AS p1
WHERE product = 1
AND Date >
( -- maximum date with 'yes'
SELECT MAX(Date)
FROM products AS p2
WHERE p1.product = p2.product
AND Valid = 'yes'
)

This should do it:
select count(1) from table where valid is null and date > (select min(date) from table where valid = 'yes')

Not sure if your logic provided covers all the possible weird and wonderful extreme scenarios but the following piece of code would do what you are after:
select a.product,
count(IIF(a.valid is null and a.date >maxdate,a.date,null)) as total
from sometable a
inner join (
select product, max(date) as Maxdate
from sometable where valid='yes' group by product
) b
on a.product=b.product group by a.product

Related

Find the first order of a supplier in a day using SQL

I am trying to write a query to return supplier ID (sup_id), order date and the order ID of the first order (based on earliest time).
+--------+--------+------------+--------+-----------------+
|orderid | sup_id | items | sales | order_ts |
+--------+--------+------------+--------+-----------------+
|1111132 | 3 | 1 | 27,0 | 24/04/17 13:00 |
|1111137 | 3 | 2 | 69,0 | 02/02/17 16:30 |
|1111147 | 1 | 1 | 87,0 | 25/04/17 08:25 |
|1111153 | 1 | 3 | 82,0 | 05/11/17 10:30 |
|1111155 | 2 | 1 | 29,0 | 03/07/17 02:30 |
|1111160 | 2 | 2 | 44,0 | 30/01/17 20:45 |
|....... | ... | ... | ... | ... ... |
+--------+--------+------------+--------+-----------------+
Output I am looking for:
+--------+--------+------------+
| sup_id | date | order_id |
+--------+--------+------------+
|....... | ... | ... |
+--------+--------+------------+
I tried using a subquery in the join clause as below but didn't know how to join it without having selected order_id.
SELECT sup_id, date(order_ts), order_id
FROM sales s
JOIN
(
SELECT sup_id, date(order_ts) as date, min(time(order_date))
FROM sales
GROUP BY merchant_id, date
) m
on ...
Kindly assist.
You can use not exists:
select *
from sales
where not exists (
-- find sales for same supplier, earlier date, same day
select *
from sales as older
where older.sup_id = sales.sup_id
and older.order_ts < sales.order_ts
and older.order_ts >= cast(sales.order_ts as date)
)
The query below might not be the fastest in the world, but it should give you all information you need.
select order_id, sup_id, items, sales, order_ts
from sales s
where order_ts <= (
select min(order_ts)
from sales m
where m.sup_id = s.sup_id
)
select sup_id, min(order_ts), min(order_id) from sales
where order_ts = '2022-15-03'
group by sup_id
Assumed orderid is an identity / auto increment column

How to select the latest date for each group by number?

I've been stuck on this question for a while, and I was wondering if the community would be able to direct me in the right direction?
I have some tag IDs that needs to be grouped, with exceptions (column: deleted) that need to be retained in the results. After which, for each grouped tag ID, I need to select the one with the latest date. How can I do this? An example below:
ID | TAG_ID | DATE | DELETED
1 | 300 | 05/01/20 | null
2 | 300 | 03/01/20 | 04/01/20
3 | 400 | 06/01/20 | null
4 | 400 | 05/01/20 | null
5 | 400 | 04/01/20 | null
6 | 500 | 03/01/20 | null
7 | 500 | 02/01/20 | null
I am trying to reach this outcome:
ID | TAG_ID | DATE | DELETED
1 | 300 | 05/01/20 | null
2 | 300 | 03/01/20 | 04/01/20
3 | 400 | 06/01/20 | null
6 | 500 | 03/01/20 | null
So, firstly if there is a date in the "DELETED" column, I would like the row to be present. Secondly, for each unique tag ID, I would like the row with the latest "DATE" to be present.
Hopefully this question is clear. Would appreciate your feedback and help! A big thanks in advance.
Your results seem to be something like this:
select t.*
from (select t.*,
row_number() over (partition by tag_id, deleted order by date desc) as seqnum
from t
) t
where seqnum = 1 or deleted is not null;
This takes one row where deleted is null -- the most recent row. It also keeps each row where deleted is not null.
You need 2 conditions combined with OR in the WHERE clause:
the 1st is deleted is not null, or
the 2nd that there isn't any other row with the same tag_id and date later than the current row's date, meaning that the current row's date is the latest:
select t.* from tablename t
where t.deleted is not null
or not exists (
select 1 from tablename
where tag_id = t.tag_id and date > t.date
)
See the demo.
Results:
| id | tag_id | date | deleted |
| --- | ------ | ---------- | -------- |
| 1 | 300 | 2020-05-01 | |
| 2 | 300 | 2020-03-01 | 04/01/20 |
| 3 | 400 | 2020-06-01 | |
| 6 | 500 | 2020-03-01 | |

Conditionally apply date filter based on column - Oracle SQL

I have a table that looks like this:
| Type | DueDate |
|:----:|:---------:|
| A | 1/1/2019 |
| B | 2/3/2019 |
| C | NULL |
| A | 1/3/2019 |
| B | 9/1/2019 |
| C | NULL |
| A | 3/3/2019 |
| B | 4/3/2019 |
| C | NULL |
| B | 1/6/2019 |
| A | 1/19/2019 |
| B | 8/1/2019 |
| C | NULL |
What I need to accomplish is:
Grab all rows that have Type C. For any other type, only grab them if they have a due date AFTER May 1st 2019.
This is a dummy data -- in actuality, there are 10 or 15 types and about ~125M or so rows.
I have tried SELECT * FROM tblTest WHERE ((Type IN ('A', 'B') AND DueDate > '05-01-2019') OR Type = 'C') but that yields exactly the table above.
Simply changing WHERE DUEDATE >= '05/01/2019' filters outNULL`
How can I edit my WHERE statement to achieve desired results of below?
| Type | DueDate |
|:----:|:--------:|
| C | NULL |
| B | 9/1/2019 |
| C | NULL |
| C | NULL |
| B | 8/1/2019 |
| C | NULL |
SQL FIDDLE for reference
If your date were stored using the correct type, you would simply do:
select t.*
from tbltest
where duedate > date '2019-05-01' or type = 'C';
I would suggest you fix the duedate column to have the correct type. Until that is fixed, you can workaround the problem:
select t.*
from tbltest
where to_date(duedate, 'MM/DD/YYYY') > date '2019-05-01' or type = 'C';
As per the answer by gordon you need to use this in or condition.
If you have more conditions in where clause apart from what is mentioned in question, you need to group the conditions.
select *
from tbltest
where (duedate > DATE '2019-05-01'
or type = 'C') -- group these condition using brackets
And other_condition;
Actually your original query has or condition with all other conditions without any brackets and that yields all the rows in result.
Cheers!!

How can I return rows that meet criteria for occurring in one day, but over a date range?

I have a query (shown below) that returns all rows for UserID that have :
a JOIN,
a subsequent CANCEL, and then
a subsequent JOIN
But: I need to return UserIDs that meet this criteria of having a JOIN,CANCEL, then JOIN in sequence ON THE SAME DAY, but for a date range: for example BETWEEN 2016-11-01 and 2016-11-30. So in the example table below, UserIDs 12345, 9876, and 33445 would be returned.
I'm not sure how this is achieved - would be involve some sort of grouping on the timestamp date? Would a stored procedure that iterates over conditional tests for UserID and ActionType be a viable solution?
+--------+--------+----------------------+------------+------------------+
| rownum | UserID | Timestamp | ActionType | Return in query? |
+--------+--------+----------------------+------------+------------------+
| 1 | 12345 | 2016-11-01 08:25:39 | JOIN | yes |
| 2 | 12345 | 2016-11-01 08:27:00 | NULL | yes |
| 3 | 12345 | 2016-11-01 08:28:20 | DOWNGRADE | yes |
| 4 | 12345 | 2016-11-01 08:31:34 | NULL | yes |
| 5 | 12345 | 2016-11-01 08:32:44 | CANCEL | yes |
| 6 | 12345 | 2016-11-01 08:45:51 | NULL | yes |
| 7 | 12345 | 2016-11-01 08:50:57 | JOIN | yes |
| 1 | 9876 | 2016-11-01 16:05:42 | JOIN | yes |
| 2 | 9876 | 2016-11-01 16:07:33 | CANCEL | yes |
| 3 | 9876 | 2016-11-01 16:09:09 | JOIN | yes |
| 1 | 56565 | 2016-11-01 18:15:16 | JOIN | no |
| 2 | 56565 | 2016-11-01 19:22:25 | CANCEL | no |
| 3 | 56565 | 2016-11-01 20:05:05 | CANCEL | no |
| 1 | 34343 | 2016-11-01 05:32:56 | JOIN | no |
NEXT DAY
| 1 | 7878 | 2016-11-02 10:05:04 | JOIN | no |
| 2 | 7878 | 2016-11-02 10:06:06 | JOIN | no |
| 1 | 33445 | 2016-11-02 02:33:34 | JOIN | yes |
| 2 | 33445 | 2016-11-02 02:33:34 | NULL | yes |
| 3 | 33445 | 2016-11-02 02:37:56 | CANCEL | yes |
| 4 | 33445 | 2016-11-02 02:38:01 | JOIN | yes |
+--------+--------+----------------------+------------+------------------+
Here is a link to the question which led me to the query that pulls data for exactly one day (not a range): How can I return rows that meet a specific sequence of events?
Here is the query:
SELECT *
FROM T
WHERE USERID IN (
select distinct userid
from t first_join
inner join t cancel
on first_join.tmstmp < cancel.tmstp
and first_join.userid = cancel.userid
inner join t.second_join
on second_join.tmstmp > cancel.tmstp
and second_join.userid = cancel.userid
where first_join.actiontype = 'JOIN'
and cancel.actiontype = 'CANCEL'
and second_join.actiontype = 'JOIN'
)
Clarification of comments/questions:
vkp:
QUESTION: can join,cancel,join be on different days with other values in between? ANSWER: No, I need to find the join>cancel>join that occur in one day only. If there is a join on 11/1 and a cancel on 11/2, that UserID does not need to be returned.
QUESTION: if a particular date satisfies cancel,join,cancel in the date range, will that be enough for a user to be included in the results? ANSWER: No, I am specifically looking at rows that meet the ActionType sequence in one day, not over a range of days.
THANK YOU!
To get all the users and the days when they have the specified sequence of events happen, use
select distinct userid,tmstamp::date
from t
where ActionType = 'CANCEL' and tmstamp::date between date '2016-11-01' and date '2016-11-30' and
exists (select 1
from t t2
where t2.userId = t.userId and
t2.actiontype = 'JOIN' and
t2.tmstamp < t.tmstamp and
t2.tmstamp::date = t.tmstamp::date
) and
exists (select 1
from t t3
where t3.userId = t.userId and
t3.actiontype = 'JOIN' and
t3.tmstamp > t.tmstamp and
t3.tmstamp::date = t.tmstamp::date
)
To get all the rows for such users on those days, wrap the previous query as a subquery against the original table.
select * from t where (userid,tmstamp::date) in (
select distinct userid,tmstamp::date
from t
where ActionType = 'CANCEL'
and tmstamp::date between date '2016-11-01' and date '2016-11-30' and
exists (select 1
from t t2
where t2.userId = t.userId and
t2.actiontype = 'JOIN' and
t2.tmstamp < t.tmstamp and
t2.tmstamp::date = t.tmstamp::date
) and
exists (select 1
from t t3
where t3.userId = t.userId and
t3.actiontype = 'JOIN' and
t3.tmstamp > t.tmstamp and
t3.tmstamp::date = t.tmstamp::date
)
)
Sample Demo
Note that this is a minor tweak to #Gordon's query (to check for these sequence of events on a particular day) in your previous question which i felt was the best.
Edit: An alternate approach with window functions
select * from t where (userid,tmstamp::date) in (
select distinct userid,tmstamp::date from (
select t.*
,min(case when actiontype = 'JOIN' then 1 else 2 end) over(partition by t.userid,t.tmstamp::date order by t.tmstamp rows between unbounded preceding and 1 preceding) min_before
,min(case when actiontype = 'JOIN' then 1 else 2 end) over(partition by t.userid,t.tmstamp::date order by t.tmstamp rows between 1 following and unbounded following) min_after
from (select userid,tmstamp from t where actiontype='CANCEL') tc
join t on t.userid=tc.userid and t.tmstamp::date=tc.tmstamp::date
) x
where min_before=1 and min_after=1
)
1) Using a case expression we designate all actiontype JOIN rows as 1 and 2 for all other actiontypes.
2) We join it with the actiontype CANCEL rows.
3) Then we check for the minimum value before CANCEL and minimum value after CANCEL for each date and userid combination. Per the case expression defined, it should be 1.
4) Get all such dates and userid's and fetch the corresponding rows.

SQL Update based on aggregate record set

I have a table with purchase orders:
po_line table
+--------+---------+-----------+
| po_num | po_line | date |
+--------+---------+-----------+
| 1 | 1 | 9/22/2013 |
| 1 | 2 | 9/22/2013 |
| 1 | 3 | 9/22/2013 |
| 2 | 1 | 9/21/2013 |
| 2 | 2 | NULL |
+--------+---------+-----------+
po table
+--------+-----------+
| po_num | confirmed |
+--------+-----------+
| 1 | NULL |
| 2 | NULL |
+--------+-----------+
For a given po, example po_num 1, I am wanting to update a value in table 2 to 'confirmed' if all the records have a date in them for those lines. Example 1 would populate confirmed. PO 2 would fail the criteria since line 2 has no date.
Do I need to use a cursor to do this? Running sql 2008 r2.
UPDATE po SET confirmed = 'confirmed'
FROM po T
WHERE
NOT T.po_num IN
(
SELECT po_num FROM po_line
WHERE po_date IS NULL
)
Alternatively, if you want to make sure that are entries for each po in the po_line table before confirming, you can use:
update po set confirmed = 'confirmed'
where po.po_num in (select po_num from
(select po_num, count(po_date) dated, count(*) total from po_line group by po_num) q
where dated=total)
as shown in http://sqlfiddle.com/#!6/b16988/8/0