MySQL FIND_IN_SET search result needed in original sequence - mysql

I am using FIND_IN_SET in MySQL as below :
SELECT
color, b.id
FROM cards b
where FIND_IN_SET(b.color, 'gold,gold,bronze,silver,silver') > 0
GROUP BY b.id
ORDER BY RAND()
limit 5;
And I get output as below :
However, I want to get cards in the same sequence as in the parameter string supplied to FIND_IN_SET i.e.
gold, gold, bronze, silver, silver
So the first output row should be of gold color, then another gold, then bronze and so on - in the same sequence without any random search.
I cannot have self join as it's a big table.

You will need to do this using a UNION of 3 queries to find random 'gold', 'silver' and 'bronze' medallists, and then order those results using FIELD:
SELECT color, id
FROM (
(
SELECT color, id
FROM cards
WHERE color = 'gold'
ORDER BY RAND()
LIMIT 2
)
UNION ALL
(
SELECT color, id
FROM cards
WHERE color = 'bronze'
ORDER BY RAND()
LIMIT 1
)
UNION ALL
(
SELECT color, id
FROM cards
WHERE color = 'silver'
ORDER BY RAND()
LIMIT 2
)
) m
ORDER BY FIELD(color, 'gold', 'bronze', 'silver')
Demo on SQLFiddle
Here's a way you might implement this as a combination of PHP/MySQL:
$pack_sql_comma_separated = 'gold,silver,gold,gold,silver,gold';
$colors = explode(',', $pack_sql_comma_separated);
$counts = array_count_values($colors);
$sql = "SELECT color, id FROM (";
$subqueries = [];
foreach ($counts as $color => $count) {
$subqueries[] = "(
SELECT color, id
FROM cards
WHERE color = '$color'
ORDER BY RAND()
LIMIT $count
)";
}
$sql .= implode(' UNION ALL ', $subqueries) . ') m';
// run SQL query
// ...
// assume all query results read into $rows according to medal colour, something like this
$rows = array('gold' => array(array('id' => 20),
array('id' => 5),
array('id' => 4),
array('id' => 27)
),
'silver' => array(array('id' => 19),
array('id' => 11)
)
);
//display in required order
foreach ($colors as $color) {
$card = array_shift($rows[$color]);
echo "$color {$card['id']}\n";
}
Demo on 3v4l.org

Related

Php randomized with an % chance

