How to ignore if second select is null? - mysql

My goal is to calculate the account mutation (Credit - Debit)
This is my current SQL statement:
SELECT
(SELECT sum(Transaction_Value) FROM transactions WHERE Date BETWEEN "2020-11-01" AND "2020-11-30" AND Destination=99005571818061833)
-
(SELECT sum(Transaction_Value) FROM transactions WHERE Date BETWEEN "2020-11-01" AND "2020-11-30" AND Source=99005571818061833)
The expected result is:
Return the account mutation value.
The actual result is:
null
The problem is if the 4th line did not find a row, it will return null regardless if the 2nd line return a value.
For example if we run the SQL statement to this table data:
ID Date Source Destination Transaction_Value
168 2020-11-24 SETORAN TUNAI 99005571818061833 1000000.0000
169 2020-11-24 98993563492155716 98993563492155717 1000.0000
The return value will be null because the SQL statement did not find a row with Source === "99005571818061833"

User conditional aggregation like this:
SELECT sum(CASE WHEN Destination = 99005571818061833 THEN Transaction_Value
WHEN Source = 99005571818061833 THEN -Transaction_Value
ELSE 0
END)
FROM transactions
WHERE Date BETWEEN 2020-11-01' AND '2020-11-30' AND
99005571818061833 IN (Source, Destination)

I am new to SQL. This by no mean effective, if you have an answer with more effective way and an explanation, feel free to answer it. I will mark it as answer.
The answer:
SELECT
(SELECT sum(Transaction_Value) FROM transactions WHERE Date BETWEEN "2020-11-01" AND "2020-11-30" AND Destination=99005571818061833)
-
IFNULL((SELECT sum(Transaction_Value) FROM transactions WHERE Date BETWEEN 2020-11-01 AND 2020-11-30 AND Source=99005571818061833), 0)
The explanation:
It turns out if you substract a number with null, the return value will be null.
For example
SELECT
(SELECT sum(Transaction_Value) FROM transactions WHERE Date BETWEEN "2020-11-01" AND "2020-11-30" AND Destination=99005571818061833)
-
null
The expected result will be null.
So... logically if your 4th line return null, you should change it to 0.

Related

MySQL query to return 1 and 0 based on difference between dates

In MySQL Database I have a table ABC that consists of a column 'LastDate'.
LastDate which has datatype as DATETIME. The default value for this 'NULL'
I need to write a query for the table which would
Return '1' in these cases.
1) If DATEDIFF(CURRENT_TIME,LastDate) is >15 or if DATEDIFF(CURRENT_TIME,LastDate) is
NULL(i.e defaultVal).
return '0' if DATEDIFF(CURRENT_TIME,LastDate) is <15.
I tried to write an SQL query for this but was unable to do it. Please help me write this Query. Thanks in advance.
You can be explicit about your logic:
select t.*,
(case when DATEDIFF(CURRENT_TIME, LastDate) > 15 or
LastDate is null
then 1 else 0
end) as flag
from t;
This can be simplified to:
select t.*,
coalesce(DATEDIFF(CURRENT_TIME, LastDate) <= 15, 1) as flag
from t;

Generating complex sql tables

I currently have an employee logging sql table that has 3 columns
fromState: String,
toState: String,
timestamp: DateTime
fromState is either In or Out. In means employee came in and Out means employee went out. Each row can only transition from In to Out or Out to In.
I'd like to generate a temporary table in sql to keep track during a given hour (hour by hour), how many employees are there in the company. Aka, resulting table has columns HourBucket, NumEmployees.
In non-SQL code I can do this by initializing the numEmployees as 0 and go through the table row by row (sorted by timestamp) and add (employee came in) or subtract (went out) to numEmployees (bucketed by timestamp hour).
I'm clueless as how to do this in SQL. Any clues?
Use a COUNT ... GROUP BY query. Can't see what you're using toState from your description though! Also, assuming you have an employeeID field.
E.g.
SELECT fromState AS 'Status', COUNT(*) AS 'Number'
FROM StaffinBuildingTable
INNER JOIN (SELECT employeeID AS 'empID', MAX(timestamp) AS 'latest' FROM StaffinBuildingTable GROUP BY employeeID) AS LastEntry ON StaffinBuildingTable.employeeID = LastEntry.empID
GROUP BY fromState
The LastEntry subquery will produce a list of employeeIDs limited to the last timestamp for each employee.
The INNER JOIN will limit the main table to just the employeeIDs that match both sides.
The outer GROUP BY produces the count.
SELECT HOUR(SBT.timestamp) AS 'Hour', SBT.fromState AS 'Status', COUNT(*) AS 'Number'
FROM StaffinBuildingTable AS SBT
INNER JOIN (
SELECT SBIJ.employeeID AS 'empID', MAX(timestamp) AS 'latest'
FROM StaffinBuildingTable AS SBIJ
WHERE DATE(SBIJ.timestamp) = CURDATE()
GROUP BY SBIJ.employeeID) AS LastEntry ON SBT.employeeID = LastEntry.empID
GROUP BY SBT.fromState, HOUR(SBT.timestamp)
Replace CURDATE() with whatever date you are interested in.
Note this is non-optimal as it calculates the HOUR twice - once for the data and once for the group.
Again you are using the INNER JOIN to limit the number of returned row, this time to the last timestamp on a given day.
To me your description of the FromState and ToState seem the wrong way round, I'd expect to doing this based on the ToState. But assuming I'm wrong on that the following should point you in the right direction:
First, I create a "Numbers" table containing 24 rows one for each hour of the day:
create table tblHours
(Number int);
insert into tblHours values
(0),(1),(2),(3),(4),(5),(6),(7),
(8),(9),(10),(11),(12),(13),(14),(15),
(16),(17),(18),(19),(20),(21),(22),(23);
Then for each date in your employee logging table, I create a row in another new table to contain your counts:
create table tblDailyHours
(
HourBucket datetime,
NumEmployees int
);
insert into tblDailyHours (HourBucket, NumEmployees)
select distinct
date_add(date(t.timeStamp), interval h.Number HOUR) as HourBucket,
0 as NumEmployees
from
tblEmployeeLogging t
CROSS JOIN tblHours h;
Then I update this table to contain all the relevant counts:
update tblDailyHours h
join
(select
h2.HourBucket,
sum(case when el.fromState = 'In' then 1 else -1 end) as cnt
from
tblDailyHours h2
join tblEmployeeLogging el on
h2.HourBucket >= el.timeStamp
group by h2.HourBucket
) cnt ON
h.HourBucket = cnt.HourBucket
set NumEmployees = cnt.cnt;
You can now retrieve the counts with
select *
from tblDailyHours
order by HourBucket;
The counts give the number on site at each of the times displayed, if you want during the hour in question, we'd need to tweak this a little.
There is a working version of this code (using not very realistic data in the logging table) here: rextester.com/DYOR23344
Original Answer (Based on a single over all count)
If you're happy to search over all rows, and want the current "head count" you can use this:
select
sum(case when t.FromState = 'In' then 1 else -1) as Heads
from
MyTable t
But if you know that there will always be no-one there at midnight, you can add a where clause to prevent it looking at more rows than it needs to:
where
date(t.timestamp) = curdate()
Again, on the assumption that the head count reaches zero at midnight, you can generalise that method to get a headcount at any time as follows:
where
date(t.timestamp) = "CENSUS DATE" AND
t.timestamp <= "CENSUS DATETIME"
Obviously you'd need to replace my quoted strings with code which returned the date and datetime of interest. If the headcount doesn't return to zero at midnight, you can achieve the same by removing the first line of the where clause.

