Jump to content

Query for One To Many Relation In The Same Table


Recommended Posts

Posted (edited)

Hi Barand,

I am seeking you help to make an appropriate query to fetch data from monthly fee table of mysql db, whereby data is stored in such a way that each row stores the data of a student for one month, there are several rows per student.

I want to fetch the data such that the result set should have the one row per student with multiple columns for months, each column showing the status of fee, paid or not.

I have created the following query but the problem is that it is showing only one month which is '2020-03-01'.

 

SELECT DISTINCT name, month, paid from mfee WHERE month >= '2020-03-01' GROUP BY name order by name

 

Desired-Result.jpg

Edited by JDevOnline
Link to post
Share on other sites

Easiest way I know is first to store your query data in an array that has the same structure as your required output

<?php
$start = '2020-03-01';

//
// Query to get column headings
//
$res = $conn->prepare("SELECT DISTINCT
                              month(month)     as mno
                            , monthname(month) as mname
                       FROM fee
                       WHERE month >= ?
                       ORDER BY mno    
                       ");
$res->execute([$start]);
$heads = [];
foreach ($res as $row) {
    $heads[$row['mno']]  = $row['mname'];
}
$empty_array = array_fill_keys(array_keys($heads), '');

//
//  now process the fee data and store in an array
//
$data = [];
$res = $conn->prepare("SELECT name
                            , month(month) as mno
                            , paid
                       FROM fee
                       WHERE month >= ? 
                       ORDER BY name, month
                      ");
$res->execute([$start]);
foreach ($res as $row) {
    if (!isset($data[$row['name']])) {
        $data[$row['name']] = $empty_array;
    }
    $data[$row['name']][$row['mno']] = $row['paid'];
}

//
//  now output the array
//
$theads = "<tr><th>Name</th><th>" . join('</th><th>', $heads) . "</th></tr>\n";
$tdata = '';
foreach ($data as $name => $paid_arr) {
    $tdata .= "<tr><td>$name</td><td>" . join('</td><td>', $paid_arr) . "</td></tr>\n";
}
?>
<html>
<head>
<title>Sample</title>
<style type="text/css">
     body, table {
         font-family: verdana, sans-serif;
         font-size: 11pt;
     }
     table {
         width: 400px;
     }
     th {
         background-color: black;
         color: white;
     }
     td {
         text-align: center;
     }
</style>
</head>
<body>
    <table>
    <?=$theads?>
    <?=$tdata?>
    </table>
</body>
</html>

image.png.ad1513dfb69be5354e32581e93aaaf1c.png

Link to post
Share on other sites

Barand thank you very much for the prompt reply. After make 2 changes to your code I saved the code into a php file and called it into the browser, but it is showing few errors, kindly let me know how to remove these errors.

Changes:

Table name changed to "mfee" at both the occurrences.

In both the queries "WHERE month >= ?" is changed to "WHERE month >= '$start'"

 

Output:

error.png

Link to post
Share on other sites

Hi Barand,

Using CSS I want to highlight the cells where paid is equal to "N". I can't figure out where to insert this code due to arrays. Please help:

<td 
    <?php
    		if($row['paid']=='N'){echo 'class="highlight"';}
    ?>
</td>

 

Link to post
Share on other sites

You need to rewrite this line ...

 $tdata .= "<tr><td>$name</td><td>" . join('</td><td>', $paid_arr) . "</td></tr>\n";

so you output the name then loop through the $paid_array.

Link to post
Share on other sites

Hello Barand,

I've tried to change the code you mentioned as below but it is still now working. Please help me modify the code to achieve the desired results:

if($paid_arr == 'Y'){$cls='class="hilite"';}else{$cls='class="centex"';}
echo "<tr><td class='centex'>$index</td><td>$name</td><td $cls>" . join("</td><td $cls>", $paid_arr) . "</td></tr>\n";

 

Link to post
Share on other sites
foreach ($data as $name => $paid_arr) {
    $tdata .= "<tr><td>$name</td>";
    foreach ($paid_arr as $p) {
        $cls = $p=='N' ? 'hilite' : '';
        $tdata .= "<td class='$cls'>$p</td>";
    }
    $tdata .= "</tr>\n";
}

image.png.0ecab09218fc2aa0da00cb432dcadafb.png

Link to post
Share on other sites

Hi Barand,

I now want to extract the details of students who have paid all 2 months, i.e. march, april and may. I have written the following query but it is not bringing any records, however there are many students who have paid all 3 months.

 

SELECT nroll.id as id, nroll.sname, nroll.ctclass, parents.memail, parents.femail FROM nroll 
INNER JOIN parents on nroll.id=parents.id 
INNER JOIN mfee on nroll.id=mfee.sid 
WHERE nroll.ctstudent = 'Y' and nroll.ctclass='1 Reception-1' and nroll.shift='1' and 
(mfee.month='2020-01-01' and mfee.month='2020-02-01') 
ORDER BY nroll.sname

 

Link to post
Share on other sites
3 hours ago, JDevOnline said:

who have paid all 2 months, i.e. march, april and may.

Ah yes , the three types of programmer, those who can count and those who can't

Use a table subquery to find those who paid all two and match against it with a join

SELECT name
	, month(month) as mno
	, paid
FROM fee
     JOIN (
            SELECT name
                 , count(*)
            FROM fee
            WHERE month(month) IN (3,4,5) AND paid = 'Y'
            GROUP BY name
            HAVING count(*) = 3
          ) paid3 USING (name)
     
WHERE month >= '2020-03-01' 
ORDER BY name, month

 

Link to post
Share on other sites
Posted (edited)
4 hours ago, JDevOnline said:

How can we show just one row per student's name and only two columns, name and "Y"

In a very to similar manner to the code already given.

SELECT name
     , 'Y' as paid
FROM fee
WHERE month(month) IN (3,4,5) AND paid = 'Y'
GROUP BY name
HAVING count(*) = 3;

 

Edited by Barand
Link to post
Share on other sites
  • 1 month later...
On 5/28/2020 at 1:48 PM, Barand said:

foreach ($data as $name => $paid_arr) {
    $tdata .= "<tr><td>$name</td>";
    foreach ($paid_arr as $p) {
        $cls = $p=='N' ? 'hilite' : '';
        $tdata .= "<td class='$cls'>$p</td>";
    }
    $tdata .= "</tr>\n";
}

image.png.0ecab09218fc2aa0da00cb432dcadafb.png

Hi Barand,

I want to insert one more value to the above table after "Name", like email or phone number. Please tell me how can that value be put inside your loops?

 

Link to post
Share on other sites

Change your query to retrieve the extra item and restructure the $data array to store it.

$data = [];
$res = $conn->prepare("SELECT name
                            , email                             -- extra item
                            , month(month) as mno
                            , paid
                       FROM fee
                       WHERE month >= ? 
                       ORDER BY name, month
                      ");
$res->execute([$start]);
foreach ($res as $row) {
    if (!isset($data[$row['name']])) {
        $data[$row['name']] = [ 'email' => $row['email'], 
                                'pays' => $empty_array ];
    }
    $data[$row['name']]['pays'][$row['mno']] = $row['paid'];
}

 

Link to post
Share on other sites
  • 2 weeks later...
On 6/2/2020 at 12:54 PM, Barand said:

Ah yes , the three types of programmer, those who can count and those who can't

Use a table subquery to find those who paid all two and match against it with a join


SELECT name
	, month(month) as mno
	, paid
FROM fee
     JOIN (
            SELECT name
                 , count(*)
            FROM fee
            WHERE month(month) IN (3,4,5) AND paid = 'Y'
            GROUP BY name
            HAVING count(*) = 3
          ) paid3 USING (name)
     
WHERE month >= '2020-03-01' 
ORDER BY name, month

 

Hi Barand, hope you are well,

Can you please suggest the edit to this sql query to fetch only the rows in which any month (from January to May) is not paid but at the same time keep showing all the columns of months. Doing it by using the quoted query, it removes the columns in which the fee is paid, thanks.

Link to post
Share on other sites

That query is looking at March to May only, so that needs changing. Also it looks for those who paid; you now want those who didin't pay. In addition, you are looking for any occurence, not when there are 3.

Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.