mysql - multiple select with UNION ALL and count() in the same query - php

I have a DB-table formed like this:
ID | pairname | timeframe | value1 | value2 | value3
I have about 290 pairnames and 4 timeframes and for each timeframe I will have 250 rows.
Table looks like this:
1 | appusd | 1h | 12319 | 129312 | 11111 | 11111
2 | appusd | 1h | 12444 | 123912 | 11221 | 111231
3 | appusd | 4h | 12312 | 123123 | 11111 | 12321
4 | gttusd | 1h | 12342 | 123123 | 11111 | 12321
5 | gttusd | 4h | 12342 | 123123 | 11111 | 12321
I run a PHP script, that will first fill the DB with values and then check every hour if values are on current.
In PHP I loop first to get all pairnames I need from an other table. Inside of this loop, I loop through the 4 different timeframes to create this SELECT:
(
SELECT value1, timeframe
FROM db1
WHERE pairname = 'blabla'
AND timeframe = '1h'
ORDER BY value1 DESC
LIMIT 2
)
UNION ALL (
SELECT value1, timeframe
FROM db1
WHERE pairname = 'blabla'
AND timeframe = '4h'
ORDER BY value1 DESC
LIMIT 2
)
UNION ALL (
SELECT value1, timeframe
FROM db1
WHERE pairname = 'blabla'
AND timeframe = '8h'
ORDER BY value1 DESC
LIMIT 2
)
UNION ALL (
SELECT value1, timeframe
FROM db1
WHERE pairname = 'blabla'
AND timeframe = '2h'
ORDER BY value1 DESC
LIMIT 2
)
it works nicely. But because I have 290 queries out of the loop, it takes some time. (about 9 secs with all the other script part, that's ok.)
Now I do not only want the last 2 entries of each pairname/timeframe but also to count how many entries I have, I need to count each row of each pairname/timeframe.
(
SELECT COUNT( * ) AS rawnr
FROM db1
WHERE pairname = 'blabla'
AND timeframe = '1h'
)
UNION ALL (
...
)
Now I have another 290 queries. and script-time doubles. It's getting slow. Is it possible to count() and select at the same time, if I have this UNION ALL inside of my SELECT?
I've tried it, and it does not work, maybe because of the "limit 2" inside of the same select? I also tried to UNION ALL the count() into a new select, and this did not work either, because UNION wants the same amount of columns.
My solution works with about 17 seconds script-time. After this, I will have to check each result if it's up to date, pull new data from another server and then also delete/add new entries. This will definitely exceed my script-time, and I will have to find a solution for refreshing the script after certain amount of pairname/timeframe editing. (if you know how to reload a php-script at a certain point, that would be also fine!)
EDIT to see the other tables and the script:
The pairname is combined out of 2 names. Name one could be "app" and second "usd".
1st table with names formed like this:
nameID | name | active | value1 | value2 | value3
2nd table contains which names can be paired.
pairID | nameID1 | nameID2 | active | somevalue
I check first which "names" are active and then which pairs are "active". After this I combine the names to get: "appusd". This pairname I use in the third table, there I save all the data to this pairnames.
The reason I use "pairname" as a own raw is, that the data I fill in comes from an API that wants exactly this "pairname", so I can handle data better later on.
The thing is, that I do not want all names, and not all pairname-combinations.
All this has a script-time of 0,7 sec. So it's fine.
$tradeagainst = array("ABC","USD");
$timeframearr = array("1h","2h","4h","8h");
$tradeagainstsql = "";
$namepairarr = array();
$insert = "";
$ticker = array();
$tabelle = "names";
$tabelle2 = "namepairs";
$sqlsc = "SELECT NameID, shortname, nametyp
FROM ".$tabelle."
ORDER BY shortname ASC";
$resultsc = mysqli_query($conn, $sqlsc);
$nameshortnamearr = array();
$nameIDarr = array();
while($rowc = mysqli_fetch_assoc($resultsc)){
$nameshortnamearr []= $rowc["shortname"];
$nameIDarr []= $rowc["NameID"];
if(in_array($rowc["shortname"],$tradeagainst)){
if($tradeagainstsql == ""){
$tradeagainstsql .= " AND (".$tabelle2.".tradenameID = '".$rowc["NameID"]."'";
}else{
$tradeagainstsql .= " OR ".$tabelle2.".tradenameID = '".$rowc["NameID"]."'";
}
}
}
if($tradeagainstsql != ""){
$tradeagainstsql .= ") ";
}
$sqls = "SELECT ".$tabelle.".*, GROUP_CONCAT(".$tabelle2.".tradenameID) as trID
FROM ".$tabelle."
LEFT JOIN ".$tabelle2." ON ".$tabelle.".NameID = ".$tabelle2.".NameID
WHERE ".$tabelle.".active != '1'
AND ".$tabelle2.".active != '1'
".$tradeagainstsql."
GROUP BY ".$tabelle.".NameID
ORDER BY ".$tabelle.".shortname";
$results = mysqli_query($conn, $sqls);
if (mysqli_num_rows($results) > 0) {
while($row = mysqli_fetch_assoc($results)) {
$nameID = $row["NameID"];
$shortname = $row["shortname"];
$active = $row["active"];
$nametyp = $row["nametyp"];
$namepairsarr = explode(",",$row["trID"]);
foreach($namepairsarr as $trnameID){
$ct++;
$namepair = $shortname.$nameshortnamearr[array_search($trnameID, $nameIDarr)];
$namepairarr []= $namepair;
}
}
}
this first part I do, to get the $namepairarr. It is also dynamically, to be able to set up, the $tradeagainst, $timeframearr;
The second part is this one:
$tabelle3 = "namepairdata";
$updatearray = array();
foreach($namepairarr as $namepair){
$sqlnp = "";
$sqlnpcount = "";
foreach($timeframearr as $timeframe){
if($sqlnp == ""){
$sqlnp .= "(SELECT value1, timeframe
FROM ".$tabelle3."
WHERE pair = '".$namepair."' AND timeframe = '".$timeframe."' ORDER BY value1 DESC LIMIT 2)";
$sqlnpcount .= "(SELECT count(*) AS rawcount
FROM ".$tabelle3."
WHERE pair = '".$namepair."' AND timeframe = '".$timeframe."')";
}else{
$sqlnp .= " UNION ALL (SELECT value1, timeframe
FROM ".$tabelle3."
WHERE pair = '".$namepair."' AND timeframe = '".$timeframe."' ORDER BY value1 DESC LIMIT 2)";
$sqlnpcount .= " UNION ALL (SELECT count(*) AS rawcount
FROM ".$tabelle3."
WHERE pair = '".$namepair."' AND timeframe = '".$timeframe."')";
}
}
$resultnp = mysqli_query($conn, $sqlnp);
$resultnpc = mysqli_query($conn, $sqlnpcount);
$rowsvalue1 = array();
$rowstimeframe = array();
$rowscount = array();
while($rowkl = mysqli_fetch_assoc($resultnp)){
$rowsvalue1 []= $rowkl['value1'];
$rowstimeframe []= $rowkl['timeframe'];
}
while($rowklc = mysqli_fetch_assoc($resultnpc)){
$rowscount []= $rowklc['rawcount'];
}
$rc = 0;
foreach($timeframearr as $timeframe){
$value11 = "";
$value12 = "";
$timeframecount = $rowscount[$rc]; $rc++;
for($ints = 0; $ints < count($rowstimeframe); $ints++){
if($timeframe == $rowstimeframe[$ints]){
if($value11 == ""){
$value11 = $rowsvalue1[$ints];
}else{
$value12 = $rowsvalue1[$ints];
}
}
}
$updatearray []= array($namepair,$timeframe,$value11,$value12,$timeframecount);
}
Finally I get an array $updatearray, which I will have to do something with.

