MySQL - SELECT query - oldest and youngest date - mysql

I have to build one SQL SELECT query. Table structure is as follows:
[ID] [PHONE] [description] [DATE]
[1] [600898367] [main] [2016-01-23]
[2] [600898367] [] [2016-01-24]
[3] [600898367] [] [2016-01-26]
[4] [600898367] [] [2016-01-28]
[5] [662349093] [main] [2016-01-10]
[6] [662349093] [] [2016-01-21]
[7] [662349093] [] [2016-01-30]
[8] [662349093] [] [2016-01-31]
You have here different records grouped within the same telephone number. The first (the oldest) occurance is marked with [main] flag. There's no two identical numbers with [main] flag.
I want to select each [main] record and additionaly one youngest with the same phone number, so the result should give records 1,4,5,8.
Please help.

Use a WHERE clause to give you the records with the main flag. Use MAX to get the most recent record and JOIN to get the additional columns. Finally, do a UNION ALL to combine the result.
-- Get the main records first
SELECT *
FROM tbl
WHERE description = 'main'
UNION ALL
-- Get the most recent records
SELECT b.*
FROM (
SELECT
t.PHONE,
MAX(DATE) AS MaxDate
FROM tbl t
GROUP BY PHONE
) a
INNER JOIN tbl b -- Do a JOIN to get the additional columns
ON b.PHONE = a.PHONE
AND b.DATE = a.MaxDate

Try this;)
SQL Fiddle
MySQL 5.6 Schema:
CREATE TABLE table1
(`ID` int, `PHONE` int, `description` varchar(4), `DATE` varchar(11))
;
INSERT INTO table1
(`ID`, `PHONE`, `description`, `DATE`)
VALUES
(1, 600898367, 'main', '2016-01-23'),
(2, 600898367, NULL, '2016-01-24'),
(3, 600898367, NULL, '2016-01-26'),
(4, 600898367, NULL, '2016-01-28'),
(5, 662349093, 'main', '2016-01-10'),
(6, 662349093, NULL, '2016-01-21'),
(7, 662349093, NULL, '2016-01-30'),
(8, 662349093, NULL, '2016-01-31')
;
Query 1:
select t.*
from table1 t
inner join (
select `PHONE`, max(`DATE`) as `DATE` from table1 group by `PHONE`
) t1 on t.`PHONE` = t1.`PHONE` and (t.`DATE` = t1.`DATE` or t.`description` = 'main')
order by t.`ID`
Results:
| ID | PHONE | description | DATE |
|----|-----------|-------------|------------|
| 1 | 600898367 | main | 2016-01-23 |
| 4 | 600898367 | (null) | 2016-01-28 |
| 5 | 662349093 | main | 2016-01-10 |
| 8 | 662349093 | (null) | 2016-01-31 |

You can use the following query:
SELECT t1.*, t3.*
FROM mytable AS t1
LEFT JOIN (
SELECT PHONE, MAX(date) AS max_date
FROM mytable
GROUP BY PHONE
) AS t2 ON t1.PHONE = t2.PHONE
LEFT JOIN mytable AS t3 ON t1.PHONE = t3.PHONE AND t2.max_date = t3.`date`
WHERE t1.description = 'main'

With a GROUP BY we'll first group on both PHONE and description, thus getting 4 rows.
Next we'll create a comma separated set using GROUP_CONCAT. It can take an ORDER BY clause, to order the phone numbers by date.
Last we want to get the first item from the set, we can do this with SUBSTRING_INDEX.
SELECT
SUBSTRING_INDEX(GROUP_CONCAT(`PHONE` ORDER BY `DATE`), ',', 1) AS `PHONE`,
description
FROM table1
GROUP BY `PHONE`, `description`;
See Fiddle

Try the following query..
SELECT
A.*
FROM
`old_young` A
INNER JOIN
(SELECT
MIN(`DATE`) AS Res
FROM
old_young
WHERE description = 'main'
GROUP BY PHONE
UNION
ALL
SELECT
MAX(`DATE`) AS Res
FROM
old_young
WHERE description = ''
GROUP BY PHONE) B
ON A.DATE = B.Res ;
Check the FIDDLE

