SQL Fetching single scalar using PDO - php

This question is more on this post. So I want to retrieve the number in a certain column. So let's say the table looks like this:
A | B | C | D | E | F
---------------------
0 | 50| 0 | 0 | 0 | 0
Let's say the user enters in B, I want to go fetch the current number in the table, which is 50. This is my SQL code:
$link = new PDO('**;dbname=**;charset=UTF-8','**','**');
$stmt = $link->prepare("SELECT MAX(:type) as max FROM table");
$stmt->bindParam(':type', $type);
$stmt->execute();
$used = $stmt->fetch(PDO::FETCH_ASSOC);
$used = $used["max"];
But this is just returning B rather than the number. How do I change my code to get the number? Thanks

SELECT MAX(:type) as max FROM table
Assuming :type gets set to "B", this will be executed as:
SELECT MAX("B") as max FROM table
The MAX of something with just one value will be that one value, i.e.:
SELECT "B" as max FROM table
Most likely you want to select the MAX from that column, not the string:
SELECT MAX(B) as max FROM table
Which you can do like this:
$stmt = $link->prepare("SELECT MAX($type) as max FROM table");
Obviously, this is vulnerable to SQL injection if $type comes from a user, but if you're getting a table name from a user, then you're doing something wrong anyway.
It's also possible that your table would make more sense like this:
type | value
------------
A | 0
B | 50
C | 0
And then you could get it with:
SELECT MAX(value) FROM tableName WHERE type = :type

Related

Fetch extra data for large number of IDs