Related

Fetch current and next row and compare in a loop

I would like to compare two values from a table:
This is my table:
ID;VALUE
1;700
2;600
3;800
4;900
This is my query:
$stmt = $db->query("SELECT ID, VALUE FROM TABLE);
Now i like to compare the result of the current row with the next row. In mysql it was easy beacause i have to set the row number. I did not find any solution with PDO.
This is my code yet:
while($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$id = $row["ID"];
$val_current = $row["VALUE"];
$row_next = $stmt->fetch(PDO::FETCH_ASSOC);
$val_next = $row["VALUE"];
if ($val_current > $val_next){
echo "$id is greate!";
}else{
echo "$id is less!";
}
}
The Result is:
1 is greater
3 is less
He missed to check the ID 2 agains ID 3 becaue i fetch the next element in the while loop. So i fetch it to get the next value and i fetch again in the loop. Can i reposition the cursor at the end of the loop?
If you are running MySQL 8.0, this is straight-forward with window function lead():
select
t.*,
(value - lead(value) over(order by id) > 0) is_greater
from mytable t
This gives you a boolean flag called is_greater, with following possible values:
1: this value is greater than the next one
0: this value is smaller than the next one
null: there is no next value
Demo on DB Fiddle:
ID | VALUE | is_greater
-: | ----: | ---------:
1 | 700 | 1
2 | 600 | 0
3 | 800 | 0
4 | 900 | null
In earlier versions, one option is to use a correlated subquery:
select
t.*,
(value - (
select t1.value
from mytable t1
where t1.id > t.id
order by t1.id limit 1
) > 0) is_greater
from mytable t
You just need to remember the previous row and use it in the next iteration pretending it's the current one, whereas the current row will serve as the next one.
$row = null;
while($row_next = $stmt->fetch(PDO::FETCH_ASSOC)) {
if ($row !== null)) {
$id = $row["ID"];
$val_current = $row["VALUE"];
$val_next = $row_next["VALUE"];
if ($val_current > $val_next){
echo "$id is greater!";
}else{
echo "$id is less!";
}
echo "\n";
}
$row = $row_next;
}

