SQL get only earlier date in repetitive rows - mysql

I have an SQL table contains transactions like this
ID | FK | STATUS | DATE
1 | A | K1 | 2021-01-01
2 | A | K1 | 2021-01-15
3 | A | K2 | 2021-01-30
4 | A | K2 | 2021-02-03
5 | B | K1 | 2021-01-12
6 | C | K1 | 2021-03-30
7 | C | K3 | 2021-09-15
As we can see, some FK have some records with different STATUS and different DATE. From each FK I want to get the first/earlier transaction date with status K1.
Maybe something like this.
ID | FK | STATUS | DATE
1 | A | K1 | 2021-01-01
5 | B | K1 | 2021-01-12
6 | C | K1 | 2021-03-30
How can I get the result like that?
Note : I'm using MariaDB version 10.5.9-MariaDB

CREATE TABLE my_table (
`ID` INTEGER,
`FK` VARCHAR(1),
`STATUS` VARCHAR(2),
`DATE` DATETIME
);
INSERT INTO my_table
(`ID`, `FK`, `STATUS`, `DATE`)
VALUES
('1', 'A', 'K1', '2021-01-01'),
('2', 'A', 'K1', '2021-01-15'),
('3', 'A', 'K2', '2021-01-30'),
('4', 'A', 'K2', '2021-02-03'),
('5', 'B', 'K1', '2021-01-12'),
('6', 'C', 'K1', '2021-03-30'),
('7', 'C', 'K3', '2021-09-15');
Query #1
SELECT
ID,
FK,
STATUS,
DATE
FROM (
SELECT
*,
row_number() over (partition by FK,STATUS order by DATE ASC) rn from my_table
) t
where STATUS='K1' and rn=1;
ID
FK
STATUS
DATE
1
A
K1
2021-01-01 00:00:00
5
B
K1
2021-01-12 00:00:00
6
C
K1
2021-03-30 00:00:00
View on DB Fiddle

WITH cte_customers AS (
SELECT
ROW_NUMBER() OVER(PARTITION BY fk,status
ORDER BY
date
) row_num,
customer_id,
fk,
status
FROM
sales.customers
) SELECT
*
FROM
cte_customers
WHERE
row_num =1;

This is solved with a windowing function:
WITH RowNumbers AS (
SELECT *,
row_number() OVER (PARTITION BY FK ORDER BY Date) As rn
FROM [MyTable]
WHERE Status = 'K1'
)
SELECT ID, FK, STATUS, DATE
FROM RowNumbers
WHERE rn = 1
Window functions were added to MariaDB in version 10.2. The CTE is also needed because row_number() isn't computed until after the WHERE clause is processed.

Related

merging multiple rows into one row with different columns sql