Search record daywise

I am stuck in a query, actually I have a search box which provide start datetime and end datetime, e.g. 2015-09-18 13:00:00 to 2015-09-21 17:00:00
I tried the query
select l.date, l.center_name, l.center_office, l.offline, l.online, l.category,
(select service_name from services_master where service_id = w.services) as servicename,
(select vendor_name from vendor_master where vendor_id = w.vendor) as vendorname
from lease_reports l,wan_entries w
where l.center_id = '7' and
w.id = l.center_id and
l.date between $startdatetime and $endatetime
I am not getting the exact result, result includes all the time between these two dates. I want day wise rows with exact interval time supplied by the user
The exact syntax will depend on what database you are using (see stackoverflow.com/questions/1658340/sql-query-to-group-by-day),
but the logic/pseudocode you are looking for is as follows.
...
AND day(l.date) BETWEEN day($startdatetime) and day($endatetime)
AND time(l.date) BETWEEN time($startdatetime) and time($endatetime)
GROUP BY day(l.date)
However, it might be clearer to the end user if you queried for date and time separately.

MySQL UPDATE not working when first subquery = 0

I have the following query to update my payment table in order to set it equal to the sum of all charges minus the sum of all payments and credits in a customer database.
This works perfectly as long as there is a charge, however, if the first subquery is equal to zero it does not update payment.balance to a negative number, it simply remains zero.
Can anyone tell me how to fix this or why this is?
UPDATE customer
SET balance = (SELECT SUM(amount)
FROM payment
WHERE type = 'C'
AND custID = '10003')
- (SELECT SUM(amount)
FROM payment
WHERE (type = 'P' OR type = 'X')
AND custID = '10003')
WHERE custID = '10003';
So in brief summary, when the first subquery (SELECT SUM(amount) FROM payment WHERE type = 'C' AND custID = '10003') is 0, the update always results in 0 instead of 0 minus the second subquery.
Thoughts?
Being that I cannot see your actual db table column setup I'm guessing this.
MySQL SUM() function returns the sum of an expression. SUM() function returns NULL when the return set has no rows.
Use an if condition so in the event that SUM() yields NULL then have the value returned as 0 so you never have NULL - x or x - NULL or NULL - NULL.

Two rows of data combined into one

A beginner in SQL but given the opportunity to create my own database.
So I have a table Invoice with similar reference numbers but different status code numbers.
Table Invoice:
Reference Status Code Status Date
10198053 300 08/07/2013
10198053 500 08/09/2013
I would like the output to show:
Table Invoice:
Reference Status Code Status Date Status Date 2
10198053 300 08/07/2013 08/09/2013
Code:
select reference r, status Code s, status Date,
case
when r=r and s=s (???)
from Table Invoice
Assuming that you need to see a date for 300 and a date for 500, you could aggregate two columns based on each of those Status_Code:
SELECT
Reference,
MIN(CASE WHEN Status_Code = '300' THEN Status_Date ELSE NULL END) AS Status_Date_300,
MIN(CASE WHEN Status_Code = '500' THEN Status_Date ELSE NULL END) AS Status_Date_500
FROM Invoice
GROUP BY Reference;
As is indicated in the comments, this could change depending on the requirements of exactly what you are trying to find - however, this should get you started.
SQL Fiddle to test the above.
EDITED
Try this:
SELECT Invoice.reference, MIN(Invoice.statusCode) AS statusCode, MIN(Invoice.statusDate) AS statusDate, MAX(Invoice.statusDate) AS statusDate2
FROM Invoice
GROUP BY Invoice.reference;
GroupBy your reference number to get one instance of it on your query. Then use the MIN and MAX function to get the first and last instance of statusDate on a specific reference.