Multiple where conditions using OR - php

I am having an issue with my SQL code.
I need to test multiple conditions against different columns where the inputs are optional. Currently the code will not execute unless both input fields have values. My code is below:
if((empty($fname) === false or empty($lname) === false) && $date1 == null && $date2 == null)
{
$result = mysqli_query($con,"SELECT u.FirstName AS 'First Name', u.LastName AS 'Last Name' , CAST(n.CreatedOn AS date) AS 'Date' , s.SportName AS 'Sport', n.DocumentID AS 'Document ID', n.DocumentName AS 'PDF'
FROM users u
INNER JOIN ncaadocuments n
ON u.UserID = n.UserID
INNER JOIN userathletes a
ON n.UserID = a.UserID
INNER JOIN sport s
ON a.SportID = s.SportID
WHERE n.SchoolID = ('$current_user->ID') AND
(u.FirstName LIKE '%$fname%' OR
u.LastName LIKE '%$lname%')
GROUP BY u.LastName
ORDER BY n.CreatedOn");
echo $fname;
echo $lname;
show_results($result);
}

One of the tricks I've used in the past is to write my where clauses like this:
WHERE (myColumn = #neededVal or #neededVal is null)
and (secondColumn = #anotherVal or #anotherVal is null)
... you'll need to make sure you're not going to be taking a performance hit - sometimes the SQL engine doesn't do the best with this sort of formulation (IIRC.)
But this'll get you where you need to go. If the value is filled out, it'll filter down on the value; if it's not filled out, the condition will always be true.

Related

Yii2 translating findBySql query to QueryBuilder query

I have the following query using findbysql:
$query = Users::findBySql('select a.user_id, a.last_name,a.first_name, a.emp_id, ar.role_id from auth_users a, auth_user_roles AR, AUTH_USER_DEPTS AD, DEPARTMENTS D
where AD.DEPT_ID = D.DEPT_ID AND AR.USER_ID = AD.USER_ID and a.user_id = ar.user_id
AND D.DEPT_GROUP_ID = :dept_group_id AND (ACCESS_END_DATE > SYSDATE OR ACCESS_END_DATE IS NULL)
UNION
SELECT DISTINCT a.user_id, a.last_name, a.first_name, a.emp_id, NULL AS role_id FROM auth_users a, AUTH_USER_ROLES AR, AUTH_USER_DEPTS AD, DEPARTMENTS D
WHERE AD.DEPT_ID = D.DEPT_ID AND AR.USER_ID = AD.USER_ID and a.user_id = ar.user_id
AND D.DEPT_GROUP_ID = :dept_group_id AND
AR.ACCESS_END_DATE < SYSDATE AND AR.USER_ID NOT IN (select USER_ID from auth_user_roles where ACCESS_END_DATE > SYSDATE OR ACCESS_END_DATE IS NULL)', [':dept_group_id' => $dept_group_id ]);
This query does exactly what I want it to, but the problem is when I try to put it into a gridview it does not sort. According to Sort and search column when I'm querying with findbysql in yii2 it seems like I need to use query builder instead.
So I was trying to do that with the first part of my query (before the union), and it looks like so:
$query1 = (new \yii\db\Query())
->select(['user_id', 'last_name', 'first_name', 'emp_id'])
->from('AUTH_USERS');
$query2 = (new \yii\db\Query())
->select('USER_ID')
->from('AUTH_USER_ROLES')
->where('ACCESS_END_DATE>SYSDATE OR ACCESS_END_DATE IS NULL');
$query = $query1->innerJoin('AUTH_USER_DEPTS', 'AUTH_USER_DEPTS.user_id = AUTH_USERS.user_id')->innerJoin('DEPARTMENTS', 'AUTH_USER_DEPTS.dept_id = DEPARTMENTS.dept_id');
$query->innerJoin('AUTH_USER_ROLES', 'AUTH_USER_ROLES.USER_ID = auth_users.USER_ID')->where('ACCESS_END_DATE>SYSDATE OR ACCESS_END_DATE IS NULL');
However, my query comes out like this in yii and apparently oracle is not accepting the double quotes around the column names:
SELECT "user_id", "last_name", "first_name", "emp_id" FROM "AUTH_USERS"
INNER JOIN "AUTH_USER_DEPTS" ON AUTH_USER_DEPTS.user_id = AUTH_USERS.user_id
INNER JOIN "DEPARTMENTS" ON AUTH_USER_DEPTS.dept_id = DEPARTMENTS.dept_id
INNER JOIN "AUTH_USER_ROLES" ON AUTH_USER_ROLES.USER_ID = auth_users.USER_ID
WHERE ACCESS_END_DATE>SYSDATE OR ACCESS_END_DATE IS NULL
I know the query might be incorrect here already but I cant even get the double quotes to go away. Tried defining the select statements multiple ways suggested by the yii docs already with no success:
select(['user_id', 'last_name', 'first_name', 'emp_id'])
select('user_id', 'last_name', 'first_name', 'emp_id')
select("user_id, last_name,first_name,emp_id")
I have also tried joining the queries like this from the docs: http://www.yiiframework.com/doc-2.0/guide-db-query-builder.html
$query = $query1->innerJoin(['u' => $query2], 'u.user_id = user_id');
but it also complains that it doesnèt recognize u and the query instead comes out like so in yii:
SELECT COUNT(*) FROM "AUTH_USERS" INNER JOIN "AUTH_USER_DEPTS" ON AUTH_USER_DEPTS.user_id = AUTH_USERS.user_id INNER JOIN "DEPARTMENTS" ON AUTH_USER_DEPTS.dept_id = DEPARTMENTS.dept_id INNER JOIN (SELECT "USER_ID" FROM "AUTH_USER_ROLES" WHERE ACCESS_END_DATE>SYSDATE OR ACCESS_END_DATE IS NULL) "u" ON u.user_id = auth_users.user_id
At this point im just looking for the easiest way to build this query (whether it be using querybuilder or some other way) so that I can pass the query to my gridview and sort it.
I would recommend you first create all the data models you need from the tables you need for the query, using Gii it should be easy and it even creates the relationships you will need.
After that, you can do something like the following:
$query = Users::find()
->joinWith('theRelation1Name')
->joinWith('theRelation2Name')
->joinWith('theRelation3Name')
...
This way you don't need to give tables aliases or add the conditions needed for the relations to work.

ORDER BY COUNT with condition

I have two tables, users and clients_closed_1.
I need to order the result by the count of the rows on the table client_closed_1 where meeting=1.
I did it for time_closed field but that was easy because there was no condition.
It's a part of a search code so I'll show you all of it.
With this code I manage to order it by meeting - but users who has no rows with meeting=1 isn't pull out from the database and I need them to show even if they doesn't have meetings.
if (project_type($_GET['project']) == 1) {
$table = 'clients_closed_1';
} else {
$table = 'clients_closed_2';
}
$s_query = "SELECT *,COUNT(time_closed) as numc FROM `".$table."` FULL JOIN `users` ON users.ID=user_c WHERE 1=1";
if (isset($_POST['search'])) {
if ($_POST['tm'] == 'da') {
$dd = strtotime($_POST['y']."-".$_POST['m']."-".$_POST['d']);
$s_query = $s_query." && DATE(FROM_UNIXTIME(time_closed)) = DATE(FROM_UNIXTIME(".$dd."))";
}
elseif ($_POST['tm'] == 'mon') {
$s_query = $s_query." && YEAR(FROM_UNIXTIME(time_closed))=".$_POST['y']." && MONTH(FROM_UNIXTIME(time_closed))=".$_POST['m'];
}
if (!empty($_POST['search_name'])) {
$s_query = $s_query." && CONCAT(p_name,' ',l_name) LIKE '%".$_POST['search_name']."%'";
}
if (!empty($_POST['level'])) {
$query = "&& (level=3 && project IN (SELECT `project` FROM `project` WHERE type='2')) || level=4";
}
} else {
$s_query = $s_query." && YEAR(FROM_UNIXTIME(time_closed))=YEAR(NOW()) && MONTH(FROM_UNIXTIME(time_closed))=MONTH(NOW())";
}
if (isset($_GET['order'])) {
if ($_GET['order'] == 'closing') {
$s_query = $s_query." GROUP BY users.ID ORDER BY numc DESC";
}
elseif ($_GET['order'] == 'meeting') {
$s_query = $s_query." && meeting='1' GROUP BY users.ID ORDER BY numd DESC";
}
}
$query = $db->query($s_query);
If you need any more code/doedn't understand something comment please and I'll fix it.
Thank you.
EDIT:
example of $s_query:
SELECT *,COUNT(time_closed) as numc, COUNT(meeting) as numd FROM `clients_closed_1`
FULL JOIN `users` ON users.ID=user_c WHERE 1=1 &&
YEAR(FROM_UNIXTIME(time_closed))=YEAR(NOW()) &&
MONTH(FROM_UNIXTIME(time_closed))=MONTH(NOW())
GROUP BY users.ID ORDER BY numc DESC
Im not sure I understand 100% of the criteria youre looking for but here is a rough draft of the query:
SELECT c.id, c.meeting, temp1.time_closed_count, temp2.meeting_count, temp3.status_count
FROM `clients_closed_1` c
FULL JOIN `users` u
ON c.user_c=u.ID
LEFT JOIN (SELECT time_closed, count(time_closed) time_closed_count FROM clients_closed_1 GROUP BY time_closed) temp1
ON c.time_closed = temp1.time_closed
LEFT JOIN (SELECT meeting, count(meeting) meeting_count FROM clients_closed_1 GROUP BY meeting) temp2
ON c.meeting = temp2.meeting
LEFT JOIN (SELECT status, count(status) status_count FROM clients_closed_1 GROUP BY status) temp3
ON c.status = temp3.status
WHERE 1=1
AND YEAR(FROM_UNIXTIME(time_closed))=YEAR(NOW())
AND MONTH(FROM_UNIXTIME(time_closed))=MONTH(NOW())
ORDER BY {$order_criteria} DESC
Whats happeneing here is, we are doing the count of all distinct meeting values in a subquery and joining it to the original query based on the value of "meeting" for each row.
This gives us the total "meetings" grouped by distinct meeting values, without cutting out rows. Such is the same for the other 2 subqueries.
This also cleans things up a bit and allows us to just insert the $order_criteria, where that could be time_closed count, meeting_count, or status_count. Just set a default (id) in case your user does not choose anything :)
Edit: Id also recommend trying to get out of the SELECT * habit. Specify the columns you need and your output will be much nicer. Its also far more efficient when you start dealing with larger tables.
After I wrote a really long query to do this I found the perfect soulution.
SELECT SUM(IF(meeting='1' && user_c=users.ID, 1,0)) as meeting_count FROM clients_closed_1 JOIN users
This query return as meeting_count the number of meeting which their value is '1'.
I didn't know I can do such thing until now, so I shared it here. I guess it can be helpull in the future.

Inner/Left join with two different where clauses

i'm in the process of joining two tables together under two different conditions. For primary example, lets say I have the following nested query:
$Query = $DB->prepare("SELECT ID, Name FROM modifications
WHERE TYPE =1 & WFAbility = '0'");
$Query->execute();
$Query->bind_result($Mod_ID,$Mod_Name);
and this query:
$Query= $DB->prepare("SELECT `ModID` from `wfabilities` WHERE `WFID`=?");
$Query->bind_param();
$Query->execute();
$Query->bind_result();
while ($Query->fetch()){ }
Basically, I want to select all the elements where type is equal to one and Ability is equal to 0, this is to be selected from the modifications table.
I further need to select all the IDs from wfabilities, but transform them into the names located in modifications where WFID is equal to the results from another query.
Here is my current semi-working code.
$Get_ID = $DB->prepare("SELECT ID FROM warframes WHERE Name=?");
$Get_ID->bind_param('s',$_GET['Frame']);
$Get_ID->execute();
$Get_ID->bind_result($FrameID);
$Get_ID->fetch();
$Get_ID->close();
echo $FrameID;
$WF_Abilties = $DB->prepare("SELECT ModID FROM `wfabilities` WHERE WFID=?");
$WF_Abilties->bind_param('i',$FrameID);
$WF_Abilties->execute();
$WF_Abilties->bind_result($ModID);
$Mod_IDArr = array();
while ($WF_Abilties->fetch()){
$Mod_IDArr[] = $ModID;
}
print_r($Mod_IDArr);
$Ability_Name = array();
foreach ($Mod_IDArr AS $AbilityMods){
$WF_AbName = $DB->prepare("SELECT `Name` FROM `modifications` WHERE ID=?");
$WF_AbName->bind_param('i',$AbilityMods);
$WF_AbName->execute();
$WF_AbName->bind_result($Mod_Name);
$WF_AbName->fetch();
$Ability_Name[] = $Mod_Name;
}
print_r($Ability_Name);
See below:
SELECT ModID,
ID,
Name
FROM modifications M
LEFT JOIN wfabilities WF
ON WF.ModID = M.ID
WHERE TYPE =1 & WFAbility = '0'
To do this, you need to join your tables, I'm not quite sure what you are trying to do so you might have to give me more info, but here is my guess.
SELECT ID, Name, ModID
FROM modifications
JOIN wfabilities
ON WFID = ID
WHERE TYPE = '1'
AND WFAbility = '0'
In this version I am connecting the tables when WFID is equal if ID. You will have to tell me exactly what is supposed to be hooking to what in your requirements.
To learn more about joins and what they do, check this page out: MySQL Join
Edit:
After looking at your larger structure, I can see that you can do this:
SELECT modifications.Name FROM modifications
JOIN wfabilities on wfabilities.ModID = modifications.ID
JOIN warframes on warframes.ID = wfabilities.WFID
WHERE warframes.Name = 'the name you want'
This query will get you an array of the ability_names from the warframes name.
This is the query:
"SELECT A.ID, A.Name,B.ModID,C.Name
FROM modifications as A
LEFT JOIN wfabilities as B ON A.ID = B.WFID
LEFT JOIN warframes as C ON C.ID = B.WFID
WHERE A.TYPE =1 AND A.WFAbility = '0' AND C.Name = ?"

MySQL error code needed

MySQL always sends a "default response" even when the data doesn't match the query!
How can I design my query to get an error code from MySQL if no data is matching the query?
I need an error code from MySQL to create a 404 or 410 page.
SELECT
place.ID AS id,
place.latitude AS lat,
place.longitude AS lng,
IF(
place.translationID IS NULL,
place.name,
placel10n.text
) AS cityname,
IF(
admcode.translationID IS NULL,
'',
statel10n.text
) AS statename,
IF (
countrycode.translationID IS NULL,
countrycode.name,
countryl10n.Text
) AS countryname,
IF(
place.textID IS NULL,
'',
`l10n-strings`.text
) AS description
FROM
places AS place
LEFT JOIN `l10n-strings` AS placel10n ON (place.translationID = placel10n.translationID AND placel10n.languageCode = 'de')
LEFT JOIN admin1codesascii AS admcode ON (place.admin1 = admcode.statecode AND place.country = admcode.country)
LEFT JOIN `l10n-strings` AS statel10n ON (admcode.translationID = statel10n.translationID AND statel10n.languageCode = 'de')
LEFT JOIN countries AS countrycode ON (place.country = countrycode.iso_alpha2)
LEFT JOIN `l10n-strings` AS countryl10n ON (countrycode.translationID = countryl10n.TranslationID AND countryl10n.LanguageCode = 'de')
LEFT JOIN texts ON (place.textID = texts.id)
LEFT JOIN `l10n-strings` ON (texts.translationID = `l10n-strings`.translationID AND `l10n-strings`.languageCode= 'de')
WHERE
place.id = '8'
AND
place.featurecode = 'A'
OR
place.featurecode = 'AB'
OR
place.featurecode = 'AAC'
LIMIT 0,1
This query always returns a result even when the where statements are wrong.
Yes, that's the way SQL works. If there are no rows that match your query, the result set will be empty. It's the job of the program (written in PHP or what have you) in which your SQL query is embedded to respond to the "no rows" case and change the response status (to 404 or what have you). SQL queries are not responsible for HTTP status codes -- it's chalk and cheese.
In MySQL, AND has higher operator precedence than OR, so the conditions in your WHERE clause will be interpreted as
(place.id = '8' AND place.featurecode = 'A') OR
place.featurecode = 'AB' OR
place.featurecode = 'AAC'
Use parentheses to be explicit about the order of operation you intend. Perhaps you meant this?
place.id = '8' AND
(place.featurecode = 'A' OR
place.featurecode = 'AB' OR
place.featurecode = 'AAC')

Best way to show default values for empty fields returned from a database query?

Is there a better way to write this code?
I want to show a default value ('No data') for any empty fields returned by the query:
$archivalie_id = $_GET['archivalie_id'];
$query = "SELECT
a.*,
ip.description AS internal_project,
o.description AS origin,
to_char(ad.origin_date,'YYYY') AS origin_date
FROM archivalie AS a
LEFT JOIN archivalie_dating AS ad ON a.id = ad.archivalie_id
LEFT JOIN internal_project AS ip ON a.internal_project_id = ip.id
LEFT JOIN origin AS o ON a.origin_id = o.id
WHERE a.id = $archivalie_id";
$result = pg_query($db, $query);
while ($row = pg_fetch_object($result))
{
$no_data = '<span class="no-data">No data</span>';
$internal_project = ($row->internal_project != '') ? $row->internal_project : $no_data;
$incoming_date = ($row->incoming_date != '') ? $row->incoming_date : $no_data;
$origin = ($row->origin != '') ? $row->origin : $no_data;
}
You could use a small helper function
function dbValue($value, $default=null)
{
if ($default===null) {
$default='<span class="no-data">No data</span>';
}
if (!empty($value)) {
return $value;
} else {
return $default;
}
}
If this is not just example code then you surely want to sanitize this query by writing...
$archivalie_id = pg_escape_string($_GET['archivalie_id']);
or you want to convert $archivalie_id with intval() if it is clearly always an integer.
furthermore I suggest to replace 'No data' with a constant like '_MYPROJECT_NODATA' so you can easily change the way your no data label looks or implement internationalisation.
You would then use
define('_MYPROJECT_NODATA', '<span class="no-data">No data</span>');
One approach would be to select the default right on the database server.
SELECT
IFNULL(NULLIF(a.field1, ''), 'No data') AS field1,
IFNULL(NULLIF(a.field2, ''), 'No data') AS field2,
IFNULL(NULLIF(ip.description, ''), 'No data') AS internal_project,
IFNULL(NULLIF(o.description, ''), 'No data') AS origin,
to_char(ad.origin_date,'YYYY') AS origin_date
FROM
archivalie AS a
LEFT JOIN archivalie_dating AS ad ON a.id = ad.archivalie_id
LEFT JOIN internal_project AS ip ON a.internal_project_id = ip.id
LEFT JOIN origin AS o ON a.origin_id = o.id
WHERE
a.id = $archivalie_id
This way you can output values right away, and you do not have to touch existing code. The IFNULL(NULLIF()) turns empty strings to NULL, and NULL to 'No data'. If you want to leave empty strings alone, use the IFNULL() only.
From an architectural perspective, this may lack some elegance (depending on how you look at it), but it is effective.
You can use standard SQL COALESCE function to return a special string instead of null, like this:
$query = "SELECT
a.*,
COALESCE(ip.description,'NO_DATA') AS internal_project,
COALESCE(o.description,'NO_DATA') AS origin,
COALESCE(to_char(ad.origin_date,'YYYY'),'NO_DATA') AS origin_date
Then you could replace 'NO_DATA' by the appropriate HTML in your program as others have suggested.

Categories