NHL standings in SQL - mysql

I have an SQL query that produces team standings based off the old NHL format. The first section of the code gets the top 3 teams in each division, and the 2nd bit gets the rest and sorts them by points/differential.
This can be seen here: http://rgmgstandings.tk
Here is my SQL query:
("(SELECT *, 1 as `SortKey` from `standings_east`
WHERE pts = (select max(pts)
from standings_east as t
where t.`div` = standings_east.`div`))
UNION ALL
(select *, 2 as SortKey from `standings_east`
where team not in
(select team from standings_east
where pts = (select max(pts)
from standings_east as t
where t.`div` = standings_east.`div`)))
order by SortKey, pts desc, diff desc")
If you visit my website, and look at the standings for the Western Conference (blue banner), you will notice 3 teams in 'CEN' that have the same amount of points (Chicago, Winnipeg, Columbus)
I want the query to select only ONE team from that division based on whoever has the most 'Wins/W'.
The correct standings should be:
Edmonton (NW) 80
Anaheim (PAC) 74
Columbus (CEN) 71
Dallas (PAC) 73
Chicago (CEN) 71
Winnipeg (CEN) 71
How can I accomplish this?

Query
select team,`div`,pts,1 as sortOrder
from
( -- note use parentheses to avoid mysql error 1221
(select team,`div`,pts,#cen:=team from `standings_west` where `div`='CEN' order by pts desc limit 1)
union all
(select team,`div`,pts,#pac:=team from `standings_west` where `div`='PAC' order by pts desc limit 1)
union all
(select team,`div`,pts,#nw:=team from `standings_west` where `div`='NW' order by pts desc limit 1)
) xDerived1
cross join (select #cen='',#pac='',#nw='') params
union
select team,`div`,pts,sortOrder
from
( select team,`div`,pts,2 as sortOrder
from `standings_west`
where team!=#cen and team!=#pac and team!=#nw
order by pts desc
limit 3
) xDerived2
order by sortOrder,pts desc;
Results
+----------+-----+-----+-----------+
| team | div | pts | sortOrder |
+----------+-----+-----+-----------+
| EDMONTON | NW | 80 | 1 |
| ANAHEIM | PAC | 74 | 1 |
| WINNIPEG | CEN | 71 | 1 |
| DALLAS | PAC | 73 | 2 |
| CHICAGO | CEN | 71 | 2 |
| COLUMBUS | CEN | 71 | 2 |
+----------+-----+-----+-----------+
Stored Proc
The following depicts a stored proc just to show it in case you are having problems and need it.
drop procedure if exists xdoit;
delimiter $$
create procedure xdoit()
begin
select team,`div`,pts,1 as sortOrder
from
( -- note use parentheses to avoid mysql error 1221
(select team,`div`,pts,#cen:=team from `standings_west` where `div`='CEN' order by pts desc limit 1)
union all
(select team,`div`,pts,#pac:=team from `standings_west` where `div`='PAC' order by pts desc limit 1)
union all
(select team,`div`,pts,#nw:=team from `standings_west` where `div`='NW' order by pts desc limit 1)
) xDerived1
cross join (select #cen='',#pac='',#nw='') params
union
select team,`div`,pts,sortOrder
from
( select team,`div`,pts,2 as sortOrder
from `standings_west`
where team!=#cen and team!=#pac and team!=#nw
order by pts desc
limit 3
) xDerived2
order by sortOrder,pts desc;
end$$
delimiter ;
call stored proc
call xdoit();
A few comments here.
First, your sqlfiddle had data from the west, but a query from the east. Based on table names, I suggest you have all your data in one table not two and have a column for east or west.
The query uses a cross join to merely establish variables for grabbing the division leaders so those division leaders are excluded for the sortOrder=2 teams.
Tweak as necessary for tie-breaks (ie: the teams with 71 points) regarding your implemention of DIFF in comments to #Clockwork
Ask if you have any questions.
The following is the multi_query php solution I came up with for you based on your posting of a comment in that chat room.
The only solution I could come up with was the following based on clearly the results were coming back in two result sets. Thus the while loop that is driven by next_result(). The first result set has the top 3 rows, the 2nd result set has the twelve rows that follow. That is just the way PHP seems to see it.
Also note that in the PHP part, since I seemed to be dealing with a multi_query, I took advantage of that and passed the mysql parameters in versus doing a cross join to pick them up.
PHP
<!DOCTYPE html>
<html lang="en">
<head>
<title>RGMG: Standings</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
</head>
<body>
<?php
//mysqli_report(MYSQLI_REPORT_ALL);
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
error_reporting(E_ALL); // report all PHP errors
ini_set("display_errors", 1);
try {
$mysqli= new mysqli('localhost', 'dbUser', 'thePassword', 'theDbName');
echo "<table><div><br><br>"; // note you had this line a little bit wrong
// notice below the concat of the $sql and the multi_query()
$sql = "set #cen:='',#pac:='',#nw:=''; ";
$sql .= "select *
from
( select team,`div`,gp,win,lose,otl,goalsF,goalsA,diff,gpg,gaa,pts,1 as sortOrder
from
( -- note use parentheses to avoid mysql error 1221
(select team,`div`,gp,win,lose,otl,goalsF,goalsA,diff,gpg,gaa,pts,#cen:=team from `standings_west` where `div`='CEN' order by pts desc, win desc, diff desc limit 1)
union all
(select team,`div`,gp,win,lose,otl,goalsF,goalsA,diff,gpg,gaa,pts,#pac:=team from `standings_west` where `div`='PAC' order by pts desc, win desc, diff desc limit 1)
union all
(select team,`div`,gp,win,lose,otl,goalsF,goalsA,diff,gpg,gaa,pts,#nw:=team from `standings_west` where `div`='NW' order by pts desc, win desc, diff desc limit 1)
) xDerived1
union all
select team,`div`,gp,win,lose,otl,goalsF,goalsA,diff,gpg,gaa,pts,sortOrder
from
( select team,`div`,gp,win,lose,otl,goalsF,goalsA,diff,gpg,gaa,pts,2 as sortOrder
from `standings_west`
where team!=#cen and team!=#pac and team!=#nw
order by pts desc
) xDerived2
) xDerived3
order by sortOrder,pts desc";
echo "<div class='container'>";
echo "<img src='http://i.imgur.com/sjDHhIV.png' width='100%' alt='West'>";
echo "<table class='table table-condensed'>
<tr class='top'>
<th class='rank'></th>
<th class='team'>TEAM</th>
<th>DIV</th>
<th>GP</th>
<th>W</th>
<th>L</th>
<th class='otl'>OTL</th>
<th class='pts'>PTS</th>
<th>GF</th>
<th>GA</th>
<th>DIFF</th>
<th>GPG</th>
<th>GAA</th>
";
$counter=1;
$mysqli->multi_query($sql);
while(true) {
if ($result = $mysqli->store_result()) {
while ($row = $result->fetch_assoc()) {
$gpg = ($row['goalsF']);
$gaa = ($row['goalsA']);
if ($row['gp'] != 0 ){
$gpg = ($row['goalsF'] / $row['gp']);
$gaa = ($row['goalsA'] / $row['gp']);
}
else {
$row['gp'] = "";
}
echo "<tr>
<td class='rank'>" . "$counter" . "</td>
<td class='team'>" . $row['team'] . "</td>
<td>" . $row['div'] . "</td>
<td>" . $row['gp'] . "</td>
<td>" . $row['win'] . "</td>
<td>" . $row['lose'] . "</td>
<td class='otl'>" . $row['otl'] . "</td>
<td class='pts'>" . $row['pts'] . "</td>
<td>" . $row['goalsF'] . "</td>
<td>" . $row['goalsA'] . "</td>
<td>" . $row['diff'] . "</td>
<td>" . round($gpg, 2) . "</td>
<td>" . round($gaa, 2) . "</td>";
$counter++;
}
$result->free();
}
if ($mysqli->more_results()) {
$mysqli->next_result();
}
else {
break;
}
}
echo "</table></div>";
$mysqli->close();
} catch (mysqli_sql_exception $e) {
throw $e;
}
?>
</body>
</html>

First i love NHL and from your query we can say the table you are showing us is standings_east,so let's try this:
select team,DIV,GP,W,L,OTL,PTS,GF,GA,DIFF,GPG,GAA,1 as sort_key
from standings_east t
where exists
(select 1
from
(select DIV,max(PTS) as PTS,max(W) as W from standings_east
group by DIV) a
where t.DIV=a.DIV and t.PTS=a.PTS and t.W=a.W
)
UNION ALL
select team,DIV,GP,W,L,OTL,PTS,GF,GA,DIFF,GPG,GAA,2 as sort_key
from standings_east t
where not exists
(select 1
from
(select DIV,max(PTS) as PTS,max(W) as W from standings_east
group by DIV) a
where t.DIV=a.DIV and t.PTS=a.PTS and t.W=a.W
)
order by sort_key,PTS DESC
i think there is some better way but this is the one like yours the most and the easiest to understand just add a group by on your code and Merry Christmas

Related

LIMIT MySQL subquery using variable value before LEFT JOIN

I have this MySQL table posts:
id | content | parentid | userid
--------------------------------------
01 | Post test #1 | 0 | 1
02 | Post test #2 | 0 | 1
03 | Comment #1 | 1 | 2
04 | Comment #2 | 1 | 1
05 | Post test #3 | 0 | 3
06 | Comment #3 | 1 | 2
07 | Comment #4 | 2 | 5
08 | Comment #5 | 5 | 6
09 | Comment #6 | 1 | 4
10 | Post test #4 | 0 | 4
This is just an example for stackoverflow
Now I need to LIMIT comments for each post and so far I have used this query:
SELECT
`posts`.`id` AS `post_id`,
`posts`.`content` AS `post_content`,
`posts`.`parentid` AS `post_parentid`,
`posts`.`userid` AS `post_userid,
`comments`.`id`, 0 AS `comment_id`,
`comments`.`content` AS `comment_content`,
`comments`.`parentid` AS `comment_parentid`,
`comments`.`userid` AS `comment_userid,
IF( IFNULL( `comments`.`id`, 0 ) > 0, "comment", "post" ) AS `contenttype`
FROM `posts` AS `posts`
LEFT JOIN ( SELECT "" AS `hello` ) AS `useless` ON #pid := `posts`.`id`
LEFT JOIN ( SELECT
`posts`.`id` AS `id`,
`posts`.`id` AS `id`,
`posts`.`id` AS `id`,
`posts`.`id` AS `id`
FROM `posts`
WHERE `posts`.`parentid` = #pid
LIMIT 10
) AS `comments`ON `comments`.`parentid` = `posts`.`id`
WHERE
`posts`.`userid` = {USERID}
To archive this I have joined an useless "table" just to update #pid (parentid) variable.
Is this the only way to LIMIT subquery results? I don't like the idea of that useless JOIN.
What if I have to LIMIT posts in the example above without affecting the comments LIMIT. Can you please give me a better query?
The real reason of posting this question was to load 10 comments with 10 sub-comments for each comment. On the question I have asked to load posts & comments so the idea is the same.
The example posted in my question doesn't works because the subquery will executed before the variable #pid gets updated.
Because I'm using PHP then I'm posting here the solution in MySQL & PHP for this situation.
1 - First let's load posts with this SQL query
SELECT
`posts`.`id` AS `id`,
`posts`.`content` AS `content`,
`posts`.`parentid` AS `parentid`,
`posts`.`userid` AS `userid
FROM `posts` AS `posts`
WHERE
`posts`.`userid` = {USERID}
AND
`posts`.`parentid` = '0'
ORDER BY `posts`.`id` DESC
LIMIT 10
2 - Store posts information in $posts array:
$posts = [];
while ( $row = $result->fetch_object() )
{
$posts[] = (object) [ "id" => $row->id,
"content" => $row->content,
"userid" => $row->userid,
"comments" => []
];
}
3 - Prepare SQL to load comments:
$size = count( $posts );
$sql = "";
for ( $i = 0; $i < $size; $i ++ )
{
$sql .= ( $sql != "" ? "UNION ALL " : "" )
. "( "
. "SELECT "
. "`comments`.`id` AS `id`, "
. "`comments`.`content` AS `content`, "
. "`comments`.`parentid` AS `parentid`, "
. "`comments`.`userid` AS `userid "
. "FROM `posts` AS `comments` "
. "WHERE "
. "`comments`.`parentid` = '" . $post[ $i ]->id . "' "
. "ORDER BY `comments`.`id` ASC "
. "LIMIT 10 "
. ") ";
}
4 - After executing the $sql code let's store comments for each post:
while ( $row = $result->fetch_object() )
{
$posts[ $row->parentid ]->comments[] = (object)[
"id" => $row->id,
"content" => $row->content,
"userid" => $row->userid,
];
}
As you can see this can be used for also comments (instead of posts ) & sub-comments (instead of comments). MySQL variables are not helpful this time. Of course to create a pagination you have to add additional field (replies) in the table and update that during comment creation.
If someone has a better solution is welcomed.

select increment counter reset by user in mysql

I read about create variable for increment in select in this post select increment counter in mysql
I need some addition to this query.. I need to reset the increment based on user_id.
Sample data :
id user_id name
1 1 A
2 2 B
3 3 C
4 1 D
5 2 E
6 2 F
7 1 G
8 3 H
Expected result:
id user_id name increment
1 1 A 1
4 1 D 2
7 1 G 3
2 2 B 1
5 2 E 2
6 2 F 3
3 3 C 1
8 3 H 2
It's not stop only until 3 increments, if I have more row with user_id, it will continue the increment.
How do I make query so the output look like that? Thanks!
Try this:
SELECT A.*, CASE WHEN id=user_id THEN 1
WHEN id>user_id THEN 2
ELSE 0 END increment
FROM yourTable A
ORDER BY A.user_id;
See it run on SQL Fiddle.
You do not need a variable to do that. simple COUNT(), and GROUP BY would do the trick.
SELECT user_id, name, COUNT(user_id) AS increment FROM your_table GROUP BY user_id;
But this will return a single value for one user.
This will pass all count.
SELECT user_id, name, (SELECT COUNT(t2.user_id) FROM your_table t2 WHERE t2.user_id=t1.user_id) AS increment FROM your_table t1;
I'm not a expert at larevel. This is a simple PHP code.
<?php
$db = new mysqli("localhost","root","","test");
$sql = $db->query("SELECT * FROM your_table ORDER BY user_id");
$name_array = array();
$array_counts = array();
?>
<table>
<tr>
<th>id</th>
<th>user id</th>
<th>name</th>
<th>increment</th>
</tr>
<?php
while ($row = $sql->fetch_assoc()){
if (in_array($row['user_id'],$name_array)){
$array_counts[$row['user_id']][0] = $array_counts[$row['user_id']][0]+1;
} else {
$array_counts[$row['user_id']][0] = 1;
}
array_push($name_array,$row['user_id']);
?>
<tr>
<td><?php echo $row['id']; ?></td>
<td><?php echo $row['user_id']; ?></td>
<td><?php echo $row['name']; ?></td>
<td><?php echo $array_counts[$row['user_id']][0]; ?></td>
</tr>
<?php
}
?>
</table>
I was looking for something similar and I may have worked it out (maybe it wasn't exactly what you were looking for) :
SET #increment := 0;
SET #user_id_mem := 0;
SELECT
#increment := CASE WHEN tbl.user_id = #user_id_mem THEN (#increment+1) ELSE 1 END as increment,
tbl.id,
#user_id_mem := tbl.user_id,
tbl.name
FROM YOURTABLE tbl
ORDER BY user_id;

Select Count and Then Order By

I am having some problems trying to find the members with the most referrals.. I have been looking up stuff and trying stuff for more than 3 hours so this is my last resort. My goal is to list the members username and the amount of referrals they have..
So each members referral is marked by their id..
so in the example test2 is orig's ref. So each persons ref is marked by their id.
id | username | ref
1 | orig |0 (for none)
2 | test2 |1 (orig id)
3 | another |1 (orig id)
and the goal is to echo this out. If it is even possible.
UserName | Refs
test 1
test2 0
my attempt. It gives me the amount but it does not order them and echoes to many.
$result = mysql_query("SELECT * FROM members");
echo '<table class="table table-bordered"><tr><th>Credits</th><th>Username</th></tr>';
while($row = mysql_fetch_array($result))
{
$contest_id = $row['id'];
$result1 = mysql_query("SELECT COUNT(*) FROM members WHERE ref=".$row['id']."");
while($row1=mysql_fetch_array($result1))
{
$result_c = mysql_query("SELECT * FROM members ORDER BY ".$row1['COUNT(*)']."+0 DESC Limit 10");
while($row_c = mysql_fetch_array($result_c))
{
$top_id = $row_c['id'];
$find_c = mysql_query("select count(*) from members where user_id='$top_id'");
$found_c = mysql_result($find_c,0);
echo '<tr><td>';
echo $found_c;
echo '</td>';
echo '<td>';
echo $row_c['username'];
echo '</td>';
echo '</tr>';
}}}
echo '</table>';
If I understood your table lay-out correctly, this should do the trick.
SELECT t1.username, t2.numref
FROM members t1
LEFT JOIN (SELECT ref, COUNT(ref) as numref
FROM members
GROUP BY ref) t2
ON t2.ref = t1.id
ORDER BY numref DESC
It should provide a NULL value in the numref column for the users that haven't made any referrals.
For reference, I added a SQL-Fiddle!

Select rows from a table where all values of an array exist

I have this query that retreives a list of id's + NAME:
$sql = "SELECT id FROM #__table1 ";
$sql .= " WHERE (";
foreach($explode_tags as $k=>$explode_tag) {
$sql .= "name = ".$db->Quote(trim($explode_tag));
if(($k+1) != count($explode_tags))
$sql .= " OR ";
}
$sql .= ")";
$db->setQuery($sql);
$results = $db->loadResultArray();
The result is an array like this:
keywordID | NAME
1 cat
2 dog
3 horse
Now I have this table2:
id | ItemID | keywordID
1 4 1
2 4 2
3 4 3
4 6 1
5 6 2
6 7 1
I want to find from table2 all ItemID's that have all keywordID's found in table1.
In the example above I want to return only itemID 4 that has all keywords (all 3 of them).
I am running this query but I am not getting results:
...
$query .= " AND i.id IN (SELECT itemID FROM #__table2 WHERE (";
foreach($results as $k=>$result) {
$query .= "keywordID = ".(int)$result;
if(($k+1) != count($results))
$query .= " AND ";
}
$query .= "))";
UPDATE
Sorry, I miss read your question. I've done simple test using this data:
id itemId keywordId
1 4 1
5 4 2
6 4 3
7 5 2
8 5 3
9 6 1
10 6 2
11 6 3
12 7 3
13 9 3
14 9 2
15 9 1
and using this query:
SELECT itemId, GROUP_CONCAT( keywordId ORDER BY keywordId ) AS crpcnct, COUNT( itemId )
FROM `temporary_table_123`
GROUP BY 1
HAVING crpcnct = '1,2,3'
I can get the value that you wanted:
itemId crpcnct count(itemId)
4 1,2,3 3
6 1,2,3 3
9 1,2,3 3
To achieve this, all you have to do is build the keywordID you want to use:
$keywordIds[] = $results['keywordId'];
and then sort accending
sort($keywordIds);
the last step is, supply this array into query:
SELECT itemId, GROUP_CONCAT( keywordId ORDER BY keywordId ) AS crpcnct, COUNT( itemId )
FROM `temporary_table_123`
GROUP BY 1
HAVING crpcnct = '" . implode(",", $keywordIds) . "'
There you have it.
SELECT ItemID FROM table2 WHERE keywordID IN (SELECT keywordID FROM table1)
This might work. I'll have to create local copies of your tables to see for sure, though.
$array_names = array();
foreach($explode_tags as $k=>$explode_tag){
$array_names[] = 'name = ' . $db->Quote(trim($explode_tag));
}
$sql = "SELECT ItemID FROM table_2 WHERE keywordID IN (SELECT keywordID FROM table_1 WHERE " . implode(' OR ', $array_names) .")";
$db->setQuery($sql);
$results = $db->loadResultArray();

how to get records in the following scenario

I have a table like below :
node_name id term_name
----------------------------------------------
test1 001 physics
test1 001 maths
test1 001 chemistry
test2 002 physics
test2 002 maths
Given a combination of term names I want to find all rows where the id set only contains exactly the given term names.
For example given the term names physics & maths my output should be like below
node_name id term_name
----------------------------------------------
test2 002 physics
test2 002 maths
Id set 001 contains also chemistry that is why it should not be included.
Your question: get all rows where no other rows with same id but other term_names exists
SELECT * FROM <table> x WHERE
term_name IN ('physics','maths') AND
NOT EXISTS (SELECT * FROM <table> WHERE id=x.id AND term_name NOT IN ('physics','maths'))
first of all you need to parse your query to convert the '&' to SQL 'OR' operator
in PHP :
//Parse the query
$arr = explode('&',$query);
$where = '';
//get the term count
$count = count($arr);
foreach($arr as $value){
$where .= "term_name = '" . $value . "' OR";
}
//Remove last or
$where = rtrim($where,'OR');
then :
use L
"select node_name ,count(1) as Total from my table where $where
group by node_name
having Total =" . $count
Finally :
your query must be in this format:
select x,count(1) as total from mytable where field1 = 'term1' or field1 = 'term2' having total = 2
One possible way to do this:
select id, node_name
from nodes join
(select id,
count(*)
from nodes
where node_name in ('physics','math')
group by id
having count(*) = 2 // this is set when generating the query ) as eligible_nodes
on nodes.id = eligible_nodes.id