0

Working on a php game so I have a table "country_development_areas" with trophies that should appear in users country pages. Each trophy starts from "value" 0 and up. The bigger the value the bigger the rank. I have attached an image with the table bellow:

enter image description here

There should be 2 lines showing on user country page: - 1st line showing trophies corresponding to country that are in top 5% in the world for the "value" field. - 2nd line showing trophies corresponding to country that are in top 10% in the world for the "value" field.

Rank should be done globally by "value" compared to all countries and shows the line of trophies corresponding to the country. It should also has in the link title the corresponding rank like "Most Efficient Economies: 3,420th".

Bellow is the code I have until now, it shows the trophies but it shows all so I don't have an idea how to make them show by percent ranking.

<?php
    // Get all rows from country_development_areas table
    try {
        $stmt = $db->prepare('SELECT * FROM country_development_areas WHERE country = :idCountry ORDER BY value DESC');
        $stmt->execute(array(':idCountry' => $row_country['id']));
    } catch(PDOException $e) {
        $error[] = $e->getMessage();
    }
?>


<div id="trophycabinet">

                        <?php
                        while($row_country_development_area = $stmt->fetch(PDO::FETCH_ASSOC))  {
                        ?>

                            <a class="top5percent" href="/nation=united_states_of_america/detail=trend?censusid=<?php echo $row_country_development_area['id']; ?>" title="<?php echo $row_country_development_area['title']; ?>: 3,420th">
                                <?php echo $row_country_development_area['icon']; ?>
                            </a>

                        <?php } ?>
                        
</div>

First for top 5% the user should see one line with trophies like in the bellow screenshot. It's what currently the script shows but only showing all trophies for the country, it doesn't take only the top 5% compared to the world since I don't know how to do it.

enter image description here

Secondly should be a second line like the previous that would show trophies line for top 10%.

main
  • 3
  • 7
  • It's not really clear what you want. Your code is wrong (`fetch()` function fetches one row!) so is of no big use. Your explanations, e.g. "_There should..._" and "_Rank should..._", are also a bit confusing. It's not clear if you want a number (a SUM of `value`s) or a list of `name`/`value` pairs near each other, etc. So please make an "edit" with a clearer explanation and show exactly how the output should look like (not php, but the final results, e.g. what the user should see on screen). –  Sep 08 '17 at 20:23
  • Made the edits. Let me know if clear. Thanks. – main Sep 09 '17 at 09:13
  • First question: What do you exactly mean by "top 5%"? Top 5% of the countries or top 5% of the areas? –  Sep 09 '17 at 09:37
  • Top 5% by the area value of the areas. So for example there are 50 countries each with it's own Civil Rights area value. Let's say for country with id 1 see if her Civil Rights area is in top 5% compared to the other Civil Rights countries areas. If it's in top 5% display it on the first line. If it's in top 10% display it on the second line. – main Sep 09 '17 at 09:54
  • So, let's see your table and say the following: The total sum of `value`s for `Civil Rights` for all countries is 102. Then the 95% limit (regarding `value` column) is: 0.95 * 102 = 96.9. So, when a `Civil Rights` area of a country has a `value` >= 96.9, then that area should be displayed in the first line. Right? –  Sep 09 '17 at 10:15

1 Answers1

1

Here is the complete code, including the rank positions. You can add as many top types as you want.

  • Don't use id = 0 in id columns!
  • Put no icon html in tables. Put just the icon names in the icon column, like "hand-peace-o".
  • For proper prepared statements + validations + exception handling see my answer (the EDIT part also, if you want to use a class).
  • I used only the icon name in the icon column.
  • You should use my recommendation from inside the catch blocks. Because an exception is usually an error type after which you would not want to continue any program steps, but just to display the error message on screen and exit.
  • Try to completely avoid using while statements in your codes, in any programming language. Except maybe there, where they are... unavoidable. E.g. if they are good documented, like, for example, on the PHP documentation website for different data access functions, and are really needed, then use them. But with care, because they can result in infinite loops which can completely overload your server.
  • Separate the PHP data access part from the HTML part. If you need data, fetch all of it in PHP arrays and in the upper part of the PHP page. In the HTML part of the PHP page just iterate through these arrays. So, in short, don't mix PHP data access codes with HTML codes. Like: no fetch() inside the HTML part of the PHP page.

Good luck.

PHP code:

<?php
// Get all rows from country_development_areas table
try {
    // Read from HTTP POST.
    $country = 1;

    /*
     * Array containing the top types (top 5%, top 10%, top 20%, etc) as keys and
     * the areas lists for each top as values.
     */
    $tops = array(
        5 => array(),
        10 => array(),
        20 => array(),
        80 => array(),
    );

    // Create a PDO instance as db connection to a MySQL db.
    $db = new PDO(
            'mysql:host=localhost;port=3306;dbname=yourDB'
            , 'yourUser'
            , 'yourPass'
    );

    // Assign the driver options to the db connection.
    $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, FALSE);
    $db->setAttribute(PDO::ATTR_PERSISTENT, TRUE);

    // Iterate through tops and fetch the top areas.
    foreach ($tops as $top => $results) {
        // The sql statement - it will be prepared.
        $sql = 'SELECT 
                    cda.*,
                    (
                        SELECT (COUNT(DISTINCT value) + 1)
                        FROM country_development_areas
                        WHERE 
                            name = cda.name
                            AND value > cda.value
                    ) AS rankPositionInTheWorld,
                    (
                        SELECT (COUNT(DISTINCT value) + 1)
                        FROM country_development_areas
                        WHERE 
                            name = cda.name 
                            AND value > cda.value 
                            AND value >= :topDownLimit1 * (
                                SELECT SUM(IFNULL(value, 0)) 
                                FROM country_development_areas 
                                WHERE name = cda.name
                            )
                    ) AS rankPositionInTop 
                FROM country_development_areas AS cda
                WHERE
                    cda.country = :country AND
                    cda.value >= :topDownLimit2 * (
                        SELECT SUM(IFNULL(value, 0)) 
                        FROM country_development_areas 
                        WHERE name = cda.name
                    )';

        // The input parameters list for the prepared sql statement.
        $bindings = array(
            ':country' => $country,
            ':topDownLimit1' => 1 - $top / 100,
            ':topDownLimit2' => 1 - $top / 100,
        );

        // Prepare and validate the sql statement.
        $stmt = $db->prepare($sql);

        if (!$stmt) {
            throw new UnexpectedValueException('The sql statement could not be prepared!');
        }

        // Bind the input parameters to the prepared statement.
        foreach ($bindings as $key => $value) {
            // Get the name of the input parameter by its key in the bindings array.
            $inputParameterName = is_int($key) ? ($key + 1) : (':' . ltrim($key, ':'));

            // Get the PDO::PARAM_* constant, e.g the data type of the input parameter, by its value.
            if (is_int($value)) {
                $inputParameterDataType = PDO::PARAM_INT;
            } elseif (is_bool($value)) {
                $inputParameterDataType = PDO::PARAM_BOOL;
            } else {
                $inputParameterDataType = PDO::PARAM_STR;
            }

            // Bind and validate the binding of the input parameter.
            $bound = $stmt->bindValue($inputParameterName, $value, $inputParameterDataType);

            if (!$bound) {
                throw new UnexpectedValueException('An input parameter could not be bound!');
            }
        }

        // Execute the prepared statement.
        $executed = $stmt->execute();

        if (!$executed) {
            throw new UnexpectedValueException('The prepared statement could not be executed!');
        }

        /// Fetch and validate the areas list.
        $topAreas = $stmt->fetchAll(PDO::FETCH_ASSOC);

        if ($topAreas === FALSE) {
            throw new UnexpectedValueException('Fetching data failed!');
        }

        $tops[$top] = $topAreas;
    }

    // Close connecion.
    $connection = NULL;
} catch (PDOException $e) {
    $error[] = $e->getMessage();

    // My recommendation:
    // echo $e->getMessage();
    // $logger->log($e);
    // exit();
} catch (Exception $e) {
    $error[] = $e->getMessage();

    // My recommendation:
    // echo $e->getMessage();
    // $logger->log($e);
    // exit();
}
?>
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Trophies test</title>

        <!-- ======================================= -->
        <!-- CSS resources -->
        <!-- ======================================= -->

        <!-- Font-Awesome -->
        <link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" type="text/css" rel="stylesheet" />

        <!-- Bootstrap -->
        <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" type="text/css" rel="stylesheet" />

        <!-- ======================================= -->
        <!-- JS resources -->
        <!-- ======================================= -->

        <!-- jQuery -->
        <script src="https://code.jquery.com/jquery-3.2.1.min.js" type="text/javascript" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script>

        <!-- Bootstrap -->
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" type="text/javascript"></script>

        <style type="text/css">
            body {
                padding: 30px 15px;
            }

            .legend {
                margin-bottom: 30px;
                max-width: 30%;
            }

            .legend-title {
                background-color: #eee;
            }

            .trophy-cabinet-title {
                margin-bottom: 10px;
            }

            .trophy-cabinet {
                margin-bottom: 20px;
            }

            .top-area {
                padding: 10px;
                border: 1px solid #ccc;
                border-radius: 4px;
                display: inline-block;
            }
        </style>
    </head>
    <body>

        <div class="list-group legend">
            <div class="list-group-item legend-title">
                Legend
            </div>
            <div class="list-group-item">
                WP: Rank position in the world
            </div>
            <div class="list-group-item">
                TP: Rank position in the top
            </div>
        </div>

        <?php
        // Just for testing, to see the areas list of all tops on screen.
        // echo '<pre>' . print_r($tops, TRUE) . '</pre>';
        ?>

        <?php
        foreach ($tops as $top => $areas) {
            /*
             * If the list of areas for the current top 
             * is not empty, then display the areas.
             */
            if ($areas) {
                ?>
                <div class="trophy-cabinet-title">
                    Trophy cabinet for top <?php echo $top; ?>%.
                </div>
                <div id="trophyCabinet<?php echo $top; ?>Percent" class="trophy-cabinet">
                    <?php
                    foreach ($areas as $area) {
                        $areaId = $area['id'];
                        $areaIcon = $area['icon'];
                        $areaTitle = $area['title'];
                        $areaRankPositionInTheWorld = $area['rankPositionInTheWorld'];
                        $areaRankPositionInTop = $area['rankPositionInTop'];
                        ?>
                        <a class="top-area top<?php echo $top; ?>percent" href="/nation=united_states_of_america/detail=trend?censusid=<?php echo $areaId; ?>" title="<?php echo $areaTitle; ?>: <?php echo $areaRankPositionInTheWorld; ?>">
                            <div class="trophy">
                                <i class="fa fa-<?php echo $areaIcon; ?>"></i> WP <?php echo $areaRankPositionInTheWorld; ?> &bullet; TP <?php echo $areaRankPositionInTop; ?>
                            </div>
                        </a>
                        <?php
                    }
                    ?>
                </div>
                <?php
            }
        }
        ?>

    </body>
