MySQL INSERT/SELECT subquery syntax - mysql

Just can't wrap my head around the proper syntax for this one. Below is my query, with a plain english explanation of my subquery, in the spot where I think I'd want it to execute.
mysql_query("INSERT INTO donations(
tid,
email,
amount,
ogrequest,
total
)
VALUES (
'".esc($p->ipn_data['txn_id'])."',
'".esc($p->ipn_data['pay_email'])."',
".(float)$amount.",
'".esc(http_build_query($_POST))."',
Here I want to select the row with the max date, get the value of the "total" column in that row, and add $amount to that value to form the new "total" for my newly inserted row.
)");
Can anyone help a bro out?

The real answer is you should not be storing the total in a column in this table. It isn't really any useful information. What you should be storing is the current date, and then calculating the total via SUM and GROUP BY. If it's something that you need to access often, then cache the value elsewhere.
Why do you need the total in any of the rows before the last one? It is just wasted data, and it can be easily regenerated from the table.
Why do you want to store the total in this column. What value does this data add to your schema? The important thing to note here is that the total is NOT a property of the individual transaction. The total is a property of an aggregated subset of individual transactions.
Also - make sure you are using DECIMAL and not FLOAT for your monetary column types in MySQL if you aren't. FLOAT values could result in rounding errors depending on what you are doing, which is something there is no reason to risk when money is involved.

I don't have access to a MySQL server to verify what I created, but try this:
INSERT INTO donations
(
tid,
email,
amount,
ogrequest,
total
)
SELECT
'".esc($p->ipn_data['txn_id'])."',
'".esc($p->ipn_data['pay_email'])."',
".(float)$amount.",
'".esc(http_build_query($_POST))."',
total + '".esc($amount)."'
FROM
ORDER BY date DESC
LIMIT 1
Instead of using a direct "INSERT INTO (...) VALUES (...)" I used a "INSERT INTO (...) SELECT ...". The SELECT statement retrieves the row with the highest date (ORDER BY date DESC LIMIT 1), then the total field is accessed and added with the value of $amount.

mysql_query("INSERT INTO donations(
tid,
email,
amount,
ogrequest,
total
)
VALUES (
'".esc($p->ipn_data['txn_id'])."',
'".esc($p->ipn_data['pay_email'])."',
".(float)$amount.",
'".esc(http_build_query($_POST))."',
(select max(total) from donations) + ".(float)$amount."
)");

Your subquery could look like this:
SELECT total
FROM donations
WHERE tid = <x>
ORDER BY date DESC
LIMIT 1
This of course requires that you have a date column in your table. If you run this one (without the outer query you already have), it should come back with a single row, single column result containing the value of latest total for tid = <x>.
If there's not already a row for txn = <x> in the table, then it will obviously return no row at all. When used as a subquery for your INSERT statement, you should probably check for NULL and replace it with a numeric 0 (zero). This is what IFNULL() can do for you.
Combining this and what you already have:
mysql_query("INSERT INTO donations(
tid,
email,
amount,
ogrequest,
total
)
VALUES (
'".esc($p->ipn_data['txn_id'])."',
'".esc($p->ipn_data['pay_email'])."',
".(float)$amount.",
'".esc(http_build_query($_POST))."',
IFNULL(SELECT total
FROM donations
WHERE id = ".esc(p->ipn_data[txn_id']."
ORDER BY date DESC
LIMIT 1),0) + ".esc($p->ipn_data['value']
)");

Related

Find duplicate value in the table via sql

