postgres - having group by min of date time and status - sql

I would like to retrieve car_id from the below table which have
min (svc_date) with status=A .
expected is 103 and 100 which have status 'A'
because it have the status A with minimum svc_date
car_id svc_date status
100 03/01/2013 A
100 04/02/2013 B
100 05/03/2013 C
101 06/01/2013 A
101 05/01/2013 B
102 06/06/2013 A
102 05/05/2013 B
103 05/25/2013 A
i am using postgres and am trying
select car_id,svc_date,status from car_tbl group by
car_id having min(svc_date) and status='A'
svc_date is timestamp with timezone and
getting error
argument of AND must be type boolean, not type timestamp with time zone
is the entire sql is wrong or any type cast will help?

Really, it is hard to understand to you:
with cte as (
select
car_id ,
status,
rank() OVER (
PARTITION BY car_id
ORDER BY svc_date )
from car_tbl
)
select *
from cte
where rank = 1
and status = 'A'
Results:
| CAR_ID | STATUS | RANK |
--------------------------
| 100 | A | 1 |
| 103 | A | 1 |

It's rather difficult to see what you're really asking; but does this do what you want?
SELECT car_id, status, MIN(svc_date) FROM car_tbl WHERE status = 'A' GROUP BY car_id, status

Related

Time difference between rows based on condition

Not really an expert with SQL and im having problems figuring out how to do this one.
Got a table like this one:
ID
Message
TimeStamp
User
1
Hello
2022-08-01 10:00:00
A
1
How are you?
2022-08-01 10:00:05
A
1
Hello there
2022-08-01 10:00:10
B
1
I am okay
2022-08-01 10:00:12
B
1
Good to know
2022-08-01 10:00:15
A
1
Bye
2022-08-01 10:00:25
B
2
Hello
2022-08-01 10:02:50
A
2
Hi
2022-08-01 10:03:50
B
I need to calculate the time difference each time there is a response from the B user after a message from A.
Expected result would be like this
ID
Difference
1
5
1
10
2
60
Trying to use Lead function to obtain the next desired timestamp but im not getting the expected result
Any tips or advice?
Thanks
Even if it is already answered - it's a nice use case for Vertica's MATCH() clause. Looking for a pattern consisting of sender = 'A' followed by sender = 'B'.
You get a pattern id, and then you can group by other stuff plus the pattern id to get max timestamp and min timestamp.
Also note that I renamed both "user" and "timestamp", as they are reserved words...
-- your input, don't use in final query
indata(ID,Message,ts,Usr) AS (
SELECT 1,'Hello' ,TIMESTAMP '2022-08-01 10:00:00','A'
UNION ALL SELECT 1,'How are you?',TIMESTAMP '2022-08-01 10:00:05','A'
UNION ALL SELECT 1,'Hello there' ,TIMESTAMP '2022-08-01 10:00:10','B'
UNION ALL SELECT 1,'I am okay' ,TIMESTAMP '2022-08-01 10:00:12','B'
UNION ALL SELECT 1,'Good to know',TIMESTAMP '2022-08-01 10:00:15','A'
UNION ALL SELECT 1,'Bye' ,TIMESTAMP '2022-08-01 10:00:25','B'
UNION ALL SELECT 2,'Hello' ,TIMESTAMP '2022-08-01 10:02:50','A'
UNION ALL SELECT 2,'Hi' ,TIMESTAMP '2022-08-01 10:03:50','B'
)
-- end of input, real query starts here , replace following comma with "WITH"
,
w_match_clause AS (
SELECT
*
, event_name()
, pattern_id()
, match_id()
FROM indata
MATCH (
PARTITION BY id ORDER BY ts
DEFINE
sentbya AS usr='A'
, sentbyb AS usr='B'
PATTERN
p AS (sentbya sentbyb)
)
-- ctl SELECT * FROM w_match_clause;
-- ctl ID | Message | ts | Usr | event_name | pattern_id | match_id
-- ctl ----+--------------+---------------------+-----+------------+------------+----------
-- ctl 1 | How are you? | 2022-08-01 10:00:05 | A | sentbya | 1 | 1
-- ctl 1 | Hello there | 2022-08-01 10:00:10 | B | sentbyb | 1 | 2
-- ctl 1 | Good to know | 2022-08-01 10:00:15 | A | sentbya | 2 | 1
-- ctl 1 | Bye | 2022-08-01 10:00:25 | B | sentbyb | 2 | 2
-- ctl 2 | Hello | 2022-08-01 10:02:50 | A | sentbya | 1 | 1
-- ctl 2 | Hi | 2022-08-01 10:03:50 | B | sentbyb | 1 | 2
)
SELECT
id
, MAX(ts) - MIN(ts) AS difference
FROM w_match_clause
GROUP BY
id
, pattern_id
ORDER BY
id;
-- out id | difference
-- out ----+------------
-- out 1 | 00:00:05
-- out 1 | 00:00:10
-- out 2 | 00:01
With mySQL 8.0:
WITH cte AS (
SELECT id, user, timestamp, ( LEAD(user) OVER (ORDER BY timestamp) ) AS to_user,
TIME_TO_SEC(TIMEDIFF(LEAD(timestamp) OVER (ORDER BY timestamp), timestamp)) AS time_diff
FROM msg_tab
)
SELECT id, time_diff
FROM cte
WHERE user='A' AND to_user IN ('B', NULL)