</html>

Used data:

I used the following data:

========================================
Create table syntax
========================================

DROP TABLE IF EXISTS `country_development_areas`;

CREATE TABLE `country_development_areas` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `country` int(11) DEFAULT NULL,
  `name` varchar(100) DEFAULT NULL,
  `value` int(11) DEFAULT NULL,
  `icon` varchar(100) DEFAULT NULL,
  `title` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;

========================================
Insert values syntax
========================================

INSERT INTO `country_development_areas` (`id`, `country`, `name`, `value`, `icon`, `title`)
VALUES
    (0,1,'Civil Rights',2,'hand-peace-o','Most Free People'),
    (1,1,'Economy',10,'area-chart','Most Efficient Economies'),
    (2,1,'Political Freedom',8,'handshake-o','Most Political Freedom'),
    (3,1,'Population',1,'users','Largest Population'),
    (4,2,'Civil Rights',100,'hand-peace-o','Most Free People'),
    (5,2,'Political Freedom',2,'handshake-o','Most Political Freedom');
  • I'm confused: Let's say that the "Civil Rights" of country 1 is displayed (e.g is in a top). The rank result (a number) should be the place (e.g. the rank position) that "Civil Rights" occupies in the list of all "Civil Rights" of all countries (independent of the fact that it's in a top)? If yes, then, theoretically, the "Civil Rights" will have the same rank position in all 3 tops. Right? –  Sep 09 '17 at 18:38
  • The place occupied by "Civil Rights" in the list of all "Civil Rights" of all countries. – main Sep 09 '17 at 18:47
  • Thanks for the solution, it works fine. However how can I hide the top 5%, 10% etc. text if no trophy is in the respective top? Screenshot with how it shows if it's only in top 80%: https://www.screencast.com/t/LIRf19j4NKh See all that empty texts: Top 5% Top 10% Top 20%. – main Sep 11 '17 at 12:08
  • In HTML part I added the check: "_If the list of areas for the current top is not empty, then display the areas._" - `if ($areas) {}` –  Sep 11 '17 at 12:35
  • Didn't notice until now. I think something is wrong with the calculation. I have Political Freedom with a value of 1000 in database but it still doesn't show in top 5%, it shows in the other tops. Screens: https://www.screencast.com/t/f6xIwO8LmnxH https://www.screencast.com/t/WiIT419CHTLq – main Sep 23 '17 at 11:55
  • @main I don't think, the calculation is wrong. I think is something else. Try my code in my answer again and see if false results are displayed. –  Sep 23 '17 at 13:05
  • Same result, it doesn't return false. I use the same code as you in a test file so should be fine but it isn't. – main Sep 23 '17 at 15:30
  • @main Check the data in the table. Are there duplicates of one `name` for a country? –  Sep 23 '17 at 16:30
  • No duplicates, screenshot with database: https://www.screencast.com/t/WiIT419CHTLq – main Sep 23 '17 at 17:31
  • @main The calculus is correct, as it should be. Total `value` of "Political Freedom" in the world = 1000 + 67 = 1067. For top 5%, the `value` must be >= (1 - 5/100) * 1067 = 0.95 * 1067 = 1013.65. So, no country has the `value` for "Political Freedom" >= 1013.65. Therefore, no country will be in top 5% for "Political Freedom". Q.E.D. –  Sep 23 '17 at 19:29
  • @main Now look at "Economy". World total = 200 + 1 = 201. To be in top 5%, a `value` must be >= 0.95 * 201 = 190.95. As you see, the `value` for "Economy" of country 1 is 200 >= 190.95. Therefore, `value` of "Economy" is in top 5%. –  Sep 23 '17 at 19:35