MySql Find multiple rows for each id - php

I have a table with 3 columns
---QID---TEXT---CID---
I would like to find 20 rows(QID and TEXT) for each distinct CID. I have already prepared string $cid so that I can use WHERE IN statement.
SELECT * FROM questions q1
WHERE cid=(SELECT cid
FROM questions q2
WHERE q2.cid IN ($cids)
GROUP BY q2.cid)
ORDER BY q1.qid LIMIT 20
Thank you!

Simple query:
$query = 'SELECT QID, TEXT FROM yourDb.yourTable WHERE CID = '.$cid;
or, if $cid is an array:
$query = 'SELECT QID, TEXT FROM yourDb.yourTable WHERE CID IN('.implode(',',$cid).')';
To get to the results:
$pdo = new PDO('mysql:host=yourDBServer','login','password');
if (!$stmt = $pdo->query($query))
{
die('query failed');
}
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
For more info on what you can do with the PDO object, refer to the manual
A quick fix (but not a good one) might be:
$q = 'SELECT QID, TEXT FROM yourDB.yourTB WHERE CID = '.$cid.' LIMIT 20';
In the case of CID IN(1,2,3), I'm not sure if there's a strait forward way of doing this. All I can think of is using unions. Mayby this page can help you out with that.
A fugly fix might also be to ORDER BY CID ASC, and insted of using fetchAll(), do this:
$query = 'SELECT CID,QID, TEXT FROM yourDb.yourTable WHERE CID IN('.implode(',',$cid).')';
//execute query, same as above: $stmt holds results
$results = array();
while($row = $stmt->fetch(PDO::FETCH_ASSOC))
{
if (!is_array($results[$row['CID']))
{
$results[$row['CID']] = array();
}
if (count($results[$row['CID']]) < 20)
{
$results[$row['CID']][] = $row;
}
}
This way, the $results array, will have a key for each CID that was found, and that key's value will be an array of up to 20 records...

The problem is in using the = operator and passing a set of values instead of single one. Change your query to the following and try again
SELECT * FROM questions q1
WHERE cid
IN $cids
ORDER BY q1.qid LIMIT 20

The following snippet uses the MySQL variable trick to assign a number for each row per CID. To keep the example simple I've limited the amount of returned rows to 2 per CID.
select cid
, qid
, text
from (
select if(#last_cid = cid, #rn := #rn + 1, #rn := 1) as rn
, (#last_cid := cid)
, cid
, qid
, text
from YourTable yt
cross join
(select #rn := 0, #last_cid := -1) r
) as SubQueryAlias
where rn < 3;
Data setup:
create table YourTable (QID int, TEXT varchar(50), CID int);
insert YourTable values
(1, 'hi', 1),
(1, 'hi', 1),
(2, 'hi', 1),
(2, 'hi', 1),
(3, 'hi', 2),
(4, 'hi', 2),
(4, 'hi', 2),
(5, 'hi', 3);
This returns up to two rows per CID:
+------+------+------+
| cid | qid | text |
+------+------+------+
| 1 | 1 | hi |
| 1 | 1 | hi |
| 2 | 3 | hi |
| 2 | 4 | hi |
| 3 | 5 | hi |
+------+------+------+

Related

how to get same value from different rows using php

I have a database and 1 table.table name is : cranetype they have 4 fields (cf_did,cf_firstname, cf_comment,cf_cranetype) .
database values below.
cf_did cf_firstname cf_comment cf_cranetype
1 Alexy tfhgfnjh 2,3
2 Thomas fdghfgh 11,6,3
3 Thomas cgjkjhl 5,6,11,3
4 Thomasxc cgjkjhl 1,6,9,4
5 Thomaseg fdghgh 11,12,3
6 Thomasm fsgdfgbd 11,6,3
7 Thomabs dgtrty 7,9,11
8 Rdgfghfdg bfdhh 1,3,4
9 Gngfdytyt eertret 1,6,3
10 Dvbgfj hfdhhyrtyr 6,3
11 Hnfhghyt bfgfdhg 11,6,3
12 Adgfdyh dfsdtrst 1
13 Rida gfhgfjgh 3,7,68
I have a select box, they have values (1,2,3,4,...).
If I select value 1 from select box.
I want to check database table(cranetype) field cf_cranetype.
E.g :
$sql= mysql_query("SELECT * FROM `cranetype` WHERE `cf_cranetype`='1'");
while($row=mysql_fetch_array($sql))
{
<?Php echo $row['cf_did'];?>
}
I want output like this:
if cf_cranetype =1.
4,8,9,12. (these are cf_did)
My code is:
$cranetype = $_POST['cranetype'];
$words = explode(',', $cranetype);
if (strlen($cranetype) > 0) {
$Where = '';
foreach ($words as $Item) {
$Where = "cf_cranetype LIKE '%$Item%' AND";
}
$Where = substr($Where, 0, -4);
$list_ul = "(SELECT * FROM cf_directory` WHERE $Where)";
$query = mysql_query($list_ul) or die(mysql_error());
}
A solution is using find_in_set and group_concat:
SELECT GROUP_CONCAT(cf_did) as cf_dids
FROM `cranetype`
WHERE FIND_IN_SET('1', `cf_cranetype`) > 0;
Demo:
create table cranetype(cf_did int, cf_firstname varchar(100), cf_comment varchar(100), cf_cranetype varchar(50));
insert into cranetype values
(4, 'Thomasxc', 'cgjkjhl', '1,6,9,4'),
(7, 'Thomabs', 'dgtrty', '7,9,11'),
(8, 'Rdgfghfdg', 'bfdhh', '1,3,4'),
(9, 'Gngfdytyt', 'eertret', '1,6,3'),
(12, 'Adgfdyh', 'dfsdtrst', '1');
SELECT GROUP_CONCAT(cf_did) as cf_dids
FROM `cranetype`
WHERE FIND_IN_SET('1', `cf_cranetype`) > 0;
Output:
mysql> SELECT GROUP_CONCAT(cf_did) as cf_dids
-> FROM `cranetype`
-> WHERE FIND_IN_SET('1', `cf_cranetype`) > 0;
+----------+
| cf_dids |
+----------+
| 4,8,9,12 |
+----------+
1 row in set (0.00 sec)
use Find_in_set() in mysql.
SELECT * FROM cranetype WHERE FIND_IN_SET ('1',cf_cranetype);
You need to use FIND_IN_SET. Please check link for more explanation.
here your query is
SELECT * FROM cranetype WHERE FIND_IN_SET ('1',cf_cranetype);

i nf data table structure from json data

I have a database (mysql) table like
id | party | rights | m_id
---+---------+------------+---------
9 |abc | 3,5,6 | ["12,","15,"6"]
20 |xyz | 5,2 | ["6,","2,"9","12"]
21 |xyz 1 | 5,2 | ["6,","9,"12"]
Now I want to make my table in this way
search result for rights 5 is ["12,","15,"6"] ["6,","2,"12"] ["6,","9,"12"]
12 | abc , xyz,xyz1 |
15 | abc|
6 | abc , xyz,xyz1 |
9 | xyz,xyz1 |
Let's start with what I believe you already have. This is an sscce. If you adjust the mysql credentials it should run on your system, creating only a temporary MySQL table. It uses PDO to access the MySQL server. Which API you actually use is not important (i.e. as long as the other API is mysqli, because the mysql_* functions are depreacted ;-))
<?php
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'localonly', 'localonly', array(
PDO::ATTR_EMULATE_PREPARES=>false,
PDO::MYSQL_ATTR_DIRECT_QUERY=>false,
PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION
));
setup($pdo);
$statement = $pdo->prepare('
SELECT
*
FROM
soFoo
WHERE
FIND_IN_SET(:right, rights)
');
$statement->execute( array(':right'=>5) );
/* in one way or another you have a loop where you fetch the records
having '5' in the `rights` field
*/
foreach( $statement as $row ) {
echo $row['party'], ' ', $row['show_ids'], "\r\n";
}
function setup($pdo) {
$pdo->exec('
CREATE TEMPORARY TABLE soFoo
(`id` int, `party` varchar(9), `exc` varchar(13), `rights` varchar(5), `show_ids` varchar(27))
');
$pdo->exec( <<< eos
INSERT INTO soFoo
(`id`, `party`, `exc`, `rights`, `show_ids`)
VALUES
(9, 'Percept', 'Non-Exclusive', '3,5,6', '["12,","15,"6"]'),
(20, 'Tata Sky', 'Non-Exclusive', '5,4,', '["6,","9,"11"]'),
(21, 'Tata Sky', 'Exclusive', '5,4', '["6,","13","15,"2","4","9"]'),
(22, 'Simbiotic', 'Exclusive', '6,2', '["12,","15,"1","6","7","8"]')
eos
);
}
this prints
Percept ["12,","15,"6"]
Tata Sky ["6,","9,"11"]
Tata Sky ["6,","13","15,"2","4","9"]
and is (as I understand the question) as far as you've already got.
Now let's decode the JSON array and check whether it contains the element 9. If it does add inforamtion from the current row to an array called $parties
$parties = array();
/* in one way or another you have a loop where you fetch the records
having '5' in the `rights` field
*/
foreach( $statement as $row ) {
$ids = json_decode($row['show_ids'], true);
if ( in_array('9', $ids) ) {
$parties[$row['id']] = $row['party'];
}
}
var_export($parties);
prints
array (
20 => 'Tata Sky',
21 => 'Tata Sky',
)
But ... from a relational database point of view this is ....suboptimal.
The FIND_IN_SET clause hinders MySQL from using indices effectively; you're searching (compound) data within a single field. It's amazing what the database server implementations can do to improve performance; but it has limits.
And you're also transfering possibly unnecessary data from the MySQL server to the php instance (those records that have 5 in rights but not 9 in show_ids). If possible, this should be avoided. Networks/Network stacks are fast and can be optimized, RAM is cheap ...but again, there are limits.
So, I suggest you look into Database normalization on the one hand and/or document-oriented databases on the other hand.
I am going to give a example below so you can write in your way,
If you are having table below
id | name
1 | a,b,c
2 | b
and expected output like this
id | name
1 | a
1 | b
1 | c
2 | b
Then follow below solutions
If you can create a numbers table, that contains numbers from 1 to the maximum fields to split, you could use a solution like this:
select
tablename.id,
SUBSTRING_INDEX(SUBSTRING_INDEX(tablename.name, ',', numbers.n), ',', -1) name
from
numbers inner join tablename
on CHAR_LENGTH(tablename.name)
-CHAR_LENGTH(REPLACE(tablename.name, ',', ''))>=numbers.n-1
order by
id, n
Please see fiddle here.
If you cannot create a table, then a solution can be this:
select
tablename.id,
SUBSTRING_INDEX(SUBSTRING_INDEX(tablename.name, ',', numbers.n), ',', -1) name
from
(select 1 n union all
select 2 union all select 3 union all
select 4 union all select 5) numbers INNER JOIN tablename
on CHAR_LENGTH(tablename.name)
-CHAR_LENGTH(REPLACE(tablename.name, ',', ''))>=numbers.n-1
order by
id, n
an example fiddle is here.

Order results by time added, but rank them by vote count

I'm writing a web app where people can add and vote on ideas. When outputting the ideas I want them to be ordered by their total vote count or time added, but always have a rank based on the vote count.
This is what I have now:
function get_ideas($status, $sortby, $count, $page){
$offset = ($page - 1) * $count;
$dbh = db_connect();
if($sortby === 'popular'){
$stmt = $dbh->prepare("
SELECT i.idea_id, i.idea_datetime, i.user_id, i.idea_title, i.idea_text,
i.idea_vote_count, u.user_name, #curRank := #curRank + 1 AS rank
FROM ideas i
JOIN (SELECT #curRank := :rankoffset) AS q
JOIN users u ON i.user_id = u.user_id
WHERE idea_status = :idea_status
ORDER BY idea_vote_count DESC
LIMIT :count
OFFSET :offset;");
} else {
$stmt = $dbh->prepare("HOW DO I DO THIS???");
}
$stmt->bindParam(':idea_status', $status, PDO::PARAM_STR);
$stmt->bindParam(':rankoffset', $offset, PDO::PARAM_INT);
$stmt->bindParam(':count', $count, PDO::PARAM_INT);
$stmt->bindParam(':offset', $offset, PDO::PARAM_INT);
$stmt->execute();
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
$stmt = NULL;
$dbh = NULL;
return $result;
}
The code in the "if" block works as intended - it returns an array ordered by "idea_vote_count" with correct ranks.
However, I have no idea what the second statement should be. Ordering by time added would be achieved easily enough by just changing "idea_vote_count" in the "ORDER BY" clause to "idea_id". How do I get the ranks, though? I couldn't think of nor find a solution which didn't involve storing the ranks in the table itself.
Hopefully I've explained my problem clearly, but just in case:
How do I get a table like this:
idea_id | idea_vote_count
1 | 20
2 | 40
3 | 30
4 | 5
To produce output like this:
rank | idea_id | idea_vote_count
4 | 4 | 5
2 | 3 | 30
1 | 2 | 40
3 | 1 | 20
Also, I'm kind of new to PHP and MySQL, so if you spot any other problems, please, point them out.
I look forward to your advice. Thanks :)
EDIT: For Strawberry:
My ideas table:
CREATE TABLE `ideas`(
`idea_id` int NOT NULL AUTO_INCREMENT,
`idea_datetime` datetime NOT NULL,
`user_id` int NOT NULL,
`idea_title` varchar(48) NOT NULL,
`idea_text` text NOT NULL,
`idea_vote_count` int NOT NULL DEFAULT 0,
`idea_status` varchar(16) NOT NULL DEFAULT 'active',
PRIMARY KEY(`idea_id`),
FOREIGN KEY(`user_id`) REFERENCES users(`user_id`))
ENGINE=INNODB;
The sample ideas have been generated by following script. I have then manually changed the idea_vote_count of row 100 to 5.
$i = 1;
set_time_limit(150);
while (i<101) {
$datetime = date('Y-m-d h:m:s');
$dbh->exec(INSERT INTO `ideas` (`idea_datetime`, `user_id`, `idea_title`, `idea_text`, `idea_vote_count`, `idea_status`)
VALUES ('{$datetime}', $i, 'Title{$i}', 'Text{$i}', $i, 'active');
$i++
sleep(1);
}
This is what I ended up with after incorporating Strawberry's SQL into my function:
function get_ideas($status, $sortby, $count, $page){
$offset = ($page - 1) * $count;
$dbh = db_connect();
if($sortby === 'popular'){
$stmt = $dbh->prepare("
SELECT i.idea_id, i.idea_datetime, i.user_id, i.idea_title, i.idea_text, i.idea_vote_count, u.user_name, #curRank := #curRank + 1 AS rank
FROM ideas i
JOIN (SELECT #curRank := :rankoffset) AS q
JOIN users u
ON i.user_id = u.user_id
WHERE idea_status = :idea_status
ORDER BY idea_vote_count DESC
LIMIT :count
OFFSET :offset;");
} else {
$stmt = $dbh->prepare("
SELECT n.*
FROM (
SELECT i.idea_id, i.idea_datetime, i.user_id, i.idea_title, i.idea_text, i.idea_vote_count, u.user_name, #curRank := #curRank + 1 AS rank
FROM ideas i
JOIN (SELECT #curRank := :rankoffset) AS q
JOIN users u
ON i.user_id = u.user_id
WHERE idea_status = :idea_status
ORDER BY idea_vote_count DESC
LIMIT :count
OFFSET :offset) n
ORDER BY idea_id DESC;");
}
$stmt->bindParam(':idea_status', $status, PDO::PARAM_STR);
$stmt->bindParam(':rankoffset', $offset, PDO::PARAM_INT);
$stmt->bindParam(':count', $count, PDO::PARAM_INT);
$stmt->bindParam(':offset', $offset, PDO::PARAM_INT);
$stmt->execute();
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
$stmt = NULL;
$dbh = NULL;
return $result;
}
As you can see, the function takes $count and $page as arguments (usually these have value of $_REQUEST['count/page']) and calculates the offset and limit based on them. This is very important, because I don't want to show all the ideas to users at the same time, I want to split them into several pages. However, this messes with the select/ranking SQL in the following way:
When $page = 1 and $count = 100 you get LIMIT 100 OFFSET 0 and the script works as intended - it shows the most recent row (row 100) as the first one ranked 96 (only rows 1, 2, 3, 4 have lower vote count), followed by the other recent rows ranked 1, 2, 3 and so on.
However, when $page = 1 and $count = 10 you get LIMIT 10 OFFSET 0 and the script outputs row 99 first, because it's the highest rated one, but not the most recent. Row 100 becomes the first result in the result set when $page = 10 (the lowest rated and OLDEST rows, despite the fact that row 100 is the most recent).
I could technically select the entire table and then handle the pagination in PHP, but I fear what the performance impact would be.
EDIT2: I have moved OFFSET and LIMIT into the outer SELECT and now everything works as it's supposed to. The side effect of this is, that the inner SELECT selects the entire table, but hopefully it won't grow too big for the server to handle. This is the solution I'm sticking with for the time being. It's based on Strawberry's SQL, so I'll mark that as the answer. Thanks, Strawberry :)
Here's one idea, using purely MySQL, but it's probably better just to return the data set with the ranks and then do any further ordering in the application code...
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(idea_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,idea_vote_count INT NOT NULL
);
INSERT INTO my_table VALUES
(1 ,20),
(2 ,40),
(3 ,30),
(4 ,5);
SELECT n.*
FROM
( SELECT x.*
, #i:=#i+1 rank
FROM my_table x
, (SELECT #i:=0) vars
ORDER
BY idea_vote_count DESC
) n
ORDER
BY idea_id DESC;
+---------+-----------------+------+
| idea_id | idea_vote_count | rank |
+---------+-----------------+------+
| 4 | 5 | 4 |
| 3 | 30 | 2 |
| 2 | 40 | 1 |
| 1 | 20 | 3 |
+---------+-----------------+------+

mysql select Where (subquery) IN

I have a mysql query of type
select some_value FROM table WHERE (subquery) IN ( values )
which seems to be extremly slow!
I have a mysql table with orders and a second one with the corresponding processing states of them. I want now to show all orders having their last status code = 1 .
table order (id = primary key)
id | value
---+-------
1 + 10
2 + 12
3 + 14
table state (id = primary key)
id | orderid | code
---+---------+-------
1 + 1 + 1
2 + 2 + 1
3 + 2 + 2
4 + 1 + 3
5 + 3 + 1
My query is:
select order.id FROM order WHERE
( select state.code FROM state WHERE state.orderid = order.id ORDER BY state.id DESC LIMIT 0,1 ) IN ( '1' )
It takes roughly 15 seconds to process this for a single order. How to modify the mysql statement in order to speed the query procession time up?
Update
Try this one:
select s1.orderid
from state s1
left outer join state s2 on s1.orderid = s2.orderid
and s2.id > s1.id
where s1.code = 1
and s2.id is null
You may need an index on state.orderid.
SQL Fiddle Example
CREATE TABLE state
(`id` int, `orderid` int, `code` int)
;
INSERT INTO state
(`id`, `orderid`, `code`)
VALUES
(1, 1, 1),
(2, 2, 1),
(3, 2, 2),
(4, 1, 3),
(5, 3, 1)
;
Query:
select s1.orderid
from state s1
left outer join state s2 on s1.orderid = s2.orderid
and s2.id > s1.id
where s1.code = 1
and s2.id is null
Results:
| ORDERID |
|---------|
| 3 |
in this situation I think you could simply forget of order, as all information stays in state.
SELECT x.id, x.orderid
FROM state AS x
WHERE x.code =1
AND x.id = (
SELECT max( id )
FROM a
WHERE orderid = x.orderid )
Maybe would be possible to change your CRUD by putting last state directly in order table too, this would be the better

MySQL greatest value in row?

I'm using MySQL with PHP. This is like my table: (I'm using 3 values, but there are more)
id | 1 | 2 | 3
---+---+---+----
1 | 3 |12 |-29
2 | 5 |8 |8
3 | 99|7 |NULL
I need to get the greatest value's column name in a certain row. It should get:
id | maxcol
---+-------
1 | 2
2 | 2
3 | 1
Are there any queries that will do this? I've been trying, but I can't get it to work right.
Are you looking for something like the GREATEST function? For example:
SELECT id, GREATEST(col1, col2, col3)
FROM tbl
WHERE ...
Combine it with a CASE statement to get column names:
SELECT id, CASE GREATEST(COALESCE(`1`, -2147483646), COALESCE(`2`, -2147483646), COALESCE(`3`, -2147483646))
WHEN `1` THEN 1
WHEN `2` THEN 2
WHEN `3` THEN 3
ELSE 0
END AS maxcol
FROM tbl
WHERE ...
It's not pretty. You'd do better to follow Bill Karwin's suggestion and normalize, or simply take care of this in PHP.
function findcol($cmp, $arr, $cols=Null) {
if (is_null($cols)) {
$cols = array_keys($arr);
}
$name = array_shift($cols);
foreach ($cols as $col) {
if (call_user_func($cmp, $arr[$name], $arr[$col])) {
$name = $col;
}
}
return $name;
}
function maxcol($arr, $cols=Null) {
return findcol(create_function('$a, $b', 'return $a < $b;'), $arr, $cols);
}
This is a great example of the way normalization helps make query design easier. In First Normal Form, you would create another table so all the values would be in one column, on separate rows.
Since you have used repeating groups to store your values across three columns, you can find the column with the greatest value this way:
SELECT id, IF(col1>col2 AND col1>col3, 'col1', IF(col2>col3, 'col2', 'col3'))
AS column_with_greatest_value
FROM mytable;
The short answer is that there is no simple means to do this via a query. You would need to transpose your data and then determine the largest value that way. So something like:
Select Id, ColumnName, Value
From (
Select '1' As ColumnName, Id, [1] As Value
From Table
Union All
Select '2', Id, [2]
From Table
Union All
Select '3', Id, [3]
From Table
) As Z
Where Exists(
Select 1
From (
Select '1' As ColumnName, Id, [1] As Value
From Table
Union All
Select '2', Id, [2]
From Table
Union All
Select '3', Id, [3]
From Table
) As Z2
Where Z2.Id = Z.Id
Group By Z2.Id
Having Max(Z2.Value) = Z.Value
)
Order By Id
This solution depends on a fixed set of columns where you basically name the columns in the UNION ALL queries. In addition, if you have two columns with identical values for the same Id, you will get duplicate rows.
This query will return the max value regardless of NULLs
SELECT MAX(value)
FROM
(SELECT 1 column_no, col1 value
FROM anotherunamedtable
UNION ALL
SELECT 2, col2
FROM anotherunamedtable
UNION ALL
SELECT 3, col3
FROM anotherunamedtable) t
If you really need the column number then
SELECT id,
(SELECT column_no
FROM
(SELECT 1 column_no, col1 value
FROM anotherunamedtable
WHERE id = t.id
UNION ALL
SELECT 2, col2
FROM anotherunamedtable
WHERE id = t.id
UNION ALL
SELECT 3, col3
FROM anotherunamedtable
WHERE id = t.id) s
ORDER BY max_value DESC
LIMIT 1)) as column_no
FROM anotherunamedtable t
But I think that the last query might perform exceptionally horrible.
(Queries are untested)
In the php side, you could do something like this:
foreach ($rows as $key => $row) {
$bestCol = $best = -99999;
foreach ($row as $col => $value) {
if ($col == 'id') continue; // skip ID column
if ($value > $best) {
$bestcol = $col;
$best = $value;
}
}
$rows[$key]['best'] = $bestCol;
}
Or something similar...
Forests and trees, here's a trivial and fastest solution (providing I didn't fumble); the expression simply looks for the largest column in the row
SELECT id,
CASE COALESCE(col1, -2147483648) >= COALESCE(col2, -2147483648)
WHEN
CASE COALESCE(col2, -2147483648) >= COALESCE(col3, -2147483648)
WHEN true THEN 1
ELSE
CASE COALESCE(col1, -2147483648) >= COALESCE(col3, -2147483648)
WHEN true THEN 1
ELSE 3
END
END
ELSE
CASE COALESCE(col2, -2147483648) >= COALESCE(col3, -2147483648)
WHEN true 2
ELSE 3
END
END
FROM table t
a version with IF() would maybe be more readable, but the above should perform a bit better
To deal with NULLS an INT value with minimum of -2147483648 was assumed, the expression could be rewritten to deal explicitly with nulls but would have to branch into 8 different cases and is left as an exercise for the OP.

Categories