Select row A if a condition satisfies else select row B for each group

We have 2 tables, bookings and docs
bookings
booking_id | name
100 | "Val1"
101 | "Val5"
102 | "Val6"
docs
doc_id | booking_id | doc_type_id
6 | 100 | 1
7 | 100 | 2
8 | 101 | 1
9 | 101 | 2
10 | 101 | 2
We need the result like this:
booking_id | doc_id
100 | 7
101 | 10
Essentially, we are trying to get the latest record of doc per booking, but if doc_type_id 2 is present, select the latest record of doc type 2 else select latest record of doc_type_id 1.
Is this possible to achieve with a performance friendly query as we need to apply this in a very huge query?
You can do it with FIRST_VALUE() window function by sorting properly the rows for each booking_id so that the rows with doc_type_id = 2 are returned first:
SELECT DISTINCT booking_id,
FIRST_VALUE(doc_id) OVER (PARTITION BY booking_id ORDER BY doc_type_id = 2 DESC, doc_id DESC) rn
FROM docs;
If you want full rows returned then you could use ROW_NUMBER() window function:
SELECT booking_id, doc_id, doc_type_id
FROM (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY booking_id ORDER BY doc_type_id = 2 DESC, doc_id DESC) rn
FROM docs
) t
WHERE rn = 1;

SQL order by two column, omit if second column doesn't meet the order

