Bind the name of a parameter - php

In Doctrine, is it possible to bind the name of a parameter
(contrary to binding the value of a parameter)?
Why I need it
There is a table having 7 boolean columns, one for each day of the week: monday, tuesday, etc. (these correspond to the structure of the calendar entity defined by GTFS, https://gtfs.org/schedule/reference/#calendartxt).
Given a day of the week (say, monday), I want to get all rows, which are available on Mondays, i.e.:
$statement = $this
->getEntityManager()
->getConnection()
->prepare('
SELECT id
FROM calendar
WHERE monday = 1
');
Generally, I want to be able to supply the name of a day in that query, which I can do simply by:
->prepare("
SELECT id
FROM calendar
WHERE $dayName = 1
");
I wonder whether it's possible to use the parameter binding for the names of parameters, i.e. something like
$statement = $this
->getEntityManager()
->getConnection()
->prepare('
SELECT id
FROM calendar
WHERE :dayName = 1
');
$statement->bindValue('dayName', $dayName);
which does not work, see below.
What I tried
#1
WHERE :dayName = 1
which translates to the following SQL query:
SELECT calendar.id
FROM calendar
WHERE 'monday' = 1
and, because that condition is never true, returns an empty set, [].
#2
WHERE `:dayName` = 1
SELECT calendar.id
FROM calendar
WHERE `'monday'` = 1
Column not found: 1054 Unknown column ''monday'' in 'where clause'
#3
WHERE ":dayName" = 1
# no query
Invalid parameter number: number of bound variables does not match number of tokens

This is of course not possible.
Within the statement, placeholders can be used as parameter markers to indicate where data values are to be bound to the query later when you execute it. The parameter markers should not be enclosed within quotes, even if you intend to bind them to string values. Parameter markers can be used only where expressions should appear, not for SQL keywords, identifiers, and so forth.
In short: you can use parameter markers for literals (string, numbers, ..) only
If you can't change your database design I would recommend using another SQL statement with a simple bit check:
prepare("select id, (monday +
tuesday * 2 +
wednesday * 4 +
thursday * 8 +
friday * 16 +
saturday * 32 + sunday *64) as day
from calendar having (day) & (1 << :daynumber)")
Now you can simply check if a service is available on a specific weekday, by binding the daynumber (monday=0, tuesday=1, .. sunday=6).

Related

How can I do an inverse LIKE on SQL on php?

I want to select a value from a database in sql with php. So, the value that I want to select is a part of the value which I compare it with.
There are two tables: one is called event and the other weekly. In the table event, there is a column called name, that is the name of each event. Weekly has also a column called name, and it´s the name of the weekly. So, I want to get the id from a weekly comparing the two names. But the name of the event can have something before and/or after the name. For example:
Weekly name: Monday
Weekly id: 1
Event: NJK Monday 3
Another example:
Weekly name: Tuesday
Weekly id: 2
Event: Tuesday ks
I´ve tried to do it with a LIKE condition, but I didn´t earn the result that I wanted.
$stmt = $conn->prepare("UPDATE db.event SET weeklyid ="
. " (SELECT id FROM db.weekly WHERE %name%"
. " LIKE 'NJK Monday 3') WHERE name = :name;");
$stmt->bindValue('name', $event->name);
$stmt->execute();
So, it´s like an inverse LIKE. The result that I want to get from the code above would be that the select sentence return 1.
You can use CONCAT() to add the % symbols around the attribute like this
SELECT id FROM db.weekly WHERE 'NJK Monday 3' LIKE CONCAT('%', name, '%')
I'm not sure how it works with named placeholders by if you will use ? placeholder you should add % to execute values not to query:
wrong
$stmt = $conn->prepare('SELECT id FROM db.weekly WHERE name LIKE %?%');
$stmt->execute(['NJK Monday 3']);
good
$stmt = $conn->prepare('SELECT id FROM db.weekly WHERE name LIKE ?');
$stmt->execute(['%NJK Monday 3%']);

Using PHP to select rows between dates through variables

I'm trying to fetch all rows where the date column's value is a day in july using the code below:
$july="07";
$query=$conn->prepare("SELECT * FROM table WHERE date BETWEEN '2018-?-01' AND '2018-?-31'");
$query->execute($july,$july);
$row=$query->setFetchMode();
I have also tried like this:
$july="07";
$month_1="'2018-".$july."-01'";
$month_2="'2018-".$july."-31'";
$query=$conn->prepare("SELECT * FROM table WHERE date BETWEEN ? AND ?");
$query->execute(array($month_1,$month_2));
$row=$query->setFetchMode();
1st case, I get the following error:
Fatal error: Uncaught exception 'PDOException' with message 'SQLSTATE[HY093]: Invalid parameter number: number of bound variables does not match number of tokens' in file.php on line 35
I assume I get that error because I cannot have '?' in a query between single brackets.
Second case, I get nothing at all.
Solved it with this:
SELECT * FROM table WHERE YEAR(date)=2018 AND MONTH(date)=?
Query 1 fails because you can't quote a placeholder.
Query 2 fails because the single quotes get escaped by the driver and then the whole escaped string gets wrapped in quotes. Roughly
date BETWEEN '\'2018-07-01\''
There are a few ways you could accomplish this.
Take the first 7 values of your date column and compare that against your string.
$date = '2018-07';
...then execute the query like ...
WHERE substr(date, 1, 7) = ?
You also could use the concat method which keeps the placeholder from being quoted.
$july="07";
$query=$conn->prepare("SELECT * FROM table WHERE date BETWEEN concat('2018-', ?, '-01') AND concat('2018-', ?, '-31')");
$query->execute(array($july, $july));
$row=$query->setFetchMode();
First case error is because you passed two variables to PDO::execute() and its expecting array there.
Suggested Reading...
http://php.net/manual/en/pdostatement.execute.php
Second case .. ummm.. you sure it does nothing?
Select * from mytable where date <= '2018-07-31' and date >= '2018-07-01'
or whatever... this maybe ...
select * from mytable where date between UNIX_TIMESTAMP(STR_TO_DATE('Jul 01 2018 12:00AM', '%M %d %Y %h:%i%p')) and UNIX_TIMESTAMP(STR_TO_DATE('Jul 31 2018 11:59PM', '%M %d %Y %h:%i%p'))
Never use * to return all columns in a table–it’s lazy. You should only extract the data you need. Even if you require every field, your tables will inevitably change. -https://www.sitepoint.com/mysql-mistakes-php-developers/

PDO: Different results with SQLite BETWEEN

I have a SQLite table with dates in unix timestamp format, which I query via PHP PDO. I'd like to mark the rows according to a date range, in my case whether the date falls in the winter semester or the summer semester of the year.
The start and end dates are arbitrary, but in my case I picked
start as March 1st (0301) and end as September 14th (0914), so I could easily check with BETWEEN.
When I run this query in PHP, like so, it returns an incorrect result ("WS" for all rows):
$statement = $db->prepare('SELECT CASE WHEN
(CAST(strftime("%m%d", class_date, "unixepoch") AS DECIMAL)
BETWEEN :start AND :end)
THEN "SS"
ELSE "WS" END
AS semester FROM dates');
$statement->execute(array(
':start' => 301,
':end' => 915
));
But when I embed the start and end dates as numbers, instead of binding them, it works correctly (some rows "WS", some "SS"):
$statement = $db->prepare('SELECT CASE WHEN
(CAST(strftime("%m%d", class_date, "unixepoch") AS DECIMAL)
BETWEEN 301 AND 915)
THEN "SS"
ELSE "WS" END
AS semester FROM dates');
$statement->execute();
What is happening here and how can I fix it? Any help is appreciated.
Documentation of PDOStatement::execute says that it accepts
An array of values with as many elements as there are bound parameters in the SQL statement being executed. All values are treated as PDO::PARAM_STR.
This means that even if you put in 301 it will be treated as "301".
Instead you should use PDOStatement::bindValue. Like this:
$statement->bindValue(':start', 301, PDO::PARAM_INT);
$statement->bindValue(':end', 915, PDO::PARAM_INT);
$statement->execute();

using mysql stored-function with select query

let me explain my purpose first, i have an vehicle booking application where, visitor will add start date and end date of his journey, in the database there is list of drivers with there availability (available_from_date and available_to_date) which is kind of duration during which they are operating, there is an field for exclude_dates for some specific dates when they are not working.
the application needs to find a list of vehicles which are available during the journey dates entered by the user.
for example user enters he want to go from place A to B during 13th sept, 2014 to 17th sept, 2014
then database needs to return a list of taxi which are available during this period and must not have any exclude date within this period.
Now i have stored the exclude_dates in comma separated format in table (i could have created a separate table but then it would take much more time for a query to execute)
I was trying to create a mysql function which would be called within the actual search query and would return true if there is some there is some excluded date present within the duration and false if not.
these are the queries that i have written
SELECT id, exclude_dates
FROM `taxi_route`
WHERE status = 1
AND `to_city` = 'Surat'
AND `from_city` = 'Ahmedabad'
AND `trip_type` = 2
AND `available_from_date` <= '2014-09-13'
AND available_to_date >= '2014-09-17'
AND STR_TO_DATE((SELECT `split`(exclude_dates, ',', 1)),'%d-%m-%Y')
NOT BETWEEN STR_TO_DATE('13-09-2014','%d-%m-%Y')
AND STR_TO_DATE('17-09-2014','%d-%m-%Y')
Split is a function i have created in mysql to separate the dates present in comma format
DELIMITER $$
CREATE FUNCTION split( str VARCHAR(500), delchar VARCHAR(2), x INT )
RETURNS VARCHAR(500)
BEGIN
RETURN SUBSTR(SUBSTRING_INDEX(str, delchar, x),
LENGTH(SUBSTRING_INDEX(str, delchar, x-1))+IF(x > 1, 2, 1));
END$$
DELIMITER ;
this works fine as far as i pass 1 in split(exclude_dates, ',', 1) , but if the exclude_dates have more then one date then this will not work
can someone please suggest or guide, how this can be accomplished.
snapshot of database is here http://i.imgur.com/JaI8MSx.png
Your query is most likely going to take more time to execute than defining a separate table for exclusion dates. It's not a good practice using comma separated list inside a column for searching purposes, this is against normalization rules.
You should define your tables separately, (e.g. taxi, taxi_route, taxi_route_exclusion, route_exclusion) and later add necessary indexes to make your searches more efficient.
Example:
taxi
---------
id
country
***
***
***
taxi_route
-------------------
id
taxi_id
available_from_date
available_to_date
from_city
to_city
route_exclusion
---------------
id
taxi_id
exclusion_date
And also add a relation table between taxi_route and route_exclusion tables to represent many-to-many relationship. Later define foreign keys on taxi_route_route_exclusion table to point taxi_route and route_exclusion tables.
taxi_route_route_exclusion
--------------------------
taxi_route_id
route_eclusion_id
Define foreign keys like:
taxi_route.taxi_id -> taxi.id
taxi_route_route_exclusion.taxi_route_id -> taxi_route.id
taxi_route_route_exclusion.route_exclusion_id -> route_exclusion.id
Define indexes like:
taxi: IX1 (status, trip_type)
taxi_route: IX1(to_city, from_city, available_from_date, available_to_date)
Your final query should look like this:
SELECT tr.id, re.exclusion_date
FROM `taxi_route` tr JOIN `taxi_route_route_exclusion` trre
ON tr.id = trre.taxi_route_id
JOIN `route_exclusion` re
ON re.id = trre.route_exclusion_id
JOIN `taxi` t
ON t.id = tr.id
WHERE
t.status = 1
AND t.trip_type = 2
AND tr.to_city = 'Surat'
AND tr.from_city = 'Ahmedabad'
AND tr.available_from_date <= '2014-09-13'
AND tr.available_to_date >= '2014-09-17'

PHP/MySQL make top 10 out of selection

A user can input it's preferences to find other users.
Now based on that input, I'd like to get the top 10 best matches to the preferences.
What I thought is:
1) Create a select statement that resolves users preferences
if ($stmt = $mysqli->prepare("SELECT sex FROM ledenvoorkeuren WHERE userid = you"))
$stmt->bind_result($ownsex);
2) Create a select statement that checks all users except for yourself
if ($stmt = $mysqli->prepare("SELECT sex FROM ledenvoorkeuren WHERE userid <> you"))
$stmt->bind_result($othersex);
3) Match select statement 1 with select statement 2
while ($stmt->fetch()) {
$match = 0;
if ($ownsex == $othersex) {
$match = $match + 10;
}
// check next preference
4) Start with a variable with value 0, if preference matches -> variable + 10%
Problem is, I can do this for all members, but how can I then select the top 10???
I think I need to do this in the SQL statement, but I have no idea how...
Ofcourse this is one just one preference and a super simple version of my code, but you'll get the idea. There are like 15 preference settings.
// EDIT //
I would also like to see how much the match rating is on screen!
Well, it was a good question from the start so I upvoted it and then wasted about 1 hour to produce the following :)
Data
I have used a DB named test and table named t for our experiment here.
Below you can find a screenshot showing this table's structure (3 int columns, 1 char(1) column) and complete data
As you can see, everything is rather simple - we have a 4 columns, with id serving as primary key, and a few records (rows).
What we want to achieve
We want to be able to select a limited set of rows from this table based upon some complex criteria, involving comparison of several column's values against needed parameters.
Solution
I've decided to create a function for this. SQL statement follows:
use test;
drop function if exists calcMatch;
delimiter //
create function calcMatch (recordId int, neededQty int, neededSex char(1)) returns int
begin
declare selectedQty int;
declare selectedSex char(1);
declare matchValue int;
set matchValue = 0;
select qty, sex into selectedQty, selectedSex from t where id = recordId;
if selectedQty = neededQty then
set matchValue = matchValue + 10;
end if;
if selectedSex = neededSex then
set matchValue = matchValue + 10;
end if;
return matchValue;
end//
delimiter ;
Minor explanation
Function calculates how well one particular record matches the specified set of parameters, returning an int value as a result. The bigger the value - the better the match.
Function accepts 3 parameters:
recordId - id of the record for which we need to calculate the result(match value)
neededQty - needed quantity. if the record's qty matches it, the result will be increased
neededSex - needed sex value, if the record's sex matches it, the result will be increased
Function selects via id specified record from the table, initializes the resulting match value with 0, then makes a comparison of each required columns against needed value. In case of successful comparison the return value is increased by 10.
Live test
So, hopefully this solves your problem. Feel free to use this for your own project, add needed parameters to function and compare them against needed columns in your table.
Cheers!
Use the limit and offset in query:
SELECT sex FROM ledenvoorkeuren WHERE userid = you limit 10 offset 0
This will give the 10 users data of top most.
You can set a limit in your query like this:
SELECT sex FROM ledenvoorkeuren WHERE userid <> yourid AND sex <> yourpreferredsex limit 0, 10
Where the '0' is the offset, and the '10' your limit
More info here
you may try this
SELECT sex FROM ledenvoorkeuren WHERE userid = you limit 0, 10 order by YOUR_PREFERENCE

Categories