I have a MySQL table containing information about objects. It looks like this:
+--------------+----+--------+-------+
| auto_incr_id | id | type | value |
+--------------+----+--------+-------+
| 1 | 1 | length | 105 |
| 2 | 1 | weight | 17 |
| 3 | 1 | price | 104 |
| 4 | 2 | length | 111 |
| 5 | 2 | weight | 18 |
| 6 | 2 | price | 87 |
+--------------+----+--------+-------+
I want to fetch the first x objects, sorted by length:
{
"id": 2,
"length": 111,
"weight": 18,
"price": 87
},
{
"id": 1,
"length": 105,
"weight": 17,
"price": 104
}
Here's what I do to achieve this:
First, I fetch the ids:
$type = "length";
$stmt = $mysqli->prepare("SELECT id FROM table WHERE type=? ORDER BY value DESC LIMIT ?");
$stmt->bind_param('si', $type, $limit);
$stmt->execute();
$result = $stmt->get_result();
$result = $result->fetch_all(MYSQLI_ASSOC);
Next, I get the other values for each object that was fetched:
$i = 0;
while ($i < count($result)) {
$stmt = $mysqli->prepare("SELECT type, value FROM table WHERE id=?");
$stmt->bind_param('i', $result[$i]['id']);
$stmt->execute();
$result_2 = $stmt->get_result();
$fetch = $result_2->fetch_all(MYSQLI_ASSOC);
$j = 0;
while ($j < count($fetch))
{
$result[$i][$fetch[$j]['type']] = $fetch[$j]['value'];
$j++;
}
}
This works great to fetch for example the top 5 ($limit = 5), but now I have a use case where I want to have this information for the top 10k or more. This take too much time, probably because it needs to go through the while loop and execute a statement for every found ID.
So I'm looking for a way to reduce the execution time getting this data. Any ideas?
Your issue appears to be a lack of understanding about table joins. There is a wrong and a right way to do this. The wrong way is very easy to understand, so I will show it so it makes sense...
select id, type, value from table where id in (select id from table where type='length');
That obviously gets the type/value combination from every id that has a value for type='length'. But, you aren't doing it ALL in the database. You still have to sort and group everything outside the database. Let's assume you only have type='length', 'weight' and 'price'. I can create three tables on the fly for that, creating attributes of length, weight, and price...
select l.id, length, weight, price from
(select id, value as length from table where type='length') l
left outer join
(select id, value as weight from table where type='weight') w
on l.id=w.id
left outer join
(select id, value as price from table where type='price') p
on l.id=p.id
order by length
Now, you will get each attribute in a row, one row per id. The id field is not guaranteed to be unique here. If you have the same ID with more than one length, it will show up more than once in the results. But, I hope you can see how I took the obvious subquery and turned it into a join to do all the work inside the database.
NOTE: I fixed the ambiguity error and added "left outer" to include rows where weight and price do not exist.
Well at least you can always use a subquery
SELECT id, type, value FROM table
WHERE id IN (SELECT id FROM table WHERE type=? ORDER BY value DESC LIMIT ?")
order by id

Improved step down SQL query without many IF/ELSE blocks

Consider a sample table with these rows:
+----+----------+-------+
| id | postcode | value |
+----+----------+-------+
| 1 | A1A3A3 | one |
| 2 | A1A3A4 | two |
| 3 | A1A3B | three |
| 4 | A1A3C | four |
| 5 | A1A3D | five |
| 6 | A1A3 | six |
| 7 | A1A | seven |
| 8 | A1 | eight |
+----+----------+-------+
My goal is to perform a query, whereby it steps down through the postcode column until an exact match is found.
Let's say my starting query parameter is A1A3E9. The expected return value, based on the sample table, would be six. It is important to note, that each step down, I remove one character from the end of the starting query parameter.
So first I would try and find a match for A1A3E9, and then A1A3E, and then A1A3 and so forth.
Currently, I achieve this simply with a series of IF/ELSE blocks, like this:
IF
EXISTS (
SELECT value FROM table
WHERE postcode=:userPost6_1
)
BEGIN
SELECT value FROM table
WHERE postcode=:userPost6_2
END
ELSE IF
EXISTS (
SELECT value FROM table
WHERE postcode=:userPost5_1
)
BEGIN
SELECT value FROM table
WHERE postcode=:userPost5_2
END
ELSE IF
EXISTS (
SELECT value FROM table
WHERE postcode=:userPost4_1
)
BEGIN
SELECT value FROM table
WHERE postcode=:userPost4_2
END
ELSE IF
EXISTS (
SELECT value FROM table
WHERE postcode=:userPost3_1
)
BEGIN
SELECT value FROM table
WHERE postcode=:userPost3_2
END
ELSE IF
EXISTS (
SELECT value FROM table
WHERE postcode=:userPost2_1
)
BEGIN
SELECT value FROM table
WHERE postcode=:userPost2_2
END
Note that I am using parameter binding in PHP, so just for context, my parameter bindings ultimately look like this:
$stmt->bindValue(':userPost6_1', "A1A3E9", PDO::PARAM_STR);
$stmt->bindValue(':userPost6_2', "A1A3E9", PDO::PARAM_STR);
$stmt->bindValue(':userPost5_1', "A1A3E", PDO::PARAM_STR);
$stmt->bindValue(':userPost5_2', "A1A3E", PDO::PARAM_STR);
$stmt->bindValue(':userPost4_1', "A1A3", PDO::PARAM_STR);
$stmt->bindValue(':userPost4_2', "A1A3", PDO::PARAM_STR);
$stmt->bindValue(':userPost3_1', "A1A", PDO::PARAM_STR);
$stmt->bindValue(':userPost3_2', "A1A", PDO::PARAM_STR);
$stmt->bindValue(':userPost2_1', "A1", PDO::PARAM_STR);
$stmt->bindValue(':userPost2_2', "A1", PDO::PARAM_STR);
I do not have have any concerns so far as performance is concerned, as I have an index on the postcode column (which contains 40,000+ rows). My concern is purely that this is visually, an unpleasant query to look at.
My question: Is there a cleaner way to write this query?
Here is one method:
select top (1) t.*
from t
where 'A1A3E9' like t.postcode + '%'
order by t.postcode desc;
The only issue is that your multiple if statements are probably faster. Getting performance is a real challenge with this type of problem. One method uses multiple joins:
select v.pc, coalesce(t0.value, t1.value, t2.value, . . . )
from (values ('A1A3E9')) v(pc) left join
t t0
on t0.postcode = v.pc left join
t t1
on t1.postcode = t0.postcode is null and
(case when len(v.pc) > 1 then left(v.pc, len(v.pc) - 1) end) left join
t t2
on t1.postcode is null and
t2.postcode = (case when len(v.pc) > 2 then left(v.pc, len(v.pc) - 2) end) left join
. . .
I would first spool all the potentially matching rows, eg:
select *
into #matches
from t
where postcode like 'AI%'
This can use an index on postcode and so should be cheap. Then whatever query you run against the matches will just operate over this subset. Even writing a UDF that compares postcode to a literal, eg:
select top 1 *
from #matches
order by dbo.NumberOfMatchingCharacters(postcode,'A1A3E9') desc

sum all row from 2 column and use select to display two column

example
|like |comment|
|user1|asd |
|user2|awe |
|user3|aqw |
|user4|atr |
|user5|axc |
|user6|azw |
|user7| |
i have two column and what i wanted to do is to sum all rows inside those column and fetch the two column but i dont have any idea on how to do it in a query in sql.
result
|like |comment|
| 7 | 6 |
i tried queering it but i can only fetch 1 column i dont know how to do it with two column..
function countpostcomment($puid){
$sql = "SELECT COUNT(pc_comment) FROM pcomment WHERE post_uid = '$puid'";
$stmt = $this->dbh->prepare($sql);
$stmt->execute(array("0"));
$active_data = $stmt->fetchAll(PDO::FETCH_ASSOC);
return $active_data;
}
here is my sample query i haven't tested the whole function only the select cause i dont know if i need to use array or not..
I think you simply want:
SELECT COUNT(*) as likes, COUNT(pc_comment) as comments
FROM pcomment
WHERE post_uid = '$puid';
COUNT(*) counts the number of rows (7 in this case). COUNT(<column name>) counts the number of non-NULL values in the column (6 in this case).

Php , Mysql Select from table where row = ? it is possible?

Hello I want to get select query with send row record number
$row = 3;
SELECT FROM clients WHERE ROW()=$row ORDER BY ID DESC
it is possible ? How can i do that ?
If you want the third row, use offset/limit:
select *
from clients
order by id
offset 2
limit 1;
Note that that offset 0 gets the first record, so offset 2 would be the third record.
You need to use LIMIT instead of WHERE
If you want get a row with N position, you can try this:
SELECT * FROM clients LIMIT N-1,1
So if you want get third row you need to use something like this:
SELECT * FROM clients LIMIT 2,1
This is what i would do.. entry_id is unique and 1 = first row, 2 = second row, etc..
entry_id is set as primary index and auto increase..
entry_id | what | ever | records
1 | a | b | c
2 | a | b | c
3 | b | c | a
4 | a | b | c
5 | a | b | c
$row = 3;
Select * From clients Where entry_id = $row
returns third row, 3, b, c, a

Updating SQL Column

So I have a table with 6 columns, each column corresponds to a certain product type. Each column holds a number that corresponds to the number of times people have chosen that product type.
A | B | C | D | E | F
---------------------
0 | 0 | 0 | 0 | 0 | 0
So if the user picks type A, then I want to update column A's number from 0 to 1. So here's the SQL code I wrote:
$link = new PDO('***;dbname=***;charset=UTF-8','***','***');
$stmt = $link->prepare("UPDATE table SET :column=:num");
$stmt->bindParam(':column', $column);
$stmt->bindParam(':num', $num);
$stmt->execute();
But it's not updating anything at all. So i'm guessing there is something wrong with the SQL code, most likely having to do with the column placeholder :column. Can anyone tell me the right SQL code?
First make sure, $column is in an accepted list of values. Next, you can't bind :column you will have assign it like so:
$stmt = $link->prepare('UPDATE table SET ' . $column .' = :num');
$stmt->bindParam(':num', $num);
$stmt->execute();
If you were going to check for a valid $column I would use
$valid_column = preg_match('/[a-z0-9_]/i, $column);
or a sufficient replace (preg_replace). Though you would likely wrap it in a try/catch and set exceptions to be thrown in your PDO instance to make sure it's even legit.

Categories