I have two tables
Account table
id | account_no
-----------------------
1 | 111
2 | 222
Account details
id | act_id (fk) | amount | created_dt_ | created_by
------------------------------------------------
1 | 1 | 10 | 2022-10-30 | SYSTEM
2 | 1 | 100 | 2022-11-05 | user1
3 | 1 | 144 | 2022-11-10 | user2
4 | 1 | 156 | 2022-11-16 | user3
5 | 2 | 50 | 2022-11-05 | SYSTEM
6 | 2 | 51 | 2022-11-10 | user2
7 | 3 | 156 | 2022-11-16 | SYSTEM
I need a query to fetch only rows from account details which has at least 2 records for an account id, and merge those rows to a single row showcasing the initial amount and user who created it and the last amount and who created it, something like this
act_id | ini_amt | ini_dt | ini_usr | fnl_amt | fnl_dt | fnl_usr
-------------------------------------------------------------------------------------
1 | 10 | 2022-10-30 | SYSTEM | 156 | 2022-11-16 | user3
2 | 50 | 2022-11-05 | SYSTEM | 51 | 2022-11-10 | user2
we need only the rows with more than one records. How do i fetch that?
In MySQL 8 you could do it like this.
If you need also information fom account, you simle can join it
CREATE TABLE Account
(`id` int, `account_no` int)
;
INSERT INTO Account
(`id`, `account_no`)
VALUES
(1, 111),
(2, 222)
;
Records: 2 Duplicates: 0 Warnings: 0
CREATE TABLE Account_details
(`id` int, `act_id` int, `amount` int, `created_dt_` varchar(10), `created_by` varchar(6))
;
INSERT INTO Account_details
(`id`, `act_id`, `amount`, `created_dt_`, `created_by`)
VALUES
(1, 1, 10, '2022-10-30', 'SYSTEM'),
(2, 1, 100, '2022-11-05', 'user1'),
(3, 1, 144, '2022-11-10', 'user2'),
(4, 1, 156, '2022-11-16', 'user3'),
(5, 2, 50, '2022-11-05', 'SYSTEM'),
(6, 2, 51, '2022-11-10', 'user2'),
(7, 3, 156, '2022-11-16', 'SYSTEM')
;
Records: 7 Duplicates: 0 Warnings: 0
WITH CTE_MIN as(
SELECT
`act_id`, `amount`, `created_dt_`, `created_by`,
ROW_NUMBER() OVER(PARTITION BY `act_id` ORDER BY `created_dt_` ASC,`id` ASC) rn
FROM Account_details),
CTE_MAX as(
SELECT
`act_id`, `amount`, `created_dt_`, `created_by`,
ROW_NUMBER() OVER(PARTITION BY `act_id` ORDER BY `created_dt_` DESC,`id` DESC) rn
FROM Account_details)
SELECT
mi.`act_id`, mi.`amount`, mi.`created_dt_`, mi.`created_by`, ma.`amount`, ma.`created_dt_`, ma.`created_by`
FROM
CTE_MIN mi JOIN CTE_MAX ma
ON mi.`act_id` = ma.`act_id`
AND mi.rn = ma.rn
AND mi.created_dt_!=ma.created_dt_
AND ma.rn = 1 ANd mi.rn = 1
act_id
amount
created_dt_
created_by
amount
created_dt_
created_by
1
10
2022-10-30
SYSTEM
156
2022-11-16
user3
2
50
2022-11-05
SYSTEM
51
2022-11-10
user2
fiddle
We can do this without CTEs, using window functions and conditional aggregation:
select act_id,
max(case when rn_asc = 1 then amount end) ini_amount,
max(case when rn_asc = 1 then created_dt end) ini_created_dt,
max(case when rn_asc = 1 then created_by end) ini_created_by,
max(case when rn_desc = 1 then amount end) fnl_amount,
max(case when rn_desc = 1 then created_dt end) fnl_created_dt,
max(case when rn_desc = 1 then created_by end) fnl_created_by
from(
select ad.*,
row_number() over(partition by act_id order by created_dt ) rn_asc,
row_number() over(partition by act_id order by created_dt desc) rn_desc,
count(*) over(partition by act_id) cnt
from account_details ad
) ad
where 1 in (rn_asc, rn_desc) and cnt > 1
group by act_id
In the subquery, row_number ranks records of the same account by ascending and descending date, while count checks how many records the account has.
Then, the outer query filters on accounts that have more than one record, and on the top/bottom record. We can then pivot the dataset with group by and conditional expressions to produce the expected result.
On older MySQL version which doesn't support windows functions:
select act_id,
max(case when new_col='min_value' then amount end) as ini_amt,
max(case when new_col='min_value' then created_dt end) as ini_dt,
max(case when new_col='min_value' then created_by end) as ini_usr,
max(case when new_col='max_value' then amount end) as fnl_amt,
max(case when new_col='max_value' then created_dt end) as fnl_dt,
max(case when new_col='max_value' then created_by end) as fnl_usr
from (
select ad.id,ad.act_id,ad.amount,ad.created_dt,ad.created_by,'max_value' as new_col
from AccountDetails ad
inner join (select act_id,max(created_dt) as max_created_dt
from AccountDetails
group by act_id
having count(*) >=2
) as max_val on max_val.act_id =ad.act_id and max_val.max_created_dt=ad.created_dt
union
select ad1.id,ad1.act_id,ad1.amount,ad1.created_dt,ad1.created_by,'min_value'
from AccountDetails ad1
inner join (select act_id,min(created_dt) as min_created_dt
from AccountDetails
group by act_id
having count(*) >=2
) as min_val on min_val.act_id =ad1.act_id and min_val.min_created_dt=ad1.created_dt
) as tbl
group by act_id;
https://dbfiddle.uk/q2Oxq0Ay

