Get Rows contains Pattern MYSQL - mysql

I have very simple table like below
CREATE TABLE `tbl_data` (
`id` int(11) NOT NULL,
`won` tinyint(4) NOT NULL,
`time` datetime NOT NULL DEFAULT current_timestamp()
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
--
-- Dumping data for table `tbl_data`
--
INSERT INTO `tbl_data` (`id`, `won`, `time`) VALUES
(1, 1, '2022-10-18 05:21:37'),
(2, 2, '2022-10-18 05:21:37'),
(5, 0, '2022-10-18 05:22:02'),
(6, 2, '2022-10-18 05:22:02'),
(7, 2, '2022-10-18 05:22:18'),
(8, 1, '2022-10-18 05:22:18');
--
-- Indexes for dumped tables
--
--
-- Indexes for table `tbl_data`
--
ALTER TABLE `tbl_data`
ADD PRIMARY KEY (`id`);
--
-- AUTO_INCREMENT for dumped tables
--
--
-- AUTO_INCREMENT for table `tbl_data`
--
ALTER TABLE `tbl_data`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=9;
COMMIT;
What I am trying to achieve is select all rows which contains pattern of won like 0,2,2
so it will select id called 5,6,7 in above example.
if my pattern is like 1,2
it should select ids called 1 and 2 like this.
Database Fiddle is here
https://www.db-fiddle.com/#&togetherjs=RDJuie555L
I am finding solution from last hour but not able to achieve the goal. Let me know if any expert here can help me for same.
Thanks!

WITH cte AS (
SELECT id id0,
LEAD(id) OVER w id1,
LEAD(id,2) OVER w id2,
CONCAT_WS(',',
won,
LEAD(won) OVER w,
LEAD(won,2) OVER w) won_list
FROM tbl_data
WINDOW w AS (ORDER BY id)
)
SELECT tbl_data.*
FROM tbl_data
JOIN cte ON tbl_data.id IN (cte.id0, cte.id1, cte.id2)
WHERE cte.won_list = '0,2,2'
id
won
time
5
0
2022-10-18 05:22:02
6
2
2022-10-18 05:22:02
7
2
2022-10-18 05:22:18
fiddle
PS. If there exists more than one copy of needed pattern then the rows will mix. Add ORDER BY id clause to the outer query for to sort output rows and any of cte.idN (N=0..2) to its output list for to see separate pattern groups.

Related

Is it possible to COUNT if one value occured more than the other value in a column using SQL

I have this table called task_status which has the following structure:
CREATE TABLE `task_status` (
`task_status_id` int(11) NOT NULL,
`status_id` int(11) NOT NULL,
`task_id` int(11) NOT NULL,
`date_recorded` varchar(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `task_status`
ADD PRIMARY KEY (`task_status_id`);
ALTER TABLE `task_status`
MODIFY `task_status_id` int(11) NOT NULL AUTO_INCREMENT;
COMMIT;
INSERT INTO `task_status` (`task_status_id`, `status_id`, `task_id`, `date_recorded`) VALUES
(1, 1, 16, 'Wednesday 6th of January 2021 09:20:35 AM'),
(2, 2, 17, 'Wednesday 6th of January 2021 09:20:35 AM'),
(3, 3, 18, 'Wednesday 6th of January 2021 09:20:36 AM');
and a status_list table that has the possible statuses available
CREATE TABLE `status` (
`statuses_id` int(11) NOT NULL,
`status` varchar(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `status`
ADD PRIMARY KEY (`statuses_id`);
ALTER TABLE `status`
MODIFY `statuses_id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4;
COMMIT;
INSERT INTO `status` (`statuses_id`, `status`) VALUES
(1, 'Yes'),
(2, 'Inprogress'),
(3, 'No');
Now what I want to do is check which number occurred more inside the status_id column 1 occurred more, 2 occurred more or 3 occurred more? using SQL.
Is it possible to do and if so how to?
You can try OVER and PARTITION BY clauses, you simply specify the column you want to partition your aggregated results by.
Example code
select status_id,count(*) over (partition by status_id) as Count_1 from task_status
You can count the column first then filter with max
there is a lot of different way to do this but i prefer using cte.
Here is a example :
with cte as(
select status_id,count(*) cnt from task_status
group by status_id
)
select * from cte
where cnt = (select max(cnt) from cte)
also here is db<>fiddle for better examine.
I modify some data to show the much more understandable output. But idea is same.
also I don't really think status table have any work doing here, but remind me if I misunderstand what you mean.
If you want exactly one status that occurs more often than the others, then I would recommend group by with order by and limit:
select status_id, count(*) as cnt
from task_status
group by status_id
order by cnt desc
limit 1;
This always returns one row, so if there are ties for the most common, then you only get one of the ties.

mysql - grouping results taking the first in an ordered subquery

In mysql, I'm having trouble pulling a single row for each foreign_id based on the largest value. Strangely, different versions of mysql works (listed below)
id foreign_id value
---------------------
1 1 1000
2 1 2000
3 2 2000
4 2 1000
5 3 2000
I try to pull ids 2,3,5 not 1,3,5
CREATE TABLE `docs` (
`id` int(11) NOT NULL,
`foreign_id` int(6) DEFAULT NULL,
`value` int(8) UNSIGNED NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
ALTER TABLE `docs`
ADD PRIMARY KEY (`id`),
ADD KEY `foreign_id_index` (`foreign_id`);
INSERT INTO `docs` (`id`, `foreign_id`, `value`) VALUES
(1, 1, 1000), (2, 1, 2000), (3, 2, 2000), (4, 2, 1000), (5, 3, 2000)
select
docs.id, docs.foreign_id, docs.value
FROM docs
INNER JOIN
(select id, max(value) from docs group by foreign_id) sub
ON sub.id = docs.id
# expected results are ids (2,3,5), not (1,3,5)
It's simpler than it looks
SELECT D.id, D.foreign_id, max_vals.max_val as value
FROM docs D
JOIN
(SELECT foreign_id, MAX(value) as max_val
FROM docs
GROUP BY foreign_id) max_vals
ON D.foreign_id=max_vals.foreign_id and D.value=max_vals.max_val
In this case, you need JOIN and not INNER JOIN
There is a difficult case where there are 2 or more foreign_ids with the same MAX value...

Joining table with min(amount) does not work

I have 3 tables, but data is only fetch from 2 tables.
I'm trying to get the lowest bids for selected items and display user name with the lowest bid.
Currently query works until when we display user name, it shows wrong user name, which does not match the bid.
Below is working example of structure and query.
SQL Fiddle
MySQL 5.6 Schema Setup:
CREATE TABLE `bid` (
`id` int(11) NOT NULL,
`amount` float NOT NULL,
`user_id` int(11) NOT NULL,
`item_id` int(11) NOT NULL
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=latin1;
INSERT INTO `bid` (`id`, `amount`, `user_id`, `item_id`) VALUES
(1, 9, 1, 1),
(2, 5, 2, 1),
(3, 4, 3, 1),
(4, 3, 4, 1),
(5, 4, 2, 2),
(6, 22, 5, 1);
-- --------------------------------------------------------
CREATE TABLE `item` (
`id` int(11) NOT NULL,
`name` varchar(100) NOT NULL
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=latin1;
INSERT INTO `item` (`id`, `name`) VALUES
(1, 'chair'),
(2, 'sofa'),
(3, 'table'),
(4, 'box');
-- --------------------------------------------------------
CREATE TABLE `user` (
`id` int(11) NOT NULL,
`name` varchar(100) NOT NULL
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=latin1;
INSERT INTO `user` (`id`, `name`) VALUES
(1, 'James'),
(2, 'Don'),
(3, 'Hipes'),
(4, 'Sam'),
(5, 'Zakam');
ALTER TABLE `bid`
ADD PRIMARY KEY (`id`);
ALTER TABLE `item`
ADD PRIMARY KEY (`id`);
ALTER TABLE `user`
ADD PRIMARY KEY (`id`);
ALTER TABLE `bid`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=7;
ALTER TABLE `item`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=5;
ALTER TABLE `user`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=5;
Query 1:
SELECT b.id, b.item_id, MIN(b.amount) as amount, b.user_id, p.name
FROM bid b
LEFT JOIN user p ON p.id = b.user_id
WHERE b.item_id in (1, 2)
GROUP BY b.item_id
ORDER BY b.amount, b.item_id
Results:
| id | item_id | amount | user_id | name |
|----|---------|--------|---------|-------|
| 5 | 2 | 4 | 2 | Don |
| 1 | 1 | 3 | 1 | James |
Explanation of query:
Get the selected items (1, 2).
get the lowest bid for thous items - MIN(b.amount)
display user names, who has given the bid - LEFT JOIN user p on p.id = b.user_id (this is not working or I'm doing something wrong)
[Note] I can't use sub-query, I'm doing this in doctrine2 (php code) which limits mysql sub-query
No, you are not necessarily fetching the user_id who has given the bid. You group by item_id, so you get one result row per item. So you are aggregating and for every column you say what value you want to see for that item. E.g.:
MIN(b.amount) - the minimum amount of the item's records
MAX(b.amount) - the maximum amount of the item's records
AVG(b.amount) - the avarage amount of the item's records
b.amount - one of the amounts of the item's records arbitrarily chosen (as there are many amounts and you don't specify which you want to see, the DBMS simply choses one of them)
This said, b.user_id isn't necessarily the user who made the lowest bid, but just one random user of the users who made a bid.
Instead find the minimum bids and join again with your bid table to access the realted records:
select bid.id, bid.item_id, bid.amount, user.id as user_id, user.name
from bid
join
(
select item_id, min(amount) as amount
from bid
group by item_id
) as min_bid on min_bid.item_id = bid.item_id and min_bid.amount = bid.amount
join user on user.id = bid.user_id
order by bid.amount, bid.item_id;
You can solve this using a subquery. I am not 100% sure if this is the most efficient way, but at least it works.
SELECT b1.id, b1.item_id, b1.amount, b1.user_id, p.name
FROM bid b1
LEFT JOIN user p ON p.id = b1.user_id
WHERE b1.id = (
SELECT b2.id
FROM bid b2
WHERE b2.item_id IN (1, 2)
ORDER BY b2.amount LIMIT 1
)
This first selects for the lowest bid with for item 1 or 2 and then uses the id of that bid to find the information you need.
Edit
You are saying that Doctrine does not support subqueries. I have not used Doctrine a lot, but something like this should work:
$subQueryBuilder = $entityManager->createQueryBuilder();
$subQuery = $subQueryBuilder
->select('b2.id')
->from('bid', 'b2')
->where('b2.item_id IN (:items)')
->orderBy('b2.amount')
->setMaxResults(1)
->getDql();
$queryBuilder = $entityManager->createQueryBuilder();
$query = $queryBuilder
->select('b1.id', 'b1.item_id', 'b1.amount', 'b1.user_id', 'p.name')
->from('bid', 'b1')
->leftJoin('user', 'p', 'with', 'p.id = b1.user_id')
->where('b1.id = (' . $subQuery . ')')
->setParameter('items', [1, 2])
->getQuery()->getSingleResult();

Mysql sort alphanumeric data

I have two tables tt1 and tt2 both contains same fields. I want to sort data by no from both table.
Table : tt1
CREATE TABLE IF NOT EXISTS `tt1` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`no` varchar(5) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ;
INSERT INTO `tt1` (`id`, `no`) VALUES
(1, '1A'),
(2, '3A'),
(3, '2A');
Table : tt2
CREATE TABLE IF NOT EXISTS `tt2` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`no` varchar(5) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ;
INSERT INTO `tt2` (`id`, `no`) VALUES
(1, '2A'),
(2, '3A'),
(3, '1A');
Expected output
ID | No
========
1 | 1A
3 | 1A
1 | 2A
3 | 2A
2 | 3A
2 | 3A
I want to ascending order of no field from both table as given output how I can get.
SQLFiddle
In this case using order by after every select is wrong (won't return desired output), because that will order both row-sets separately and union them after that.
What you want here is to order already combined data, therefore you should be using order by only once, after mysql makes union of tables (i.e. combines), because once it combines tables it has all data together but unordered, so when mysql sees order by it orders whole data for you.
Sample:
select * from `tt1`
union all
select * from `tt2`
order by `no`
Note: I've noticed you have wrong syntax in your fiddle. You need to add parentheses:
(select * from tt1 order by no)
union
(select * from tt2 order by no)
Note 2: Thanks #AlmaDo's notice. You should not use * with union queries. Because modification of your tables columns will break query. Use column names that you actually need. E.g. query in sample becomes:
select `id`, `no` from `tt1`
union all
select `id`, `no` from `tt2`
order by `no`

MySQL, using columns in another table for a CASE statement

I have two tables, an Items table that looks like this:
Items
Item Count
-----------------
1 20
2 24
3 49
And a table called ItemsConfig that looks like this:
ItemsConfig
ItemCountLow ItemCountHigh ItemCountStatus
------------------------------------------------
0 20 Low
21 50 Normal
51 100 Surplus
What I would like to do is build a query that will compare item counts in the Items table using the ItemCountLow and ItemCountHigh thresholds in the ItemsConfig table, to derive the ItemCountStatus.
Here are the create statements for each of these tables
Items table
CREATE TABLE IF NOT EXISTS `Items` (
`Item` int(11) NOT NULL,
`Count` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Dumping data for table `Items`
--
INSERT INTO `Items` (`Item`, `Count`) VALUES
(1, 20),
(2, 24),
(3, 49);
ItemsConfig table
CREATE TABLE IF NOT EXISTS `ItemsConfig` (
`ItemCountLow` int(11) NOT NULL,
`ItemCountHigh` int(11) NOT NULL,
`ItemCountStatus` varchar(10) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Dumping data for table `ItemsConfig`
--
INSERT INTO `ItemsConfig` (`ItemCountLow`, `ItemCountHigh`, `ItemCountStatus`) VALUES
(0, 20, 'Low'),
(21, 50, 'Normal'),
(51, 100, 'Surplus');
I'm trying to do something like this, but can't seem to figure it out.
SELECT item, count,
CASE
WHEN count > ItemsConfig.ItemCountLow AND count < ItemsConfig.ItemCountHigh
THEN ItemsConfig.ItemCountStatus
END as 'status'
FROM Items
Could someone please help?
How about:
SELECT i.item, i.count, ifnull(ic.ItemCountStatus,'Unknown') status
FROM items i
LEFT JOIN ItemsConfig ic on (i.count between ic.ItemCountLow AND ic.ItemCountHigh)