MySQL - DATEDIFF LOOKUP previous row (same ID) - mysql

I'm trying to create an extra column on my SQL where I could identify if the user_id generated a subsequent (or even third) row.
And if so, calculate the datediff between the connection time and previous row disconnection time.
If no duplicate user_id, the response should be NULL.
Here's a screenshot of my data and notes:
I tried the DATEDIFF formula but no success.
Could someone help me on this? I really would appreciate any input.
SELECT id,
user_id,
connected_at,
disconnected_at,
IFNULL(DateDiff('second', Lookup(disconnected_at), -1), connected_at)
FROM data
ORDER BY id, user_id, connected_at

When comparing values in different rows, you need to join the table to itself to get the second row. Try this:
SELECT
a.id,
a.user_id,
a.connected_at,
a.disconnected_at,
DateDiff('second',b.`connected_at`,a.`disconnected_at`) as `time_diff`
FROM `data` a
JOIN `data` b
ON a.`user_id` = b.`user_id` AND b.`connected_at` > a.`disconnected_at`

Related

MySql: Select value of current and next row

I am trying to learn of a better way in achieving the desired result of a select query - details below, thank you in advance.
MySQL version: 5.7
Table:
id int(11)
product_number int(8)
service_group int (4)
datetime datetime
value int (6)
Indexes on all but value column.
MySql table has the following data:
id,product_number, service_group,datetime,value
1,1234,1,2022-02-10 00:00:00,0
2,1234,1,2022-02-10 00:01:30,25
3,1234,1,2022-02-10 00:02:30,11
4,1234,2,2022-02-10 01:00:30,0
5,1234,2,2022-02-10 01:01:30,65
6,1234,2,2022-02-10 01:02:30,55
In essence, the value for each product within the service group is wrongly recorded, and the correct value for the "current" row is actually recorded against the next row for the product within the same service group - correct output should look like this:
id,product_number, service_group,datetime,value
1,1234,1,2022-02-10 00:00:00,25
2,1234,1,2022-02-10 00:01:30,11
3,1234,1,2022-02-10 00:02:30,0
4,1234,2,2022-02-10 01:00:30,65
5,1234,2,2022-02-10 01:01:30,55
6,1234,2,2022-02-10 01:02:30,0
The below query is what seems to be hugely inefficient way of returning the correct results - what would be a better way to go about this in MySql? Thank you.
Select
a.id,
a.product_number,
a.service_group,
a.datetime,
(
Select b.value FROM products b
Where b.product_number=a.product_number AND b.service_group=a.service_group
AND b.datetime>a.datetime
Order by b.datetime ASC
Limit 1
)
FROM products a```
If there's no skipped id (the number is in sequence) then you could probably use simple select like below
1.
Select
a.id,
a.product_number,
a.service_group,
a.datetime,
(Select b.value FROM products b Where b.id = a.id+1)
FROM products a
Select
a.id,
a.product_number,
a.service_group,
a.datetime,
b.value
FROM products a
INNER JOIN products b ON b.id = a.id+1
Note that both SQL 1 and 2 is assuming your ID is primary key as I see that's an incrementing value
Either way you need to run an explain query so you could analyze which one is the most efficient one
And more importantly I suggest to update it if it's "wrongly recorded", you should put the your service on maintenance mode and do update+fix on the data using query
Edit:
based on your comment "Hi, Gunawan. Thank you for your suggestion. Unfortunately IDs will not be in sequences to support the proposed approach."
You could alter the subquery on (1) a bit to
Select b.value
FROM products b
Where b.id > a.id order by id asc limit 1
so it became
Select
a.id,
a.product_number,
a.service_group,
a.datetime,
(Select b.value FROM products b Where b.id > a.id order by b.id asc limit 1)
FROM products a
I think what you need in THIS case is the Windows LEAD() function and can be found Here for example and clarification
In summary, LEAD() looks at the NEXT possible record for the given column in question, LAG() looks at the prior.
So in this example, I am asking for the LEAD() of the record (the next one in line) and getting the VALUE column of that record. The 1 represents how many records to skip ahead, in this case 1. The last parameter 0 is what should be returned if no such record exists.
The second half of the clause with the ORDER BY clause identifies how you want the records returned, in this case the datetime sequence.
I included both the value and the NEXTVALUE so you could see the results, but can remove the extra column once you see and understand how it works.
But since you have each service group to its own, and dont want to carry-over the value from another, you need the PARTITION clause as well. So I added that as an additional column... both so you can see how the results work with OR without it and the impact of the query you need.
select
t.id,
t.product_number,
t.service_group,
t.datetime,
t.value,
-- lead (t.value, 1, 0)
-- over (order by t.datetime) as NextValue,
-- without the ,1, 0 in the lead call as sample above
-- you can see the NULLs are not getting the value
-- from the next row when the service group changes.
lead (t.value)
over ( PARTITION BY
t.service_group
order by
t.datetime) as NextValuePerServiceGroup
from
Tmp1 t
order by
t.service_group,
t.datetime

SQL query - union on NOW()

I tried making a SQL query and union the result on the current time, but I cannot seem to find a neat way to solve this.
I've tried the following:
SELECT * FROM `accounts`
UNION SELECT NOW()
And Sequel Pro just reports The used SELECT statements have a different number of columns.
The accountstable just has three columns:
ID (INT(32), AUTO_INC)
CREATED (Timestamp)
NAME (VAR_CHAR(28))
I anticipated I'd get a response with four columns: ID, CREATED, NAME, NOW
What do I do wrong?
Union means that the records from the second query will be appended to those retrieved from the first one.
So the two tables must have the same structure for this to work.
For example:
SELECT field1,field2,field3 FROM tableA
UNION
SELECT field1,field2,field3 FROM tableB
What you want to do is
SELECT *, NOW() as now FROM `accounts`
This will retrieve all the records from the accounts table and will add the timestamp to all the rows on a column named "now" (this is just an alias so use whatever you like).
try this
SELECT *,now() as now FROM `accounts`

INSERT ... SELECT Syntax issue when value(s) from two or more different query

I have a table name shop_balance. which has 3 columns (shop_balance_id(INT,PK), shop_balance(DOUBLE), balance_date(DATE)).
For shop_balance(DOUBLE) column I use two sub query.
1.Get last shop balance amount row shop_balance column in shop_balance table.
2.Get purchase amount after one purchase product(s).
and finally I subtract them and get current shop balance
My query is here
INSERT INTO shop_balance
SELECT null,
(
(SELECT shop_balance FROM shop_balance
WHERE
shop_balance_id=(SELECT MAX(shop_balance_id) FROM shop_balance)
)
-
(
SELECT
SUM(pr_pur_cost_price*quantity) AS net FROM product_purchase_item AS i
LEFT JOIN
product_purchases AS p
ON
p.product_purchase_item_id=i.product_purchase_item_id
WHERE
p.insert_operation=$id
GROUP by
p.insert_operation
)
),curdate();
It is clear that the two sub query are different condition and no direct relation them. Above INSERT query is work well. But is it good idea to use many sub query without INSERT ... SELECT Syntax for INSERT one value? If not, how can I convert to INSERT ... SELECT Syntax?
You have to do the calculation the way you do the calculation. I am guessing that a trigger might better meet your needs, but doing the logic in an insert is fine.
The following slightly simplifies your query. It eliminates the double subquery on shop_balance, changes the left join to an inner join (you have a condition on the second table), and eliminates the group by from the second subquery:
INSERT INTO shop_balance
SELECT null,
((SELECT shop_balance
FROM shop_balance
ORDER BY shop_balance_id desc
LIMIT 1
) -
(SELECT SUM(pr_pur_cost_price*quantity) AS net
FROM product_purchase_item i JOIN
product_purchases p
ON p.product_purchase_item_id=i.product_purchase_item_id
WHERE p.insert_operation=$id
)
), curdate();
You should also list the columns in the insert clause, and probably eliminate the first NULL (it would be set to NULL by default). The final curdate() suggests that you might want an automatic column to store the insertion time as well.

How to merge this two query statement into one query statement

1. SELECT * FROM instalmentsdetails WHERE instalmentName='Third Installment'AND studentFeeId='1'
2. select max(`receiptNo`)as `receiptNo` FROM instalmentsdetails
Table instalmentsdetails
instalmentsDetailsId
studentFeeId
receiptNo
instalmentName
amount
dueDate
fineAmt
waivedAmt
scholarShip
grandTotal
status
Little confused .How to merge this two query statement into one query statement
P.S: One statement checks for the condition and the other checks for the max of receiptNo in that table
I want both the values in one query
Is this what you want?
SELECT max(`receiptNo`) as `receiptNo`
FROM instalmentsdetails
WHERE instalmentName='Third Installment' AND studentFeeId='1'
Update: how about this:
SELECT *
FROM instalmentsdetails as inds
INNER JOIN (
SELECT max(`receiptNo`) as `maxreceiptNo`
FROM instalmentsdetails
) as maxt
WHERE inds.instalmentName='Third Installment' AND inds.studentFeeId='1'
This applies the filter to the table, then adds an extra column (the maximum receiptNo)
Assuming the goal is to get:
a list of instalmentsdetails with specific a instalmentName and studentFeeId
global maximum
 
SELECT *, 0 AS receiptNo FROM instalmentsdetails WHERE instalmentName='Third Installment'AND studentFeeId='1'
UNION
select *, max(`receiptNo`) as `receiptNo` FROM instalmentsdetails
Update
Apparently the OP simply wants to consolidate separate query results into a single row. In that case:
SELECT
*,
(SELECT max(`receiptNo`) FROM instalmentsdetails) AS maxReceiptNo
FROM instalmentsdetails WHERE instalmentName='Third Installment'AND studentFeeId='1'
From all the reading, Matt is correct, but maybe I can help explain what he's doing...
The first part of the query (From InstalmentsDetails WHERE YourCondition) will get all records that qualify for the condition.
THEN, by doing a JOIN to the second query (select max( 'receiptNo' ) from... with no where clause ) will ALWAYS return a single row, single column of the maximum receipt without regard to ANY criteria.
This creates an implied Cartesian result. Join everything in the first table with every record in the second. Since there is no explicit join condition, every row will get the same "max()" value as a returned column. And since there will only be one record in the select max() call, you never worry about duplicates.
Now, if you wanted the maximum receipt within the same criteria, you would just copy that same criteria to the select max() query portion.

MySQL INSERT/SELECT subquery syntax

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']
)");