I need something where I really dont know how to make it.
Here is an example:
user 1 = 3
user 2 = 1
user 3 = 6
What I want with this, is that user 1, has 30% chance, user 2 has 10% and user 3 60%. But those numbers can also be 0.01 instead of 6. What now want, is that it gets randomised but also have the % of chances. I dont really know how to explain it. That it then draws an number with 60% chance for user 3, 10 chance for user 2 and 30% chance for user 1. But, this last can be as long as possible. How to do this? Sorry, I am really bad in explaining.
Thanks!
What you want can be described as a "weighted probability distribution" or to be technical a "discrete distribution" and a "Categorical distribution":
A categorical distribution is a discrete probability distribution that describes the possible results of a random variable that can take on one of K possible elementary events, with the probability of each elementary event separately specified.
-- Wikipedia
Given you have a random variable with uniform distribution in the range 0 to 1 you can build any distribution you want by using the Inverse Method.
Your first step is to normalize the distribution. That means to make sure that the area of below the curve equals one (that is, that your weight do not sum more than 100%). For a discrete distribution, it means to make sure that sum of the weights is equal to one. [Which is the same as taking the values as a vector and calculate the unit vector in the same direction], just take the each value and divide it by the sum of the values.
Therefore, you go from this:
(original)
user 1 = 3
user 2 = 1
user 3 = 6
To this:
sum = 3 + 1 + 6 = 10
(normalized)
user 1 = 3 / 10 = 0.3
user 2 = 1 / 10 = 0.1
user 3 = 6 / 10 = 0.6
Next, get the cumulative distribution. That is, for each value you do not want its (normalized) weight but the weight of it plus all previous ones.
Therefore, you go from this
(normalized)
user 1 = 0.3
user 2 = 0.1
user 3 = 0.6
To this:
(cumulative)
user 1 = 0.3
user 2 = 0.1 + 0.3 = 0.4
user 3 = 0.6 + 0.3 + 0.1 = 1
Finally, you get your random variable with uniform distribution in the range 0 to 1 and check below which value it falls:
$r = (float)rand()/(float)getrandmax();
if ($r <= 0.3) return "user 1"; // user 1 = 0.3
else if ($r <= 0.4) return "user 2"; // user 2 = 0.4
else return "user 3"; // user 3 = 1
Note: The range is inclusive because PHP is weird.
Ok, all in one go, in (ugly) PHP:
$p = ['user 1' => 3, 'user 2' => 1, 'user 3' => 6];
$s = array_sum($p);
$n = array_map(function($i) use ($p, $s){return $i/$s;},$p);
$a = []; $t = 0;
foreach($n as $k => $i) {$t += $i; $a[$k] = $t;}
$r = (float)rand()/(float)getrandmax();
foreach($a as $k => $i) { if ($r <= $i) return $k; }
Try online.
Let us reimplement in MySQL because reasons.
First we need a table with the input, for example:
SELECT 'user 1' AS `id`, 3 AS `chance`
UNION
SELECT 'user 2' AS `id`, 1 AS `chance`
UNION
SELECT 'user 3' AS `id`, 6 AS `chance`
Then we sum the values
SELECT sum(chance) FROM (SELECT 'user 1' AS `id`, 3 AS `chance`
UNION
SELECT 'user 2' AS `id`, 1 AS `chance`
UNION
SELECT 'user 3' AS `id`, 6 AS `chance`) input
Then we normalize
SELECT id, chance / sum FROM (SELECT 'user 1' AS `id`, 3 AS `chance`
UNION
SELECT 'user 2' AS `id`, 1 AS `chance`
UNION
SELECT 'user 3' AS `id`, 6 AS `chance`) input CROSS JOIN (SELECT sum(chance) as sum FROM (SELECT 'user 1' AS `id`, 3 AS `chance`
UNION
SELECT 'user 2' AS `id`, 1 AS `chance`
UNION
SELECT 'user 3' AS `id`, 6 AS `chance`) input) s
Then we cumulate
SELECT id, chance / sum as sum, (#tmp := #tmp + chance / sum) as csum FROM (SELECT 'user 1' AS `id`, 3 AS `chance`
UNION
SELECT 'user 2' AS `id`, 1 AS `chance`
UNION
SELECT 'user 3' AS `id`, 6 AS `chance`) input CROSS JOIN (SELECT sum(chance) as sum FROM (SELECT 'user 1' AS `id`, 3 AS `chance`
UNION
SELECT 'user 2' AS `id`, 1 AS `chance`
UNION
SELECT 'user 3' AS `id`, 6 AS `chance`) input) s CROSS JOIN (SELECT #tmp := 0) cheat
Then we pick
SELECT id from (
SELECT id, chance / sum as sum, (#tmp := #tmp + chance / sum) as csum FROM (SELECT 'user 1' AS `id`, 3 AS `chance`
UNION
SELECT 'user 2' AS `id`, 1 AS `chance`
UNION
SELECT 'user 3' AS `id`, 6 AS `chance`) input CROSS JOIN (SELECT sum(chance) as sum FROM (SELECT 'user 1' AS `id`, 3 AS `chance`
UNION
SELECT 'user 2' AS `id`, 1 AS `chance`
UNION
SELECT 'user 3' AS `id`, 6 AS `chance`) input) s CROSS JOIN (SELECT #tmp := 0) cheat) a
CROSS JOIN (SELECT RAND() as r) random
WHERE csum > r
LIMIT 1
Try online.
Edit: This answer is based on the title "PHP...." not the tag "mysql"
Each user has n tickets. In your case you have 10 tickets and therefore need a random number between 0 and 9. An unelegant solution could be:
<?php
$tickets = array();
$number_of_tickets = 0;
foreach($users as $user) {
for($i = 0; i < $user->tickets; $i++) {
$tickets[] = $user->id;
$tickets++;
}
}
$lucky_draw = rand(0, $number_of_tickets);
$winner = tickets[$lucky_draw] //ID of the user
print("And the winner is...." . $winner);

Get month wise count from Mysql database even if there are no results

I am creating graph for display month wise count from Mysql database.
Execute this following query:
$cash_query = $this->db->query("select COUNT(*) as count_monthwise, MONTH(FROM_UNIXTIME(dt_added)) as month from `order` where user_id='1' and status != '9' and payment_type = '1' GROUP BY month");
$cash_result = $cash_query->result();
Output:
Array
(
[0] => stdClass Object
(
[count_monthwise] => 1
[month] => 8
)
[1] => stdClass Object
(
[count_monthwise] => 2
[month] => 9
)
)
In above output, there are display "count_monthwise" means count and month "8" means "8th month - August".
But i want to display output with all months, if find count are 0 in any months, then display [count_monthwise] => 0.
I want to display Exact output like:
Array
(
[0] => stdClass Object
(
[count_monthwise] => 1
[month] => 1
)
[1] => stdClass Object
(
[count_monthwise] => 1
[month] => 2
)
.
.
.
.
.
.
.
[10] => stdClass Object
(
[count_monthwise] => 0
[month] => 11
)
[11] => stdClass Object
(
[count_monthwise] => 0
[month] => 12
)
)
I have used using foreach loop something like this, but this is not working.
Loop
foreach($cash_result as $cash => $cash_value){
for($i=0;$i<=11;$i++){
if($i == $cash){
}
}
}
That could be the one way around:
SELECT
COALESCE(yourQuery.count_monthwise,0) AS monthwise_count,
allMonths.month
(SELECT 1 AS month UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9 UNION SELECT 10 UNION SELECT 11 UNION SELECT 12 ) AS allMonths
LEFT JOIN
(
SELECT
COUNT(*) as count_monthwise,
MONTH(FROM_UNIXTIME(dt_added)) as month
from `order`
where user_id='1' and status != '9' and payment_type = '1'
GROUP BY month
) AS yourQuery
ON allMonths.month = yourQuery.month
use below way after getting the result from database don't do complex query just do some code twist
$cash_result = $cash_query->result_array(); // make it to array
$month = range(1,12); // month array
$new_array = array();
$counta = count($month);
for($i=0; $i<$counta ; $i++){
foreach($arr as $key=>$row){
if(($month[$i] == $row['month'])){
$new_array[$i] = $arr[$key];
break;
}else{
$new_array[$i] = array('count_monthwise' => 0,'month' => $month[$i]);
}
}
}
echo '<pre>'; print_r($new_array);

Doctrine 2 DQL CASE WHEN in Count

I have this Query in native MySQL Code
SELECT *
FROM `turn`
LEFT JOIN (
poi
) ON ( turn.id = poi.turn_id )
GROUP BY turn.id
ORDER BY count( case when poi.image = 1 then 1 else null end) DESC;
I need to rebuild this in Doctrine 2 DQL
My attempt so far is this:
SELECT t, COUNT((CASE WHEN Bundle\Entity\Poi p.image = 1 then 1 ELSE NULL END)) AS num
FROM Bundle\Entity\Turn t
JOIN t.pois p
GROUP BY t.id
ORDER BY num DESC
And im getting this error:
An exception has been thrown during the rendering of a template ("[Syntax Error] line 0, col 99: Error: Expected end of string, got '.'") in Bundle:Admin:showTurnsFiltered.html.twig at line 75.
What am i doing wrong?
I found it by myself after hours of trying and searching, it's working with this DQL:
$dql = 'SELECT t,
SUM(CASE WHEN p.image = 1 THEN 1 ELSE 0 END) AS numImage
FROM Bundle\Entity\Turn t
JOIN t.pois p
GROUP BY t.id
ORDER BY numImage DESC';
Important that you need to use SUM instead of COUNT
You need to use ResultSetMappingBuilder. It would look something like :
public function getTurn()
{
$rsm = new ResultSetMappingBuilder($this->_em);
$rsm->addRootEntityFromClassMetadata('Foo\BarBundle\Entity\Turn', 't');
$rsm->addJoinedEntityFromClassMetadata('Foo\BarBundle\Entity\Poi', 'p', 't', 'poi', array('id' => 'poi_id'));
$rsm->addScalarResult('ImageCount', 'ImageCount');
$sql = 'SELECT t.id, t.foo, t.bar,
SUM(CASE WHEN p.image = 1 then 1 else null end) ImageCount,
FROM Turn t
INNER JOIN poi p ON t.id = p.turn_id
ORDER BY ImageCount DESC';
$query = $this->_em->createNativeQuery($sql, $rsm);
return $query->getScalarResult();
}
note: you might need to change $query->getScalarResult()to $query->getResult().

Columns to Rows queries

I have a table that look like this:
Company Year Revenue Cost Profit
ABC 1 10 6 4
ABC 2 12 7 5
ABC 3 14 8 6
XYZ 1 25 18 7
XYZ 2 27 19 8
XYZ 3 29 20 9
I want it look like this:
Company Item 1 2 3
ABC Revenue 10 12 14
ABC Cost 6 7 8
ABC Profit 4 5 6
XYZ Revenue 25 27 29
XYZ Cost 18 19 20
XYZ Profit 7 8 9
A crosstab query only allows one value. I can do it using separate crosstab queries for Revenue, Cost And Profit and the combine using the Union function but there must be an easier way.
Any help would be really appreciated.
Max
Variation for you to try.
SELECT Company,
item,
SUBSTRING_INDEX(SUBSTRING_INDEX(item_details, ',', 1), ',', -1) AS `1`,
SUBSTRING_INDEX(SUBSTRING_INDEX(item_details, ',', 2), ',', -1) AS `2`,
SUBSTRING_INDEX(SUBSTRING_INDEX(item_details, ',', 3), ',', -1) AS `3`
FROM
(
SELECT Company, 'Revenue' AS item, GROUP_CONCAT(Revenue ORDER BY `Year`) AS item_details
FROM SomeTable
GROUP BY Company
UNION
SELECT Company, 'Cost' AS item, GROUP_CONCAT(Cost ORDER BY `Year`)
FROM SomeTable
GROUP BY Company
UNION
SELECT Company, 'Profit' AS item, GROUP_CONCAT(Profit ORDER BY `Year`)
FROM SomeTable
GROUP BY Company
) Sub1
ORDER BY Company, FIELD(Item, 'Revenue', 'Cost', 'Profit')
SQL fiddle for you:-
http://www.sqlfiddle.com/#!2/995a0/6
For reasons of scalability (and flexibility), problems like this are best left to the application level code (e.g. a simple PHP loop on a well-ordered result set) but, just for fun...
SELECT company
, item
, MAX(CASE WHEN year = 1 THEN value END) y1
, MAX(CASE WHEN year = 2 THEN value END) y2
, MAX(CASE WHEN year = 3 THEN value END) y3
FROM
( SELECT company, year, 'revenue' item, revenue value FROM my_table
UNION
SELECT company, year, 'cost',cost FROM my_table
UNION
SELECT company, year, 'profit',profit FROM my_table
) x
GROUP
BY company
, item
ORDER
BY company
, FIELD(item,'Revenue','Cost','Profit');
Try this:
SELECT Company, Item, Col1 AS 1, Col2 AS 2, Col3 AS 3
FROM (SELECT a.Company, 'Revenue' AS Item, MAX(IF(a.Year = 1, a.Revenue, 0)) AS Col1,
MAX(IF(a.Year = 2, a.Revenue, 0)) AS Col2, MAX(IF(a.Year = 3, a.Revenue, 0)) AS Col3
FROM tableA a
GROUP BY a.Company
UNION
SELECT a.Company, 'Cost' AS Item, MAX(IF(a.Year = 1, a.Cost, 0)) AS Col1,
MAX(IF(a.Year = 2, a.Cost, 0)) AS Col2, MAX(IF(a.Year = 3, a.Cost, 0)) AS Col3
FROM tableA a
GROUP BY a.Company
UNION
SELECT a.Company, 'Profit' AS Item, MAX(IF(a.Year = 1, a.Profit, 0)) AS Col1,
MAX(IF(a.Year = 2, a.Profit, 0)) AS Col2, MAX(IF(a.Year = 3, a.Profit, 0)) AS Col3
FROM tableA a
GROUP BY a.Company
) AS A
ORDER BY Company, FIELD(Item, 'Revenue', 'Cost', 'Profit')
A second way of doing it. Not tested (so probably some typos), but doing some SQL to get the data then looping around the details, lobbing them to an object to put out the rows. This will cope with any where a company is missing a data for a year.
Note that you could greatly simplify the SQL if you had a table of years that you are interested in and a table of companies.
<?php
$sql = "SELECT Sub1.Year, Sub2.Company, IFNULL(SomeTable.Revenue, 0) AS aValue, 'Revenue' AS Item
FROM
(
SELECT DISTINCT Year FROM SomeTable
) Sub1
CROSS JOIN
(
SELECT DISTINCT Company FROM SomeTable
) Sub2
LEFT OUTER JOIN SomeTable
ON Sub1.Year = SomeTable.Year
AND Sub2.Company = SomeTable.Company
UNION
SELECT Sub1.Year, Sub2.Company, IFNULL(SomeTable.Cost, 0) AS aValue, 'Cost' AS Item
FROM
(
SELECT DISTINCT Year FROM SomeTable
) Sub1
CROSS JOIN
(
SELECT DISTINCT Company FROM SomeTable
) Sub2
LEFT OUTER JOIN SomeTable
ON Sub1.Year = SomeTable.Year
AND Sub2.Company = SomeTable.Company
UNION
SELECT Sub1.Year, Sub2.Company, IFNULL(SomeTable.Profit, 0) AS aValue, 'Profit' AS Item
FROM
(
SELECT DISTINCT Year FROM SomeTable
) Sub1
CROSS JOIN
(
SELECT DISTINCT Company FROM SomeTable
) Sub2
LEFT OUTER JOIN SomeTable
ON Sub1.Year = SomeTable.Year
AND Sub2.Company = SomeTable.Company
ORDER BY Company, FIELD(Item, 'Revenue', 'Cost', 'Profit'), Year";
$query = $db->query($sql) or die($db->error()) ;
if ($row = $this->db->fetchAssoc())
{
echo "<table>";
$PrevCompany = $row['Company'];
$PrevItem = $row['Item'];
$aLine = new ProcessLine($PrevCompany, $PrevItem, true);
do
{
if ($PrevCompany != $row['Company'] or $PrevItem != $row['Item'])
{
unset($aLine);
$PrevCompany = $row['Company'];
$PrevItem = $row['Item'];
$aLine = new ProcessLine($PrevCompany, $PrevItem);
}
$aLine->Assign_Detail($row['Year'], $row['aValue'])
} while($row = $this->db->fetchAssoc());
unset($aLine);
echo "</table>";
}
class ProcessLine
{
private $Company;
private $Item;
private $row_details = array();
private $FirstRow = false
public __CONSTRUCT($Company, $Item, $FirstRow=false)
{
$this->Company = $Company;
$this->Item = $Item;
}
public __DESTRUCT()
{
if ($this->Firstrow)
{
echo "<tr><th>".$this->Company."</th><th>".$this->Item."</th>";
foreach($row_details AS $row_year=>$row_value)
{
echo "<th>$row_year</th>";
}
echo "</tr>";
}
echo "<tr><td>".$this->Company."</td><td>".$this->Item."</td><td>".implode("</td><td>", $row_details)."</td></tr>";
}
public Assign_Detail($in_year, $in_value)
{
$row_details[$in_year] = $in_value;
}
}
?>

SQL ORDER BY query

I want to have my table,rcarddet, ordered by "SDNO" (not primary key) in ascending order with the exception of "0". So it should turn out to be like:
1
1
2
.
.
10
0
0
My query now is:
SELECT *
FROM `rcarddet`
WHERE `RDATE` = '2011-05-25'
AND `RCNO` = '1'
AND `PLACE` = 'H'
AND `SDNO` != 0
ORDER BY `rcarddet`.`SDNO` ASC;
The easiest way
SELECT * FROM rcarddet
WHERE RDATE = '2011-05-25' and RCNO = '1'and PLACE = 'H'
ORDER BY CASE
WHEN rcarddet.SDNO = 0 THEN [max_number_for_the_type_of_SDNO]
ELSE rcarddet.SDNO
END ASC
SELECT *
FROM `rcarddet`
WHERE `RDATE` = '2011-05-25'
AND `RCNO` = '1'
AND `PLACE` = 'H'
ORDER BY
`SDNO` = 0,
`SDNO`;