MySQL using AND in a single column [duplicate]

This question already has answers here:
How to return rows that have the same column values in MySql
(3 answers)
Closed 5 years ago.
I'm working on a tag system where an array (of tags) is queried and all rows with the same tags in the Tag column are selected.
The issue is that I used the IN condition which is a type of an OR function, which selected all the rows with the same tags as opposed to narrowing them down, for example.
Instead of narrowing down an image with tags like 'sun' and 'landscape' it would select all images with those tags.
What I'm looking for is an AND version of IN () or a substitute that can work with arrays.
This is just an example. In reality, the user can add as many tags as they want
+----+---------+---------+
| ID | ImageID | Tag |
+----+---------+---------+
| 1 | 2 | sun |
+----+---------+---------+
| 2 | 12 |landscape|
+----+---------+---------+
| 3 | 15 | field |
+----+---------+---------+
| 4 | 15 |landscape|
+----+---------+---------+
My code
$tag = $_POST['tag'];
$tag = preg_split("#/#", $tag);
$tag = implode("', '", $tag);
$query = "SELECT * FROM ComicStripTags WHERE `Tag` IN ('$tag')";
$result = mysqli_query($link, $query);
while ($row = mysqli_fetch_array($result)){
$ID[] = $row['ImageID'];
}
Thanks
P.S. I'm not working in SQL, im working in PHP
You can do it using inner joins:
select * from TAGS a
inner join TAGS b
on a.ImageID = b.ImageID
where a.tag = 'field'
and b.tag = 'landscape'
According to your PHP code:
$joins = [];
$conditions = [];
foreach (preg_split("#/#", $_POST['tag']) as $index => $tag) {
$alias = "ComicStripTags_$index";
$joins[] = "ComicStripTags AS $alias" . ($index > 0 ? " ON ComicStripTags_0.ImageID = $alias.ImageID" : '');
$conditions[] = "$alias.Tag = '$tag'";
}
$query = sprintf("SELECT * FROM %s WHERE %s", implode(' INNER JOIN ', $joins), implode(' AND ', $conditions));
Assuming each ImageID can have only one of each tag, you can group by ImageID and select only rows where the count of tags equals the size of the input array.
SELECT ImageID FROM ComicStripTags
WHERE Tag IN ('landscape', 'field')
GROUP BY ImageID HAVING COUNT(Tag) = 2;
Alternatively you can do some ranking which would support partial tag matches as well:
SELECT
ImageID,
group_concat(Tag) as 'matched_tags',
count(*) as 'tag_matches'
FROM TAGS
WHERE Tag IN ( [list_of_tags] )
GROUP BY ImageID
HAVING tag_matches >= [minimum_number_of_tags]
ORDER BY tag_matches DESC