I have a table called stocktransfer
Rows truncated to keep it simple here.
as per the image the problem with the record is that there is duplicated transaction number across two different invoice number which is incorrect to context of how business logic is.
So duplicate transaction is expected as long as it is under the same invoice number.
I wrote this query but it does not help since the duplication is expected.
Select strefno,sttran,STDATE,count(sttran)
From postfrh
Group By sttran,strefno,STDATE
Having Count(sttran) >1
Order By sttran
Can anyone please help with how to write a logic to find duplicated transaction where invoice numbers are different two.
strefno > TransctionNumber
sttran > InvoiceNumber
STDATE > date
SELECT strefno,
sttran,
STDATE,
row_number ( )
OVER ( PARTITION BY strefno
ORDER BY STDATE ) AS `rowNumber`
FROM postfrh
WHERE strefno IN
(SELECT strefno
FROM postfrh
GROUP BY strefno
HAVING count( sttran ) > 1 )
ORDER BY strefno;
You are probably looking for something like this. I don't have the exact table so I cannot be sure.
select a.tnum
from postfrh as a
, postfrh as b
where a.tnum = b.tnum
and b.inum != a.inum
(tnum = transaction number, inum = invoice number)
There are several ways to approach the problem but the above query works by joining two instances of the table, the first condition in the where clause means that there will only be items with the same transaction number, the second statement filters out transactions that have the same transaction number and invoice number.

SQL unexpected return

