Ordering by a prototype - php

There are 6 possible keys in a MySQL field. Lets call them types. Through PHP, I have defined an array, that is called $order, and arranges these types in order I want them to appear.
There is a table, articles, which has a field articles.type . Any article can have 0-6 types added to it. Now, what I want to do, is grab all of the articles, and order them from the prototype. What is the best way to do this? Can this be done in MySQL, since I suppose that would be faster? And if not, how can it be done in PHP?
Example:
Table:
id articleId type
1 3 type1
2 3 type2
3 3 type3
4 3 type4
5 4 type5
6 4 type6
7 5 type5
8 7 type1
9 7 type5
Order:
$order=array('type1','type2','type3','type4','type5','type6');
How do I fetch the results ordered by my $order variable?

You'd need to massage that array into a mysql-style if/case statement:
$order_by = "ORDER BY CASE";
$pos = 1;
foreach ($order as $clause) {
$order_by .= " CASE `type`='$clause' THEN " . $pos++;
}
$order_by .= " ELSE " . $pos++;
which would generate something like
ORDER BY CASE
WHEN `type`='type1' THEN 1
WHEN 'type`='type2' THEN 2
...
ELSE n

Can this be done in MySQL, since I suppose that would be faster?
Only if you allow MySQL to use an index
You can create a temp table:
$query = "CREATE TABLE IF NOT EXISTS test (
sort INT NOT NULL AUTO_INCREMENT,
`type` VARCHAR(20) NOT NULL,
PRIMARY KEY (sort),
KEY (`type`, sort)
ENGINE=MEMORY (SELECT 1,'other' <<-- see query below.
UNION SELECT 2,'type1' <<-- build this part using
UNION SELECT 3,'type2' <<-- Marc B's code.
UNION SELECT 4,'type3'
UNION SELECT 5,'type4'
UNION SELECT 6,'type5'
UNION SELECT 7,'type6' ";
Run this query.
Now you can link against this query using a join and use test.sort as your sortkey:
SELECT t1.id, t1.article_id, COALESCE(t.`type`,'other') as sort_type
FROM table1 t1
LEFT JOIN test t ON (t1.`type` = t.`type`)
WHERE ....
ORDER BY t.sort;
This query will be fully indexed and run as fast as possible.

Related

MYSQL Match Entries that Meet Multiple Criteria over Multiple Rows

I think this will be an easy one for those using MYSQL a lot, but I just can't quite get it...mainly through not knowing the right terminology to search for.
Basically I have a table mapping tag ids to photo ids called tag_map. When I perform this query:
SELECT * FROM tag_map WHERE tag_id='1' OR tag_id='5';
I get the following results:
ph_id tag_id
1 1
2 1
5 1
7 5
8 1
9 5
10 5
11 1
12 1
13 1
But what I really want is to only select ph_id that have a tag_id of BOTH '1' and '5'.
So, as you can probably see, I am trying to filter down selections based on multiple tags. I want to end up with:
ph_id tag_id
7 1
7 5
8 1
8 5
11 1
11 5
So ph_id 7, 8 and 11 reference tag_id 1 AND 5.
Hope that makes sense.
Thanks.
Solution
Due to the dynamic nature of my query (user selecting any number of available tags to 'narrow down selection) I went with a PHP solution, as suggested by #Y U NO WORK
Basically I get the tag_id of all selected tags from table 'tags'
Then I selected all photo ids (ph_id) that are mapped to the selected tag_ids from my table tag_map.
Then I reduce this down to ph_ids that occur the same number of times as the number of selected tags:
$numTags = count($arTagId); //$arTagId is an array of the selected tags
// get only photo ids that match both tags
$arPhId = array();
// in arPhId, find ph_ids that have occurances equal to $numTags
$arPhIdCnt = array_count_values($arPhIdAll); //$arPhIdAll is array of all ph_id that match all selected tag_ids
foreach($arPhIdCnt as $pid => $pidQty) {
if($pidQty == $numTags) {
$arPhId[] = $pid;
}
}
So I end up with an array of only the ph_ids that match both tag_ids.
Thanks for everyone's help.
You will have to join the table with itself, the code could be kinda complicated. PHP would be an easier, but not such a performant solution.
You have to join the table with itself based on ph_id, then check that the tab_id col of table1 instance equals 1 and that tab_id of table2 instance equals 5.
SELECT t1.* FROM tag_map t1, tag_map t2
WHERE t1.ph_id = t2.ph_id
AND t1.tag_id='1'
AND t2.tag_id='5';
with inner join if you prefer
SELECT t1.* FROM tag_map t1
INNER JOIN tag_map t2 on t2.ph_id=t1.ph_id
WHERE t1.tag_id='1'
AND t2.tag_id='5';

trying to delete duplicate records in sql but the query is going in an infinite loop

this is my table test
id identifier
--- ---------
1 zz
1 zzz
3 d
5 w
7 v
8 q
9 cc
9 ccc
here I want to remove the duplicate id's and keep the latest id's.
the identifier can have duplicate values it dose not matter but the id's should be unique.
I wrote this query to solve this problem but the problem is that it goes into a infinite loop.
please help me with this as I am not able to see the error.
Thanks
delete test
from test
inner join(
select max(id) as lastId, identifier
from test
where id in (
select id
from test
group by id
having count(*) > 1
)
group by id
)dup on dup.id = test.id
where test.id<dup.id
If you have an index on test(id, identifier), the following should be pretty efficient:
delete from test
where test.identifer < (select maxid
from (select max(identifier) as maxid from test t2 where t2.id = t.id
) a
)
The double nested query is a MySQL trick for referencing the update/delete table in the same query.
Look at How to delete duplicate rows with SQL?
And try this one(works for you want to do), i did with the identifier column, but with the date column as shown in the post is better.
DELETE FROM Test WHERE Identifier NOT IN
(SELECT MAX(Identifier) FROM Test GROUP BY Id);
Now with the DateField:
id identifier DateField
--- --------- ----------
1 zz 2013-02-01
1 zzz 2013-03-02
3 d 2013-03-02
5 w 2013-03-02
7 v 2013-03-02
8 q 2013-03-02
9 cc 2013-01-15
9 ccc 2013-03-02
that is the table, and row (1, zzz) is newer than (1,zz), you can know it by the DateField column, then this query deletes two rows (1, zz) and (9, cc) the oldest for Id's 1 and 9.
DELETE FROM Test WHERE Datefield NOT IN
(SELECT MAX(Datefield) FROM Test GROUP BY Id);
in SQL Server 2008 R2 i didnt get any error.
CREATE TEMPORARY TABLE tmp_tb_name AS
SELECT id,SUBSTRING_INDEX(GROUP_CONCAT(identifier ORDER BY id DESC),',',1)
FROM tb_name GROUP BY id ORDER BY NULL;
TRUNCATE TABLE tb_name;
INSERT INTO tb_name SELECT tmp_tb_name;
DROP TEMPORARY TABLE IF EXISTS tmp_tb_name;
Update
I have found a solution for the issue: This is how I did it to solve the issue
This is the solution which worked for me when there are millions on entities in the table.
Any other SQL query is creating a lot of processes and burdening the server.
$i=0;
while($i<10)
{
$statement="SELECT *
FROM test
WHERE id = :i";
$query=$db->prepare($statement);
$query->bindParam(':i',$i,PDO::PARAM_INT);
$query->execute();
$results=$query->fetchAll(PDO::FETCH_ASSOC);
$c=count($results);
$temp=$results[$c-1];
$statement="DELETE FROM test WHERE id= :i";
$query=$db->prepare($statement);
$query->bindParam(':i',$i,PDO::PARAM_INT);
$query->execute();
$statement="Insert into test values(:id,:identifier)";
$query=$db->prepare($statement);
$query->bindParam(':id',$temp['id'],PDO::PARAM_INT);
$query->bindParam(':identifier',$temp['identifier'],PDO::PARAM_STR);
$query->execute();
$results=$query->fetchAll(PDO::FETCH_ASSOC);
$i++;
}

How do I write this query?

I have the following mysql table:
map_id - module_id - category_id
1 - 3 - 6
2 - 3 - 9
3 - 3 - 11
4 - 4 - 6
5 - 4 - 9
6 - 4 - 12
map_id is the primary key. I have a list of category_id in an array.
My select criteria is:
Both module_id = 3, 4 should be returned if category_id array contains 6,9.
Only module_id = 3 should be returned if category_id array contains 6,9,11
Right now if I run a simple WHERE...IN query then all the rows are selected, which is not what I want.
Edit: Actually I was looking for a more dynamic solution. Probably my question wasn't clear (it is my first one).
say I have these category_id's:
$cat = array(6,9)
If I query
"SELECT module_id FROM maptable WHERE category_id IN (".implode(',', $cat).")"
then I get rows with both module_id 3 and 4.
On the other hand, if the array is
$cat = array(6,9,11)
and I run the same query, I again get same result. But I want only rows with module_id = 3 this time.
SQL isn't really designed for a query like this, but it can be done.
SELECT module_id FROM
(SELECT module_id FROM table WHERE category_id = 6) cat_6
JOIN (SELECT module_id FROM table WHERE category_id = 9) cat_9 USING (module_id)
JOIN (SELECT module_id FROM table WHERE category_id = 11) cat_11 USING (module_id)
Make sure to have an index on category_id, module_id and a second index on module_id, category_id. Ideally you make those unique indexes.
WHERE ((category_id = 6 OR category_id=9) AND module_id=3) OR ((category_id = 6 OR category_id=9 OR category_id = 11) AND (module_id=3 OR module_id=4))
?
There's probably a simpler way of writing this, but I think you get the point :-)
when you use WHERE...IN it means 'any of in(...) value'
you can use AND instead
You could try:
SELECT * FROM `table`
WHERE (`module_id` IN(3,4) AND `category_id` IN(6,9))
OR (`module_id` IN(3) AND `category_id` IN(6,9,11))

Need help with MySQL query & PHP

I have the following 3 tables:
(PK = Primary Key, FK = Foreign Key)
Files Table
File ID (PK) File Name ...
------------ ---------
1 a.jpg ...
2 b.png ...
3 c.jpg ...
. .
. .
. .
Tags Table
Tag ID (PK) Tag Name ...
----------- ----------
1 Melbourne ...
2 April ...
3 2010 ...
. .
. .
. .
Files_Tags Table
File ID (FK) Tag ID (FK)
------------ -----------
1 1
1 5
1 7
2 2
2 4
3 3
. .
. .
. .
In PHP, I want to get a list of all tags along with the number of times the tag appears (i.e. the number of files that have this tag).
Is that possible to do with one MySQL query ?
Try GROUP BY on your tag id. Use a LEFT JOIN to include tags that exist in the tags table but aren't ever used.
SELECT
Tag_Name,
COUNT(Files_Tags.Tag_ID) AS cnt
FROM Tags
LEFT JOIN Files_Tags
ON Tags.Tag_ID = Files_Tags.Tag_ID
GROUP BY Tags.Tag_ID
Result:
Melbourne 1
April 1
2010 1
... ...
You may also want to add an ORDER BY Tag_Name or an ORDER BY COUNT(*) if you want the rows returned in sorted order.
Daniel Vassello also submitted an answer but deleted it. However his answer is quite easy to adapt to meet your new requirements. Here is his solution, modified to use a LEFT JOIN instead of an INNER JOIN:
SELECT t.tag_id,
t.tag_name,
IFNULL(d.tag_count, 0) AS tag_count
FROM tags t
LEFT JOIN
(
SELECT tag_id, COUNT(*) tag_count
FROM files_tags
GROUP BY tag_id
) d ON d.tag_id = t.tag_id;
You shouldn't use too much of GROUP BY, ORDER BY and * JOIN as those query are very heavy and it's not something you should base your code on.
If I was you, I would do multiple simple SELECT query and group the stuff together using PHP algorithms. This way, you're DB won't be hit by very slow query.
So basically, in your specific question I would have more than 1 query.
I would start by doing a
SELECT * FROM "tags_table".
In php, I would created a foreach loop that would count appearance of every tag in your "files_tags" table:
SELECT FILE_ID COUNT(*) FROM TAGS_TABLE WHERE TAG_ID = 'tag_uid'
It's mostly pseudo-code so I wouldn't expect those query to work but you get the general idea.

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