Let's say we have next data
id | date | price
------------------------
1 | 10-09-2016 | 200
2 | 11-09-2016 | 190
3 | 12-09-2016 | 210
4 | 13-09-2016 | 220
5 | 14-09-2016 | 200
6 | 15-09-2016 | 200
7 | 16-09-2016 | 230
8 | 17-09-2016 | 240
and we have to order by date first, and price second, however if the price must be in order. If current price is less than previous we should omit this row, and the result will be:
id | date | price
------------------------
1 | 10-09-2016 | 200
3 | 12-09-2016 | 210
4 | 13-09-2016 | 220
7 | 16-09-2016 | 230
8 | 17-09-2016 | 240
Is it possible without join?
Use LAG window function
SELECT *
FROM (SELECT *,
Lag(price)OVER( ORDER BY date) AS prev_price
FROM Yourtable) a
WHERE price > prev_price
OR prev_price IS NULL -- to get the first record
If "previous" is supposed to mean the previous row in the output, then keep track of a running maximum. Postgres solution with a window function in a subquery:
SELECT id, date, price
FROM (
SELECT *, price >= max(price) OVER (ORDER BY date, price) AS ok
FROM tbl
) sub
WHERE ok;
If Postgres:
select id, date, price
from
(select
t.*,
price - lag(price, 1, price) over (order by id) diff
from
your_table) t
where diff > 0;
If MySQL:
select id, date, price from
(
select t.*,
price - #lastprice diff,
#lastprice := price
from
(select *
from your_table
order by id) t
cross join (select #lastprice := 0) t2
) t where t.diff > 0;

get status on specific date

Table structure: (database is oracle 12c)
CUSTOMER_ID | STATUS | STATUS_FROM_DATE
101 | ABC | 10-01-2015
101 | PQR | 27-02-2015
101 | LMN | 04-08-2015
101 | ABC | 08-09-2015
Question: How to get status of customer for specific date from above table?
For example:
CUSTOMER_ID | Input Date | Expected Output
101 | 15-01-2015 | ABC
101 | 27-02-2015 | PQR
101 | 28-02-2015 | PQR
101 | 10-09-2015 | ABC
In above example,
ABC was the status of customer on 15-01-2015, because this is set on 10-01-2015 and not changed till 27-02-2015.
PQR was the status of customer on 28-02-2015, because this is set on 27-02-2015 and not changed till 04-08-2015.
You can use lead analytic function to get the end of interval. Then just search using between.
select * from
(
select
customer_id,
status,
status_from_date,
nvl(lead(status_from_date) over (partition by customer_id order by status_from_date)-1,
to_date('2099','yyyy')
)as end_date
from your_table
)
where your_date_here between status_start_date and end_date
Using newly introduced row limiting clause in Oracle 12c,
select <your input date> as date_, expected_status
from myt
where status_date <= <your input date>
order by status_date desc
fetch first 1 rows only;
SQL FIDDLE DEMO
with ranges as (
select t.*,
lead(STATUS_FROM_DATE,1, (select sysdate from dual))
over (partition by CUSTOMER_ID order by STATUS_FROM_DATE) as status_change
from Table1 t
)
select r.status, s."Date", s."Expected Output"
from ranges r
inner join TestStatus s
on s."Date" < r.status_change
and s."Date" >= r.STATUS_FROM_DATE;

Mysql query for better sorting of result

The mysql table that I'm using has a structure:
id | type | date
--------------------------------
101 | 1 | 2011-02-08
102 | 2 | 2011-02-08
103 | 2 | 2011-02-08
104 | 2 | 2011-02-07
105 | 1 | 2011-02-07
105 | 1 | 2011-02-07
What I want to do is create a query that will give the following result:
total type 1 | total type 2 | date
------------------------------------------------
1 | 2 | 2011-02-08
2 | 1 | 2011-02-07
I tried with following query but not getting the desired result
SELECT count(DISTINCT id) as total, date, type FROM my_table WHERE type !='0' GROUP BY date, type ORDER BY date DESC
How can I do that?
Thanks for all suggestions
Use:
SELECT t.date,
SUM(CASE WHEN t.type = 1 THEN 1 ELSE 0 END) AS total_type_1,
SUM(CASE WHEN t.type = 2 THEN 1 ELSE 0 END) AS total_type_2
FROM YOUR_TABLE t
WHERE t.type != 0
GROUP BY t.date
ORDER BY t.date DESC
I'm assuming the type column is numeric, not string based like the single quotes suggest. Change to suit if that's not the case.
Try this:
SELECT type, COUNT(*) as total_type, date FROM my_table WHERE type != '0' GROUP BY type ORDER BY date DESC
What you're asking for is pivot functionality, which I guess mysql isn't great at.
If it works for you, I'd suggest altering the output to:
type count date
==== ===== ====
1 1 2011-02-08
....
and rearrange at application logic. It would probably also make your SQL more db-independent.