So basically, I have a simple Database with only one table (it's a test DB).
The table has 4 columns:
ID
Name
OralGrade
WrittenGrade
What I'm trying to do is pretty simple (that's why i'm asking for your help): I want to get the name and average of the student whith the highest average.
What i tried:
SELECT nom, MAX(avg)
FROM (
SELECT nom, (noteOrale + noteEcrit)/2 as avg
FROM etudiant
GROUP BY nom) AS Table;
After trying this query, it returned me the name and an average but the average doesn't correspond to the name.
Can someone give me pointers or explain what went wrong? Thanks
Use order by and limit. No subquery is necessary:
SELECT nom, (noteOrale + noteEcrit)/2 as avg
FROM etudiant
ORDER BY avg DESC
LIMIT 1;
It would appear that no GROUP BY is needed either, because the values are all on one row.
If they are multiple rows, then you need GROUP BY.
I would just use limit for this:
SELECT nom, avg
FROM (
SELECT nom, (noteOrale + noteEcrit)/2 as avg
FROM etudiant
GROUP BY nom
) t
ORDER BY avg DESC
LIMIT 1
mysql allows you to use aggregation without including all non-aggregated columns in the group by clause -- so your query is just returning an arbitrary value for name.
What's wrong is that the value returned for nom is indeterminate. The MAX() aggregate is causing the query to collapse all of the rows, and picks out the the highest value of avg. That part is working.
But the value returned for nom is from some row in the collapsed group, not necessarily the row that has the highest value of avg. The query basically told MySQL to return a value of nom from any row in the group.
Other databases would throw an error with the query, with an error message along the lines of "non-aggregate in SELECT list not in GROUP BY".
A non-standard MySQL-specific extension allows the query to run. (It is possible to get MySQL to follow the standard more closely, like other database, if we include ONLY_FULL_GROUP_BY in sql_mode. With the extension disabled, MySQL would behave like other databases, and return an error.)
That's the reason for the "unexpected" behavior you observe.
(This answers the question you asked... explaining what went wrong.)
Just list the rows where the sum of the two fields is equal to the higest sum of the two fields in all the rows. You don't need to divide by two . That's just scaling.
Select * from table
Where noteOrale + noteEcrit =
(Select Max(noteOrale + noteEcrit)
From table)
if you also want the computed avg in the output, add it to the select clause.
Select nom, noteOrale, noteEcrit,
(noteOrale + noteEcrit)/2 Avg
from table
Where noteOrale + noteEcrit =
(Select Max(noteOrale + noteEcrit)
From table)

Select row based on max date across several columns

For a mysql database
I have a table which includes duplicate rows because of date values in several columns. I am looking to select a single row for each unique customer id based on a max date value evaluated across several date columns
[customer id, startDate, StopDate, modifyDate, buyDate]
For each customer id, i'd like to return the row that has the maximum date either in the startDate, StopDate, modifyDate or buyDate columns ( there are some nulls in the date columns.
editing to include example - but to sure how to create a table here:
*** Edit
Been trying for quite awhile now to create a table here with an example. can't figure out. So posting an image? the desired rows to the returned indicated in red.
Assuming that the values are never NULL, you can use greatest():
select t.*
from table t
where greatest(t.startDate, stopDate, buyDate) =
(select max(greatest(t.startDate, stopDate, buyDate))
from t t2
where t2.customerid = t.customerid
);
Note: this will return multiple rows for a customer if more than one row contains the maximum date.

How do I use MAX() to return the row that has the max value?

I have table orders with fields id, customer_id and amt:
SQL Fiddle
And I want get customer_id with the largest amt and value of this amt.
I made the query:
SELECT customer_id, MAX(amt) FROM orders;
But the result of this query contained an incorrect value of customer_id.
Then I built such the query:
SELECT customer_id, MAX(amt) AS maximum FROM orders GROUP BY customer_id ORDER BY maximum DESC LIMIT 1;
and got the correct result.
But I do not understand why my first query not worked properly. What am I doing wrong?
And is it possible to change my second query to obtain the necessary information to me in a simpler and competent way?
MySQL will allow you to leave GROUP BY off of a query, thus returning the MAX(amt) in the entire table with an arbitrary customer_id. Most other RDBMS require the GROUP BY clause when using an aggregate.
I don't see anything wrong with your 2nd query -- there are other ways to do it, but yours will work fine.
Some versions of SQL give you a warning or error when you select a field, have an aggregate operator like MAX or SUM, and the field you are selecting does not appear in GROUP BY.
You need a more complicated query to fetch the customer_id corresponding to the max amt. Unfortunately SQL is not as naive as you think. Once such way to do this is:
select customer_id from orders where amt = ( select max(amt) from orders);
Although a solution using joins is likely more performant.
To understand why what you were trying to do doesn't make sense, replace MAX with SUM. From the stance of how aggregate operators are interpreted, it's a mere coincidence that MAX returns something that corresponds to an actual row. SUM does not have this property, for instance.
Practically your first query can be seen as if it were GROUP BY-ed into a big single group.
Also, MySQL is free to choose each output value from different source rows from the same group.
http://dev.mysql.com/doc/refman/5.7/en/group-by-extensions.html
MySQL extends the use of GROUP BY so that the select list can refer to
nonaggregated columns not named in the GROUP BY clause.
The server is free to choose any value from each group, so
unless they are the same, the values chosen are indeterminate.
Furthermore, the selection of values from each group cannot be
influenced by adding an ORDER BY clause. Sorting of the result set
occurs after values have been chosen, and ORDER BY does not affect
which values within each group the server chooses.
The problem with MAX() is that it will select the highest value of that specified field, considering the specified field alone. The other values in the same row are not considered or given preference for the result at any degree. MySQL will usually return whatever value is the first row of the GROUP (in this case the GROUP is composed by the entire table sinse no group was specified), dropping the information of the other rows during the agregation.
To solve this, you could do that:
SELECT customer_id, amt FROM orders ORDER BY amt DESC LIMIT 1
It should return you the customer_id and the highest amt while preserving the relation between both, because no agregation was made.

Mysql COUNT, GROUP BY and ORDER BY

This sounds quite simple but I just can't figure it out.
I have a table orders (id, username, telephone_number).
I want to get number of orders from one user by comparing the last 8 numbers in telephone_number.
I tried using SUBSTR(telephone_number, -8), I've searched and experimented a lot, but still I can't get it to work.
Any suggestions?
Untested:
SELECT
COUNT(*) AS cnt,
*
FROM
Orders
GROUP BY
SUBSTR(telephone_number, -8)
ORDER BY
cnt DESC
The idea:
Select COUNT(*) (i.e., number of rows in each GROUPing) and all fields from Orders (*)
GROUP by the last eight digits of telephone_number1
Optionally, ORDER by number of rows in GROUPing descending.
1) If you plan to do this type of query often, some kind of index on the last part of the phone number could be desirable. How this could be best implemented depends on the concrete values stored in the field.
//Memory intensive.
SELECT COUNT(*) FROM `orders` WHERE REGEXP `telephone_number` = '(.*?)12345678'
OR
//The same, but better and quicker.
SELECT COUNT(*) FROM `orders` WHERE `telephone_number` LIKE '%12345678'
You can use the below query to get last 8 characters from a column values.
select right(rtrim(First_Name),8) FROM [ated].[dbo].[Employee]