Advanced SQL
Charles Severance
[Link]
Error Checking So Far
We get away with ignoring errors because they are rare and usually “big”
• Bad database connection
• Bad SQL syntax in a query
• Missing table, missing column – schema / query mismatch
• Missing required parameter
• Violation of a constraint
Start Simple
We just configure PDO to throw an error if anything goes wrong
<?php
$pdo = new PDO('mysql:host=localhost;port=8889;dbname=misc',
'fred', 'zap');
// See the "errors" folder for details...
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
php-intro/code/pdo/[Link]
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $pdo->prepare("SELECT * FROM users where id = :xyz");
$stmt->execute(array(":pizza" => $_GET['id']));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if ( $row === false ) {
$_SESSION['error'] = 'Bad value for id';
header( 'Location: [Link]' ) ;
return;
}
php-intro/code/pdoerrors/[Link]
In Production Environments
• We do not want to have tracebacks in the user interface - may reveal
sensitive data
• We want extensive error logging of any error anywhere in our application
– users will not report errors
• Some errors are subtle and can be affected by user-entered data – length of
VARCHAR field, for example
• People attacking your system “Fuzz Testing” POST weird data
[Link]
$sql = "INSERT INTO {$p}sample_map
(context_id, user_id, lat, lng, updated_at)
VALUES ( :CID, :UID, :LAT, :LNG, NOW() )
ON DUPLICATE KEY
UPDATE lat = :LAT, lng = :LNG, updated_at = NOW()";
$stmt = $PDOX->prepare($sql);
$stmt->execute(array(
':CID' => $CONTEXT->id, What could go
':UID' => $USER->id, wrong?
':LAT' => $_POST['lat'],
':LNG' => $_POST['lng']));
$_SESSION['success'] = 'Location updated...';
header( 'Location: '.addSession('[Link]') ) ;
return;
tsugi/exercises/map/[Link]
[Link]
[Link]
[Link]
$rows = $PDOX->queryDie(
"DELETE FROM {$p}attend WHERE link_id = :LI",
array(':LI' => $LINK->id)
);
function queryDie($sql, $arr=FALSE, $error_log=TRUE) {
tsugi/mod/attend/[Link]
$q = FALSE;
$success = FALSE;
$message = '';
try {
$q = $this->prepare($sql);
if ( $arr === FALSE ) {
$success = $q->execute();
} else {
$success = $q->execute($arr);
}
} catch(\Exception $e) {
$success = FALSE;
$message = $e->getMessage();
if ( $error_log ) error_log($message);
}
if ( ! $success ) die('Internal database error');
return $q;
}
tsugi/lib/vendor/Tsugi/Util/[Link] queryReturnError()
$sql = "INSERT INTO {$p}sample_map
(context_id, user_id, lat, lng, updated_at)
VALUES ( :CID, :UID, :LAT, :LNG, NOW() )
ON DUPLICATE KEY
UPDATE lat = :LAT, lng = :LNG, updated_at = NOW()";
$stmt = $PDOX->prepare($sql);
$stmt->execute(array(
':CID' => $CONTEXT->id,
':UID' => $USER->id,
':LAT' => $_POST['lat'],
':LNG' => $_POST['lng']));
$_SESSION['success'] = 'Location updated...';
header( 'Location: '.addSession('[Link]') ) ;
return;
tsugi/exercises/map/[Link]
$stmt = $PDOX->queryDie("INSERT INTO {$p}sample_map
(context_id, user_id, lat, lng, updated_at)
VALUES ( :CID, :UID, :LAT, :LNG, NOW() )
ON DUPLICATE KEY
UPDATE lat = :LAT, lng = :LNG, updated_at = NOW()",
array(
':CID' => $CONTEXT->id,
':UID' => $USER->id,
':LAT' => $_POST['lat'],
':LNG' => $_POST['lng'])
);
$_SESSION['success'] = 'Location updated...';
header( 'Location: '.addSession('[Link]') ) ;
return;
tsugi/lib/vendor/Tsugi/Util/[Link]
Advanced Queries
LEFT JOIN
• For a normal JOIN, a row is included in the result of the SELECT if and
only if both sides of the ON clause are present.
- The ON clause functions as a WHERE clause.
- The order of the tables in the JOIN clause does not matter.
• A LEFT JOIN removes this restriction.
All the rows from the “left” table that match the WHERE clause are
included whether or not the ON clause finds a row in the “right” table.
[Link] Album.artist_id Artist.artist_id [Link]
select [Link], Album.artist_id, Artist.artist_id,[Link]
from Album join Artist on Album.artist_id = Artist.artist_id
Users Profile
SELECT [Link],Users.user_id,
Profile.user_id,[Link]
FROM Users JOIN Profile ON Users.user_id = Profile.user_id
Users Profile
SELECT [Link],Users.user_id, Profile.user_id,[Link]
FROM Users LEFT JOIN Profile ON Users.user_id = Profile.user_id
Example of LEFT JOIN
In the OAUTH 1.x protocols in order to defeat replay attacks, each launch
includes a “Cryptographic Nonce”.
In security engineering, a nonce is an arbitrary number used only once in a
cryptographic communication. It is similar in spirit to a nonce word, hence
the name. It is often a random or pseudo-random number issued in an
authentication protocol to ensure that old communications cannot be reused
in replay attacks.
[Link]
[Link]
LTI Sample Launch Data
lti_version=LTI-1p0
lti_message_type=basic-lti-launch-request
context_id=456434513
context_title=SI301 – PHP
resource_link_id=120988f929-274612
user_id=292832126
roles=Instructor
lis_person_name_full=Charles R. Severance
lis_person_contact_email_primary = csev@[Link]
tool_consumer_instance_description=University of School
oauth_consumer_key=[Link]
oauth_nonce=0ff19a855706012c33233dfb8ecd0c9c
...
[Link]
tsugi/docs/lectures/[Link]
lti_nonce
lti_nonce
SELECT k.key_id, k.key_key, [Link], [Link]
FROM lti_key AS k
LEFT JOIN lti_nonce AS n ON k.key_id = n.key_id AND [Link] = :nonce
WHERE k.key_sha256 = :key
LIMIT 1
SELECT k.key_id, k.key_key, [Link], k.new_secret, c.settings_url AS key_settings_url,
[Link],
c.context_id, [Link] AS context_title, context_sha256, c.settings_url AS context_settings_url,
l.link_id, [Link] AS link_title, [Link] AS link_settings, l.settings_url AS link_settings_url,
u.user_id, [Link] AS user_displayname, [Link] AS user_email,
[Link] AS subscribe, u.user_sha256 AS user_sha256,
m.membership_id, [Link], m.role_override,
p.profile_id, [Link] AS profile_displayname, [Link] AS profile_email,
[Link] AS profile_subscribe,
The “big
s.service_id, s.service_key AS service,
r.result_id, [Link], [Link], r.result_url
JOIN”
FROM lti_key AS k
LEFT JOIN lti_nonce AS n ON k.key_id = n.key_id AND [Link] = :nonce
LEFT JOIN lti_context AS c ON k.key_id = c.key_id AND c.context_sha256 = :context
LEFT JOIN lti_link AS l ON c.context_id = l.context_id AND l.link_sha256 = :link
LEFT JOIN lti_user AS u ON k.key_id = u.key_id AND u.user_sha256 = :user
LEFT JOIN lti_membership AS m ON u.user_id = m.user_id AND c.context_id = m.context_id
LEFT JOIN profile AS p ON u.profile_id = p.profile_id
LEFT JOIN lti_service AS s ON k.key_id = s.key_id AND s.service_sha256 = :service
LEFT JOIN lti_result AS r ON u.user_id = r.user_id AND l.link_id = r.link_id
WHERE k.key_sha256 = :key LIMIT 1
tsugi/lib/vendor/Tsugi/Core/[Link] loadAllData()
GROUP BY
• Sometimes instead of wanting all of the rows from a table, we want to
count the distinct values of a column.
• This is done with a GROUP BY and aggregation function.
SELECT album_id, COUNT(track_id) FROM Track GROUP BY album_id
Subqueries (use wisely)
Sometimes in a WHERE clause, you want to choose records based on
another query.
SELECT [Link] FROM Track WHERE album_id IN
(SELECT album_id FROM Album WHERE title LIKE '%I%')
[Link]
An Example from Tsugi
tsugi/mod/peer-grade
SELECT S.submit_id, S.user_id, S.created_at,
count(G.user_id) AS grade_count
FROM {$CFG->dbprefix}peer_submit AS S
LEFT JOIN {$CFG->dbprefix}peer_grade AS G
ON S.submit_id = G.submit_id
WHERE S.assn_id = :AID AND S.user_id != :UID AND S.submit_id NOT IN
( SELECT DISTINCT submit_id from {$CFG->dbprefix}peer_grade
WHERE user_id = :UID)
GROUP BY S.submit_id, S.created_at
ORDER BY grade_count ASC, S.created_at ASC
LIMIT 10
tsugi/mod/peer-grade/peer_util.php loadUngraded()
[Link]
tsugi/mod/peer-grade/[Link]
SELECT S.user_id AS user_id, displayname, email,
S.submit_id as _submit_id,
MAX([Link]) as max_score, MIN([Link]) AS min_score,
COUNT([Link]) as scores,
COUNT(DISTINCT F.flag_id) as flagged,
MAX(S.updated_at) AS updated_at, user_key
FROM {$p}peer_assn AS A
JOIN {$p}peer_submit as S
ON A.assn_id = S.assn_id
JOIN {$p}lti_user AS U
ON S.user_id = U.user_id
LEFT JOIN {$p}peer_grade AS G
ON S.submit_id = G.submit_id
LEFT JOIN {$p}peer_flag AS F
ON S.submit_id = F.submit_id
WHERE A.link_id = :LID
GROUP BY S.submit_id
tsugi/mod/peer-grade/[Link]
SELECT S.assn_id, S.user_id AS user_id, email,
displayname, S.submit_id as submit_id,
MAX(points) as max_points, COUNT(points) as count_points,
C.grade_count as grade_count
FROM {$CFG->dbprefix}peer_submit as S
JOIN {$CFG->dbprefix}peer_grade AS G ON S.submit_id = G.submit_id
JOIN {$CFG->dbprefix}lti_user AS U ON S.user_id = U.user_id
LEFT JOIN (
SELECT G.user_id AS user_id, count(G.user_id) as grade_count
FROM {$CFG->dbprefix}peer_submit as S
JOIN {$CFG->dbprefix}peer_grade AS G
ON S.submit_id = G.submit_id
WHERE S.assn_id = :AID AND G.user_id = :UID
) AS C
ON U.user_id = C.user_id
WHERE S.assn_id = :AID AND S.user_id = :UID
tsugi/mod/peer-grade/peer_util.php computeGrade()
Summary
• More advanced error checking in PDO (it’s complex)
• LEFT JOIN
• GROUP BY
• Subqueries
• AS
• There is still much more...
Acknowledgements / Contributions
These slides are Copyright 2010- Charles R. Severance ([Link]- Continue new Contributors and Translators here
[Link]) as part of [Link] and made available under a
Creative Commons Attribution 4.0 License. Please maintain this
last slide in all copies of the document to comply with the
attribution requirements of the license. If you make a change, feel
free to add your name and organization to the list of contributors on
this page as you republish the materials.
Initial Development: Charles Severance, University of Michigan
School of Information
Insert new Contributors and Translators here including names and
dates