looping through record set an updating individual records with calculated value

I have created a recordset which brings up 3 records. for each record I am trying to update a prorata value. the calculation works but only the first record is updated. I can't see what I'm doing wrong. Can anyone help?
$results8a = mysql_query("SELECT orders.id, orders.install_date, orders_detail.product_name,
orders_detail.connection_fee, orders_detail.rental_fee, orders_detail.prorata_start, orders_detail.prorata_end, orders_detail.id as itemID
FROM orders INNER JOIN orders_detail ON orders.id = orders_detail.order_id
WHERE orders.id = '$order_id' AND rental_fee >'0.00' ");
echo mysql_error();
while ($row = mysql_fetch_array($results8a)) {
$itemID = $row['itemID'];
$itemRental = $row['rental_fee'];
$itemConn = $row['connection_fee'];
$install_date = $row['install_date'];
//start prorata calculations = start date until the end of month
$s_year = date("Y", strtotime($install_date)); // get year number
$s_monthname = date("F'", strtotime($install_date)); // get month name
$s_daytostart = date("d", strtotime($install_date)); // get the day number
$s_month = date("m", strtotime($s_monthname)); // get this months number
$s_daysinmonth = cal_days_in_month(CAL_GREGORIAN, $s_month, $s_year);// get days in this month
$start_daysleft = $s_daysinmonth - $s_daytostart; // days left until the end of the month
// prorata value = (monthlyrental / days in month) * days left
$prorata_startservice = $itemRental / $s_daysinmonth;
$prorata_startservice = $prorata_startservice * $start_daysleft;
// update to fields
mysql_query("UPDATE orders_detail SET prorata_start = ' $prorata_startservice' WHERE id = '$order_id' AND id = '$itemID' ");
}
mysql_free_result($results8a);
You could possibly do the calculations all in one query in mysql - though it is not tested.
UPDATE `orders_detail`
SET `prorata_start` =( ( `rental_fee` / day( last_day(`install_date`) ) ) * ( day( last_day(`install_date`) ) - day( now() ) ) )
where `id`='{$order_id}';
You are not supplying us with enough information. We would need to know how your orders_detail table is structured but I would have to assume with the information you gave us, the table is probably like this:
+----+---------+---------------+-------+-------+
| id | item_id | prorata_start | other | other |
+----+---------+---------------+-------+-------+
| | | | | |
+----+---------+---------------+-------+-------+
With that assumption in mind, your UPDATE query is wrong because you are using WHERE id = '$order_id' AND id = '$itemID' which means that if $order_id = 1 and $item_id = 2 the update will never happen.
You most likely need to change it to something like:
WHERE id = '$order_id' AND item_id = '$itemID'
However, do you really need item_id in your table? If this table tracks orders, you shouldnt need the item_id there, the order ID number should be sufficient, in that case your WHERE should only be:
WHERE id = '$order_id'

Checking users options from default

I have table:
user_id | song_id| points
--------|----------------
2 | 1 | 0
2 | 2 | 1
2 | 3 | 2
2 | 4 | 3
2 | 5 | 4
And I need to check if the user have changed the points value.
Therefore it should be something like:
while ($row = mysql_fetch_array($query)) {
$userID = $row['user_id'];
$songID = $row['song_id'];
$points = $row['points'];
if($songID-$points==1){
echo $userID."<br>";
}
But this will print out every occasion of userID where the song-id - points=1.
I need to print out only these user_id's that have all the values =1 and the username must echo'd only once.
EDIT:
SELECT DISTINCT user_id WHERE (song_id - points) = 1
This is half way there. This echo's user_ids' where the song_id - points = 1, but if the user is reordered (i use jQuery sortable) the list, then there can be some rows that is "song_id - points = 1".
My script must echo only these user_id-s, where users every song_id - points = 1, not only one
SELECT DISTINCT user_id FROM table WHERE (song_id - points) = 1
After edit:
SELECT table.user_id
FROM table
LEFT JOIN (
SELECT user_id, COUNT(*) AS C FROM table) AS T2
ON table.user_id = T2.user_id
WHERE (table.song_id - table.points) = 1
GROUP BY table.user_id
HAVING COUNT(*) = T2.C
You can first filter the users which has modified point values:
SELECT DISTINCT user_id FROM table
WHERE (song_id - points) != 1
Then you can use fetch the users which doesn't fit the above condition:
SELECT DISTINCT user_id FROM table
WHERE user_id NOT IN (
SELECT DISTINCT user_id FROM table
WHERE (song_id - points) != 1
)
According to your last edit this last SQL statement might work.
You can check a working example.
Here is what you're looking for:
select user_id from (
select user_id, if(song_id - points = 1, 0, 1) flag from t
) as S
group by user_id
having sum(flag) = 0
And here is a working example.
In case I didn't understand the requirements this shows all users who don't even have one row in which song_id - points != 1, i.e, all users who have all rows that match song_id - points = 1
Or maybe, if you prefer a different approach that might be more efficient:
select distinct t1.user_id from t t1
where not exists (
select * from t t2
where t2.song_id - t2.points != 1 and t1.user_id = t2.user_id
)
Here is the working example.
Not sure I understand the why of the situation, but a simple control-break structure will achieve the desired result ...
$old_id = '';
$good = false;
while($row = mysql_fetch_array($query)){
//check to see if we have a new user ...
if($row['user_id'] != $old_id){
//check to see if all values were == 1
if($good){
echo $old_id . '<br />';
}
//re-initialize variables
$good = true;
$old_id = $row['user_id'];
}
//if value != 1, we won't print the user ...
if($row['song_id'] - $row['points'] != 1){
$good = false;
}
}
//final end-of-loop condition ...
if($good){
echo $old_id . '<br />';
}
OK, here's a query that's a lot more simple than the join above:
SELECT user_id, sum(song_id) as song_total, sum(points) as point_total, count(*) AS cnt FROM table GROUP BY user_id
If the difference between song_id and points is 1 for every song, then the difference between the totals will equal the number of rows for that user ... so using this query:
while($row = mysql_fetch_array($query)){
if($row['cnt'] == ($row['song_total'] - $row['point_total'])){
echo $row['user_id'] . '<br />';
}
}

Mysql reducing number of queries

I have 3 columns in table1: book, key and value.
| book | key | value |
------------------------------
| 1 | author | a |
| 1 | editor | b |
| 1 | book | c |
Instead of runnuing three queries
$data = mysql_query("
SELECT * FROM table1 WHERE book = '1' AND key = 'author'
") or die(mysql_error());
while($info = mysql_fetch_array( $data ))
{
$value1 = $info['value'];
}
Then repeat this for editor and book.
$value1 $value2 $value3 are inserted in different places on page
Could I do this with one query?
Yes. If there are no other entries with "book = 1" you just query
SELECT * FROM table1 WHERE book = '1'
If there are more entries you can use this query:
SELECT * FROM table1 WHERE book = '1' AND key IN('author','editor','book')
And then create an assoc array:
while($info = mysqli_fetch_assoc( $data ))
{
$value[$info['key']] = $info['value'];
}
...
echo "the book {$value['book']} was written by {$value['author']}";
for create a $value array you have to use this :
$value[] = $info['value'];
instead of this :
$value1 = $info['value'];
Also you can use this code :
$value[]["key"] = $info['key'];
$value[]["value"] = $info['value'];
And for example you can call first row's value with $value[0]["value"]
If you want the values in a single record try this:
SELECT `t1`.`value` AS `value1`, `t2`.`value` AS `value2`, `t3`.`value` AS `value3`
FROM
`table1` AS `t1` CROSS JOIN
`table1` AS `t2` CROSS JOIN
`table1` AS `t3`
WHERE
(`t1`.`book` = 1) AND (`t1`.`key` = 'author')
AND (`t2`.`book` = 1) AND (`t2`.`key` = 'editor')
AND (`t3`.`book` = 1) AND (`t3`.`key` = 'book');

Categories