Related

MySQL: Returning rows which are matching the conditions: 1. Dates are ascending and 2. Views are descending

I have a table (tab1) which contains id, trending_date and views coulmns. I have to write a query to return those records whose views are decreasing as the trending_dates are going by. E.g. -
|ID |Trending_Date | Views |
|---|--------------|-------|
|A |2021-01-03 | 10 |
|B |2020-10-30 | 8 |
|A |2021-02-05 | 9 |
|B |2020-11-02 | 11 |
My intended query will return only the records having "A" as ID. Need help on developing this query...
Thanks,
Amitava
If you want only the IDs in the results then aggregate and use GROUP_CONCAT() in the HAVING clause:
SELECT ID
FROM tablename
GROUP BY id
HAVING COUNT(*) = COUNT(DISTINCT Views)
AND GROUP_CONCAT(Views ORDER BY Views DESC) = GROUP_CONCAT(Views ORDER BY Trending_Date)
GROUP_CONCAT(Views ORDER BY Views DESC) sorts the Views of each ID descending and GROUP_CONCAT(Views ORDER BY Trending_Date) sorts the the Views of each ID by Trending_Date ascending.
If the 2 results are the same this means that Views are monotonically decreasing.
The condition COUNT(*) = COUNT(DISTINCT Views) filters out IDs with duplicate Views.
If you want full rows of the table use the above query with the operator IN:
SELECT *
FROM tablename
WHERE id IN (
SELECT ID
FROM tablename
GROUP BY id
HAVING COUNT(*) = COUNT(DISTINCT Views)
AND GROUP_CONCAT(Views ORDER BY Views DESC) = GROUP_CONCAT(Views ORDER BY Trending_Date)
)
See the demo.
With MySQL 8 you can use the window function LAG
I added also a version for MySQL 5.x
CREATE TABLE tab1 (
`ID` VARCHAR(1),
`Trending_Date` VARCHAR(10),
`Views` INTEGER
);
INSERT INTO tab1
(`ID`, `Trending_Date`, `Views`)
VALUES
('A', '2021-01-03', '10'),
('B', '2020-10-30', '8'),
('A', '2021-02-05', '9'),
('B', '2020-11-02', '11');
SELECT ID,`Trending_Date`,`Views`
FROM
(SELECT ID,`Trending_Date`,
`Views` ,
LAG(`Views`, 1) OVER (
PARTITION BY `ID`
ORDER BY `Trending_Date`
) lastview
FROM tab1) t1
WHERE `Views` < lastview
ID | Trending_Date | Views
:- | :------------ | ----:
A | 2021-02-05 | 9
SELECT ID,`Trending_Date`,`Views`
FROM
(SELECT
`Trending_Date`,
IF (#id <> ID,#views := 0,#views := #views) idchange,
IF (#views > `Views`,1,0) smaller,
#views := `Views` as Views,
#id := ID as ID
FROM tab1,(SELECT #views := 0,#id := '') t1
ORDER BY ID,`Trending_Date`) t2
WHERE smaller = 1;
ID | Trending_Date | Views
:- | :------------ | ----:
A | 2021-02-05 | 9
db<>fiddle here
I have added two queries. First one with window function for MySQL 8.0 and second one is using subquery for older version of MySQL.
CREATE TABLE tab1 (
`ID` VARCHAR(1),
`Trending_Date` VARCHAR(10),
`Views` INTEGER
);
INSERT INTO tab1
(`ID`, `Trending_Date`, `Views`)
VALUES
('A', '2021-01-03', '10'),
('B', '2020-10-30', '8'),
('A', '2021-02-05', '9'),
('B', '2020-11-02', '11');
Query#1 (For MySQL 8.0)
select id
from
(
select id,trending_date,views,max(trending_date)over(partition by id)maxtdate,
min(trending_date)over(partition by id)mintdate
from tab1
)t
group by id
having max(case when trending_date=maxtdate then views end) < max(case when trending_date=mintdate then views end)
Output:
id
A
Query#2 (For older version of MySQL)
select id
from
(
select id,trending_date,views,(select max(trending_date)from tab1 t2 where t1.id=t2.id)maxtdate,
(select min(trending_date)from tab1 t2 where t1.id=t2.id)mintdate
from tab1 t1
)t
group by id
having max(case when trending_date=maxtdate then views end) < max(case when trending_date=mintdate then views end)
Output:
id
A
db<fiddle here

SQL - complex SELECT query to UPDATE

SQL noob here.
So i have a table with a lot of products. Some of these products are clothing, meaning i have e.g. six different SKU's for one product due to different sizes. I now want to set the first product variant as the parent for each group. Product IDs and therefore the parent IDs are UUIDs. I managed to write a SELECT query which is taking all the product numbers, finds all groups (by cutting off the last pair of numbers) and assigns all the respective parent (uu)IDs and parent product numbers (for human readable comparison) - it works absolutely fine.
But i have no clue on how to convert this rather complex SELECT into an UPDATE. Anyone having ideas? Version is MySQL 8
Table1 looks like this (with all unused columns cut out):
ID |product_number |parent_id
-------------------------------------------
[UUID]1-1 |123-456-01 | NULL
[UUID]1-2 |123-456-02 | NULL
[UUID]1-3 |123-456-03 | NULL
[UUID]1-4 |123-456-04 | NULL
[UUID]2-1 |987-65-43-21-01 | NULL
[UUID]2-2 |987-65-43-21-02 | NULL
[UUID]2-3 |987-65-43-21-03 | NULL
[UUID]2-4 |987-65-43-21-04 | NULL
My SELECT query:
SELECT ArticleNumber, ArticleGroup, ParentID, t3.id as ID
FROM (
SELECT t2.product_number as ArticleNumber, GroupTable.GroupNr as ArticleGroup, GroupTable.product_number as ParentID
FROM (
SELECT MIN(result.product_number) as product_number, result.GroupNr
FROM (
SELECT product_number,
SUBSTRING_INDEX(product_number, "-", (LENGTH(product_number) - LENGTH(REPLACE(product_number, "-", "")))) as GroupNr
FROM table1.product
) result
WHERE LENGTH(result.GroupNr) > 0
GROUP BY result.GroupNr
ORDER BY GroupNr
) as GroupTable
JOIN table1.product as t2
ON t2.product_number like concat(GroupTable.GroupNr, '%') AND t2.product_number != GroupTable.product_number
ORDER BY GroupTable.GroupNr
) as Energija
JOIN table1.product as t3
ON t3.product_number = Energija.ParentID
I want to update the parent_id so that Table1 looks like this:
ID |product_number |parent_id
-------------------------------------------
[UUID]1-1 |123-456-01 | NULL
[UUID]1-2 |123-456-02 | [UUID]1-2
[UUID]1-3 |123-456-03 | [UUID]1-2
[UUID]1-4 |123-456-04 | [UUID]1-2
[UUID]2-1 |987-65-43-21-01 | NULL
[UUID]2-2 |987-65-43-21-02 | [UUID]2-2
[UUID]2-3 |987-65-43-21-03 | [UUID]2-2
[UUID]2-4 |987-65-43-21-04 | [UUID]2-2
It works in the SELECT query, i just don't know how to make an UPDATE out of this.
Sample table with UUIDs switched for string:
CREATE TABLE table1.product (
id varchar(255),
product_number varchar(255),
parent_id varchar(255));
INSERT INTO Table1.product (
id, product_number, parent_id)
VALUES(
'1-1',
'123-456-01',
NULL),
(
'1-2',
'123-456-02',
NULL),
(
'1-3',
'123-456-03',
NULL),
(
'1-4',
'123-456-04',
NULL),
(
'2-1',
'987-65-43-21-01',
NULL),
(
'2-2',
'987-65-43-21-02',
NULL),
(
'2-3',
'987-65-43-21-03',
NULL),
(
'2-4',
'987-65-43-21-04',
NULL);
You just need to slightly adapt your query and set the parentUuid in the update statement, where the product uuid matches.
In the example code below I adapted your query to get a mapping between the products uuid and the parent uuid. Then I update the table setting the parent_id from the product-table where the products uuid matches the product uuid from the query.
UPDATE table1.product p
SET parent_id = (
SELECT parentUUID
FROM (SELECT t3.id as parentUUID, Energija.productuuid as productUuid
FROM (
SELECT t2.id as productuuid,
t2.product_number as ArticleNumber,
GroupTable.GroupNr as ArticleGroup,
GroupTable.product_number as ParentID
FROM (
SELECT MIN(result.product_number) as product_number, result.GroupNr
FROM (
SELECT product_number,
SUBSTRING_INDEX(product_number, "-",
(LENGTH(product_number) - LENGTH(REPLACE(product_number, "-", "")))) as GroupNr
FROM table1.product
) result
WHERE LENGTH(result.GroupNr) > 0
GROUP BY result.GroupNr
ORDER BY GroupNr
) as GroupTable
JOIN table1.product as t2
ON t2.product_number like concat(GroupTable.GroupNr, '%') AND
t2.product_number != GroupTable.product_number
ORDER BY GroupTable.GroupNr
) as Energija
JOIN table1.product as t3
ON t3.product_number = Energija.ParentID) parentMapping
where parentMapping.productuuid = p.id);

MySQL turn JSON_ARRAY of ids into JSON_ARRAY of values [MySQL 8]

I have a JSON_ARRAY of ids in the form of [1,3,...]. Each value represents an id to a value in another table.
Table: pets
id | value
1 | cat
2 | dog
3 | hamster
Table: pet_owner
id | pets_array
1 | [1, 3]
2 | [2]
3 | []
What I want to get when I query pet_owners is the following result:
Table: pet_owner
id | pets_array
1 | ["cat", "hamster"]
2 | ["dog"]
3 | []
How do I run a sub-select on each array element to get its value?
As JSON goes, it is always a pain to handle
When you need also all that have no pets, you must left Join the owner table
CREATE TABLE pet_owner (
`id` INTEGER,
`pets_array` JSON
);
INSERT INTO pet_owner
(`id`, `pets_array`)
VALUES
('1', '[1, 3]'),
('2', '[2]'),
('3', '[]');
CREATE TABLE pets (
`id` INTEGER,
`value` VARCHAR(7)
);
INSERT INTO pets
(`id`, `value`)
VALUES
('1', 'cat'),
('2', 'dog'),
('3', 'hamster');
SELECT
t1.id,
JSON_ARRAYAGG(
p.`value`
) AS pets_array
FROM(
SELECT *
FROM pet_owner ,
JSON_TABLE(
pet_owner.pets_array , "$[*]"
COLUMNS(IDs int PATH "$" NULL ON ERROR DEFAULT '0' ON EMPTY )
) AS J_LINK ) t1
LEFT JOIN pets p ON p.id =t1.IDs
GROUP BY
t1.id
;
id | pets_array
-: | :-----------------
1 | ["cat", "hamster"]
2 | ["dog"]
db<>fiddle here
A normalized Table would spare you to convert the data into usable columns.
You can join on json_contains(), then re-aggregate:
select po.id, json_arrayagg(p.value) as owners
from pet_owner po
left join pets p on json_contains(po.pets_array, cast(p.id as char))
group by po.id
Note that, unlike most (if not all!) other databases, MySQL does not guarantee the ordering of elements in an array generated by json_arrayagg(): that's just a fact we have to live with as of the current version.

Select All customers with exact name and birthdate

I want to select all the customers that have the same name and birth date on mysql table.
my query right now is close, but it seems to have a flaw:
SELECT
id,
customer.name,
date
FROM
customer
INNER JOIN (
SELECT
name
FROM
customer
GROUP BY
name,date
HAVING
COUNT(id) > 1
) temp ON customer.name = customer.name
ORDER BY
name;
Return a customer if there EXISTS another one with same name and date, but other id:
SELECT
id,
name,
date
FROM
customer c1
where exists (SELECT 1 from customer c2
where c2.name = c1.name
and c2.date = c1.date
and c2.id <> c1.id)
JOIN version:
SELECT
c1.id,
c1.name,
c1.date
FROM
customer c1
JOIN customer c2
ON c2.name = c1.name
and c2.date = c1.date
and c2.id <> c1.id
You can do it using EXISTS expression:
SELECT
id,
customer.name,
date
FROM customer c
WHERE EXISTS (
SELECT *
FROM customer cc
WHERE cc.name=c.name AND cc.date=c.date AND cc.id <> c.id
)
The meaning of this query can be derived by pretending it's plain English: we are looking for all customers c for which there exists another customer cc with the same name and birth date, but different id.
Something like this should do it:
select
c1.*
from
customer c1
join customer c2 on
c1.name = c2.name
and c1.birth_date = c2.birth_date
and c1.id != c2.id
order by name, birth_date, id
;
And here's the full example:
drop table customer;
create table customer (
id int,
name varchar(64),
birth_date date
);
insert into customer values (1, 'Joe', '2001-01-01');
insert into customer values (2, 'Joe', '2001-01-02');
insert into customer values (3, 'Joe', '2001-01-03');
insert into customer values (4, 'Jim', '2001-01-01');
insert into customer values (5, 'Jack', '2001-01-01');
insert into customer values (6, 'George', '2001-01-01');
insert into customer values (7, 'George', '2001-01-02');
insert into customer values (8, 'Jeff', '2001-01-02');
insert into customer values (10, 'Joe', '2001-01-01');
insert into customer values (60, 'George', '2001-01-01');
select * from customer;
select
c1.*
from
customer c1
join customer c2 on
c1.name = c2.name
and c1.birth_date = c2.birth_date
and c1.id != c2.id
order by name, birth_date, id
;
+ ------- + --------- + --------------- +
| id | name | birth_date |
+ ------- + --------- + --------------- +
| 6 | George | 2001-01-01 |
| 60 | George | 2001-01-01 |
| 1 | Joe | 2001-01-01 |
| 10 | Joe | 2001-01-01 |
+ ------- + --------- + --------------- +
4 rows
Assuming a table with only id, name and date, you can expand it after that.
The solution is going to be in using an aliased join or an exists clause.
First some code to setup a temporary table with some values for us to query against.
--Drop Temporary Test Table if it exists
IF OBJECT_ID('tempdb.dbo.#Customers_Test', 'U') IS NOT NULL
DROP TABLE #Customers_Test;
--Create a Temporary Test Table
CREATE TABLE #Customers_Test
(
[id] [int] IDENTITY(1,1) NOT NULL,
[name] [varchar](50) NULL,
[date] [datetime] NULL,
CONSTRAINT [PK_Customers_Test] PRIMARY KEY CLUSTERED
(
[id] ASC
)
WITH
(
PAD_INDEX = OFF,
STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON
) ON [PRIMARY]
) ON [PRIMARY]
--Put some temporary values into the table
INSERT INTO #Customers_Test(name, [date])
VALUES ('Joe', GETUTCDATE()),
('Mark', GETDATE()),
('Dan', '1/1/1990'),
('Dan', '1/1/1990')
Now for the actual selection styles, there are several ways to do this depending on mostly preferences, if this query had become more complex there may have been a significant run time advantage of one over another.
--First Select Version
SELECT #Customers_Test.id,
#Customers_Test.name,
#Customers_Test.[date]
FROM #Customers_Test
WHERE EXISTS (
SELECT 1
FROM #Customers_Test Duped_Customers
WHERE Duped_Customers.name = #Customers_Test.name
AND Duped_Customers.[date] = #Customers_Test.[date]
AND Duped_Customers.id <> #Customers_Test.id
)
Another way:
--Second Select Version
SELECT #Customers_Test.id,
#Customers_Test.name,
#Customers_Test.[date]
FROM #Customers_Test
INNER JOIN #Customers_Test AS Duped_Customers
ON #Customers_Test.name = Duped_Customers.name
AND #Customers_Test.[date] = Duped_Customers.[date]
AND #Customers_Test.id <> Duped_Customers.id
One Final Way:
--Third Select Version ( Similar to your current logic).
SELECT #Customers_Test.id,
#Customers_Test.name,
#Customers_Test.[date]
FROM #Customers_Test
WHERE #Customers_Test.name IN (
SELECT name
FROM #Customers_Test Duped_Customers
GROUP BY name, [date]
HAVING COUNT(name) > 1
)

How to select a row with maximum value for a column in MySQL?

*None of other available answers solved my problem
I have a table t like this
id,cc,count
'1','HN','22'
'1','US','18'
'1','VN','1'
'2','DK','2'
'2','US','256'
'3','SK','1'
'3','US','66310'
'4','UA','2'
'4','US','263'
'6','FR','7'
'6','US','84'
'9','BR','3'
I want to get the rows for ids with maximum count, like below:
id,cc,count
'1','HN','22'
'2','US','256'
'3','US','66310'
'4','US','263'
'6','US','84'
'9','BR','3'
My current code is like this but I am not getting the expected results:
SELECT t.* FROM t
JOIN (
SELECT
t.id,t.cc
,max(t.count) as max_slash24_count
FROM t
group by t.id,t.cc
) highest
ON t.count = highest.max_slash24_count
and t.cc = highest.cc
Can anybody help me out?
Remove CC column from group by. Try this.
SELECT t.* FROM t
JOIN (
SELECT
t.id
,max(t.count) as max_slash24_count
FROM t
group by t.id
) highest
ON t.count = highest.max_slash24_count
and t.id= highest.id
Try this:
create table t (id varchar(10), cc varchar(10), count varchar(10))
insert into t (id,cc,count) values ('1','HN','22');
insert into t (id,cc,count) values ('1','US','18');
insert into t (id,cc,count) values ('1','VN','1');
insert into t (id,cc,count) values ('2','DK','2');
insert into t (id,cc,count) values ('2','US','256');
insert into t (id,cc,count) values ('3','SK','1');
insert into t (id,cc,count) values ('3','US','66310');
insert into t (id,cc,count) values ('4','UA','2');
insert into t (id,cc,count) values ('4','US','263');
insert into t (id,cc,count) values ('6','FR','7');
insert into t (id,cc,count) values ('6','US','84');
insert into t (id,cc,count) values ('9','BR','3');
select *
from t
where exists (
select *
from t as t1
group by t1.id
having t1.id = t.id and max(t1.count) = t.count
)
Result
ID CC COUNT
-------------
1 HN 22
2 US 256
3 US 66310
4 US 263
6 US 84
9 BR 3
Check SQLFiddle
This question was answered a lot of times on SO. The query is as simple as this:
SELECT m.id, m.cc, m.count
FROM t m # "m" from "max"
LEFT JOIN t b # "b" from "bigger"
ON m.id = b.id # match a row in "m" with a row in "b" by `id`
AND m.count < b.count # match only rows from "b" having bigger count
WHERE b.count IS NULL # there is no "bigger" count than "max"
The real issue on your question is about the column types. If count is char (and not int) then the string comparison happens using the dictionary order, not the numeric order.
For example, if the third row reads:
'1','VN','123'
you might expect it to be selected in the output, because 123 is bigger than 22. This does not happen because, as string, '123' is smaller than '22'.
Even tho, this was already answered, using ROW_NUMBER functionality as in SQL Server is quite fun and interesting: please look at this query:
SELECT TT.Id, TT.cc, TT.count
FROM (
SELECT t.cc
, t.count
, #row_number:=CASE WHEN #Id=Id THEN #row_number+1 ELSE 1 END AS row_number
, #Id:=Id AS Id
FROM t, (SELECT #row_number:=0, #Id:='') AS temp
ORDER BY t.Id, t.count DESC
) AS TT
WHERE TT.row_number = 1
ORDER BY TT.Id;
It produces expected output:
| Id | cc | count |
|----|----|-------|
| 1 | HN | 22 |
| 2 | US | 256 |
| 3 | US | 66310 |
| 4 | US | 263 |
| 6 | US | 84 |
| 9 | BR | 3 |
SQLFiddle
I've taken test data from #Andrey Morozov