Select all orders with the specified status saved in another table (PDO)

At the moment i have all informations of a order in one table, including the order status.
In the future i will have a new table "status" to make a order history.
My tables currently look like this (simplified):
Table "orders":
id
date
name
10001
2021-08-24 16:47:52
Surname Lastname
10002
2021-08-30 17:32:05
Nicename Nicelastname
Table "status":
id
order_id
statusdate
status
1
10001
2021-08-24 16:47:52
new
2
10002
2021-08-30 17:32:05
new
3
10001
2021-08-26 13:44:11
pending
4
10001
2021-09-02 10:01:12
shipped
My problem is: At this moment i can select all orders with status "shipped" to list them like that:
$sql = $pdo->prepare("SELECT * FROM orders WHERE status = ?");
(? is my status, e.g. "shipped")
I know i have to use LEFT JOIN to combine the two tables (correct? or is there a better/easier way?), but i have absolutely no idea how i can select all orders with status X, because the "status" table can have multiple entries per order_id... So the statement must select only the newest entrie!?
Iy you have no 1 timestamps with the same date, you can use the first query the second is in case you can have multiple timestampo for the same order
CREATE TABLE orders
(`id` int, `date` varchar(19), `name` varchar(21))
;
INSERT INTO orders
(`id`, `date`, `name`)
VALUES
(10001, '2021-08-24 16:47:52', 'Surname Lastname'),
(10002, '2021-08-30 17:32:05', 'Nicename Nicelastname')
;
CREATE TABLE status
(`id` int, `order_id` int, `statusdate` varchar(19), `status` varchar(7))
;
INSERT INTO status
(`id`, `order_id`, `statusdate`, `status`)
VALUES
(1, 10001, '2021-08-24 16:47:52', 'new'),
(2, 10002, '2021-08-30 17:32:05', 'new'),
(3, 10001, '2021-08-26 13:44:11', 'pending'),
(4, 10001, '2021-09-02 10:01:12', 'shipped')
,
(5, 10001, '2021-09-02 10:01:13', 'shipped')
;
select o.`id` FROM orders o
| id |
| ----: |
| 10001 |
| 10002 |
select o.`id`,o.`date`, o.`name`,s.`statusdate`
from orders o join status s on o.`id` = s.order_id
where s.`status` = "shipped"
AND s.`statusdate` = (SELECT MAX(`statusdate`) FROM `status` WHERE order_id = o.`id` AND `status` = "shipped")
order by o.`id` desc
id | date | name | statusdate
----: | :------------------ | :--------------- | :------------------
10001 | 2021-08-24 16:47:52 | Surname Lastname | 2021-09-02 10:01:13
SELECT
id, `date`, `name`,`statusdate`
FROM
(SELECT `date`, `name`,`statusdate`,IF( `id` = #id,#rownum := #rownum +1,#rownum :=1) rn, #id := `id` as id
FROM (select o.`id`,o.`date`, o.`name`,s.`statusdate`
from orders o join status s on o.`id` = s.order_id
where s.`status` = "shipped"
order by o.`id` desc,s.`statusdate` DESC) t2 ,(SELECT #id := 0, #rownum:=0) t1) t2
WHERE rn = 1
id | date | name | statusdate
----: | :------------------ | :--------------- | :------------------
10001 | 2021-08-24 16:47:52 | Surname Lastname | 2021-09-02 10:01:13
db<>fiddle here

Time difference between rows

In a mysql database (ver.5.7.15) I've got the following table named operation_list containing the following data: id (autoincrement integer, primary key), operation_date_time (datetime), operation (enumerated values: START,STOP), so the table looks like that:
+------+---------------------+-----------+
| id | operation_date_time | operation |
+------+---------------------+-----------+
| 1 | 2000-01-01 06:30:45 | START |
| 2 | 2000-01-01 07:45:00 | STOP |
| 3 | 2000-01-01 08:18:12 | START |
| 4 | 2000-01-01 11:23:58 | STOP |
| 5 | 2000-01-01 15:45:01 | START |
| 6 | 2000-01-01 19:01:33 | STOP |
+------+---------------------+-----------+
Now, assuming that the first row is always a START, the last ROW is always a STOP, the STOP is always placed after a START, I need to retrieve the time difference between START and STOP in seconds. Hence, I need to write an SQL that would produce the following recordset:
+------+---------------------+-----------+
| id | operation_date_time | duration |
+------+---------------------+-----------+
| 1 | 2000-01-01 06:30:45 | 4455 |
| 3 | 2000-01-01 08:78:12 | 11146 |
| 5 | 2000-01-01 15:45:01 | 11792 |
+------+---------------------+-----------+
Where 4455 is equivalent to 1 hour, 14 minutes and 15 seconds,
11146 is equivalent to 3 hours, 5 minutes and 46 seconds,
11792 is equivalent to 3 hours, 16 minutes and 32 seconds, and so on.
What's the best way to do it in a single SQL statement without creating additional tables or dedicated scripting?
This works IN mysql 5.X
But it is uglier as in 8.0
SELECT
MIN(id) id
,MIN(`operation_date_time`) `operation_date_time`
,MAX(diff) duration
FROM
(SELECT
id
, IF(`operation` = 'START', 0,TIME_TO_SEC(TIMEDIFF(`operation_date_time`, #datetime))) diff
,IF(`operation` = 'START', #count := #count + 1,#count := #count) groupby
,#datetime := `operation_date_time` `operation_date_time`
FROM
(SELECT * FROM timetable ORDER by `operation_date_time` ASC) t1, (SELECT #datetime := NOW()) a,
(SELECT #count := 0) b) t2
GROUP by groupby;
CREATE TABLE timetable (
`id` INTEGER,
`operation_date_time` VARCHAR(19),
`operation` VARCHAR(5)
);
INSERT INTO timetable
(`id`, `operation_date_time`, `operation`)
VALUES
('1', '2000-01-01 06:30:45', 'START'),
('2', '2000-01-01 07:45:00', 'STOP'),
('3', '2000-01-01 08:18:12', 'START'),
('4', '2000-01-01 11:23:58', 'STOP'),
('5', '2000-01-01 15:45:01', 'START'),
('6', '2000-01-01 19:01:33', 'STOP');
✓
✓
SELECT
MIN(id) id
,MIN(`operation_date_time`) `operation_date_time`
,MAX(diff) duration
FROM
(SELECT
id
, IF(`operation` = 'START', 0,TIME_TO_SEC(TIMEDIFF(`operation_date_time`, #datetime))) diff
,IF(`operation` = 'START', #count := #count + 1,#count := #count) groupby
,#datetime := `operation_date_time` `operation_date_time`
FROM
(SELECT * FROM timetable ORDER by `operation_date_time` ASC) t1, (SELECT #datetime := NOW()) a,
(SELECT #count := 0) b) t2
GROUP by groupby;
id | operation_date_time | duration
-: | :------------------ | -------:
1 | 2000-01-01 06:30:45 | 4455
3 | 2000-01-01 08:18:12 | 11146
5 | 2000-01-01 15:45:01 | 11792
db<>fiddle here
SELECT operation_date_time,DURATION FROM (
SELECT *,DATEDIFF(SECOND,operation_date_time,LEAD(operation_date_time)OVER(ORDER BY ID)) AS DURATION FROM PRACTICE
)A
WHERE operation='START'
Use window functions to get the ending time. I am going to use a cumulative conditional min:
select t.*,
timestampdiff(second, operation_date_time, stop_dt) as diff_seconds
from (select t.*,
min(case when operation = 'STOP' then operation_date_time end) over (order by operation_date_time) as stop_dt
from t
) t
where operation = 'START';
If the data really is interleaved, then you could just use lead():
select t.*,
timestampdiff(second, operation_date_time, stop_dt) as diff_seconds
from (select t.*,
lead(operation_date_time) over (order by operation_date_time) as stop_dt
from t
) t
where operation = 'START';
EDIT:
In MySQL pre-8.0:
select t.*,
timestampdiff(second, operation_date_time, stop_dt) as diff_seconds
from (select t.*,
(select min(t2.operation_date_time)
from t t2
where t2.operation = 'STOP' and
t2.operation_date_time > t.operation_date_time
) as stop_dt
from t
) t
where operation = 'START';

Insert ordinal number

MySQL 5.7
Consider the following sample data:
CREATE TABLE catalog_product_entity_media_gallery (
`value` VARCHAR(24),
`entity_id` INTEGER
);
INSERT INTO catalog_product_entity_media_gallery
(`value`, `entity_id`)
VALUES
('a01-some-item-p1-png.png', '1'),
('a01-some-item-p2-png.png', '1'),
('a01-some-item-d1-png.png', '1'),
('a01-some-item-d5-png.png', '1'),
('another-transparent.png', '2'),
('another-back.png', '2'),
('another-front.png', '2'),
('another-side.png', '2');
CREATE TABLE catalog_product_entity (
`entity_id` INTEGER,
`sku` VARCHAR(3)
);
INSERT INTO catalog_product_entity
(`entity_id`, `sku`)
VALUES
('1', 'a01'),
('2', 'b22');
CREATE TABLE catalog_product_entity_varchar (
`attribute_id` INTEGER,
`value` VARCHAR(24)
);
INSERT INTO catalog_product_entity_varchar
(`attribute_id`, `value`)
VALUES
('85', 'a01-some-item-p1-png.png'),
('85', 'another-transparent.png');
DB Fiddle of same: https://www.db-fiddle.com/f/7fAx1waY3TwjR34PanBkkv/0
With the query below, I get the following result:
select
a.value as 'original_file_name',
b.sku,
if(isnull(c.attribute_id), 0, 1) as 'is_default',
concat(sku, '_', if(isnull(c.attribute_id), concat('slideshow_', 'x'), 'default_1'), '.', substring_index(a.value, "." , -1)) as 'new_file_name'
from catalog_product_entity_media_gallery a
join catalog_product_entity b on b.entity_id = a.entity_id
left join catalog_product_entity_varchar c on c.attribute_id = 85 and c.value = a.value
order by sku, is_default desc;
+--------------------------+-----+------------+---------------------+
| original_file_name | sku | is_default | new_file_name |
+--------------------------+-----+------------+---------------------+
| a01-some-item-p1-png.png | a01 | 1 | a01_default_1.png |
| a01-some-item-p2-png.png | a01 | 0 | a01_slideshow_x.png |
| a01-some-item-d1-png.png | a01 | 0 | a01_slideshow_x.png |
| a01-some-item-d5-png.png | a01 | 0 | a01_slideshow_x.png |
| another-transparent.png | b22 | 1 | b22_default_1.png |
| another-back.png | b22 | 0 | b22_slideshow_x.png |
| another-front.png | b22 | 0 | b22_slideshow_x.png |
| another-side.png | b22 | 0 | b22_slideshow_x.png |
+--------------------------+-----+------------+---------------------+
In the new_file_name column, I want to insert an ordinal number in place of x. It should start at 1 for every new sku.
Wanted result:
a01_default_1.png
a01_slideshow_1.png
a01_slideshow_2.png
a01_slideshow_3.png
b22_default_1.png
b22_slideshow_1.png
b22_slideshow_2.png
b22_slideshow_3.png
Kindly try it
;with cte as (
select a.*,ROW_NUMBER() over (partition by new_file_name order by ID) as ROWNUMBER
from catalog_product_entity_media_gallery a
)
select concat(a.new_file_name,'_',ROWNUMBER) from cte
Using a variable that holds a number that increments for every row.
When is_default = 1, the number gets reset.
drop temporary table if exists pictures;
create temporary table pictures
select
a.entity_id,
a.value as original_file_name,
b.sku as sku,
if(isnull(d.attribute_id), 0, 1) as is_default,
substring_index(a.value, "." , -1) as file_extension
from catalog_product_entity_media_gallery a
join catalog_product_entity b on b.entity_id = a.entity_id
left join catalog_product_entity_varchar d on d.attribute_id = 85 and d.value = a.value
order by sku, is_default desc;
set #number = 0;
select
original_file_name,
sku,
is_default,
case
when
is_default = 0
then
#number := #number + 1
else #number := 0
end as number,
concat(sku, '_', if(is_default, 'default_1', concat('slideshow_', #number)), '.', file_extension) as new_file_name
from pictures;

mysql join different fields

i basically have two tables:
id pk_id status
1 2162125 open
2 2162125 fixed
3 2162125 released
4 2162125 closed
and
id pk_id type date
1 2162125 date_close 2018-11-09 18:15:17.212
2 2162125 date_fix 2018-11-09 18:14:37.139
3 2162125 date_confirm 2018-11-09 18:14:11.746
the first table has the status,and the second table has the datetime of when the status changed. I need to find a way to join the two tables,so that fixed is related to fix_date value, closed to date_close value etc.
Sadly im pretty limited,i cant create new tables or change the type values,or i would have just named them the same and be done with it.
edit, this is the expected output:
id pk_id status type date
1 2162125 open null null
2 2162125 fixed date_fix 2018-11-09 18:14:37.139
3 2162125 released null null
4 2162125 closed date_close 2018-11-09 18:15:17.212
You can try using a left join
select a.id,a.pk_id,a.status,b.type,b.date
from table1 a left join table2
on a.status like concat(concat('%',replace(b.type,'date_','')),'%')
You can concat 'date_' with the status and concat the type with '%' and use it as a join clause :
Schema (MySQL v5.7)
CREATE TABLE table1 (
`id` INTEGER,
`pk_id` INTEGER,
`status` VARCHAR(8)
);
INSERT INTO table1
(`id`, `pk_id`, `status`)
VALUES
(1, 2162125, 'open'),
(2, 2162125, 'fixed'),
(3, 2162125, 'released'),
(4, 2162125, 'closed');
CREATE TABLE table2 (
`id` INTEGER,
`pk_id` INTEGER,
`type` VARCHAR(12),
`date` VARCHAR(23)
);
INSERT INTO table2
(`id`, `pk_id`, `type`, `date`)
VALUES
(1, 2162125, 'date_close', '2018-11-09 18:15:17.212'),
(2, 2162125, 'date_fix', '2018-11-09 18:14:37.139'),
(3, 2162125, 'date_confirm', '2018-11-09 18:14:11.746');
Query #1
SELECT t1.id,
t1.pk_id,
t1.status,
t2.type,
t2.date
FROM table1 t1
LEFT JOIN table2 t2
ON t1.pk_id = t2.pk_id
AND CONCAT('date_', t1.status) LIKE CONCAT(t2.type, '%')
ORDER BY t1.id;
Output
| id | pk_id | status | type | date |
| --- | ------- | -------- | ---------- | ----------------------- |
| 1 | 2162125 | open | | |
| 2 | 2162125 | fixed | date_fix | 2018-11-09 18:14:37.139 |
| 3 | 2162125 | released | | |
| 4 | 2162125 | closed | date_close | 2018-11-09 18:15:17.212 |
View on DB Fiddle
UNION of SELECTs with specific where clause could do the trick:
FROM status s
JOIN change c ON c.pk_id=s.pk_id
WHERE s.status = "closed" and c.type = "date_close"
UNION
SELECT s.pk_id, s.status, c.type, c.date
FROM status s
JOIN change c ON c.pk_id=s.pk_id
WHERE s.status = "fixed" and c.type = "date_fix"
...