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.
Related
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.
I have 2 tables in MySQL:
emails_sent(
id int primary key,
id_type int,
date_sent datetime)
emails_clicks(
id int primary key,
id_email int,
date_click datetime,
foreign key(id_email) references emails_sent(id))
I want to write a query, that will return a percentage of emails, that were clicked not later than 10 min after user received an email. Grouped by each type of email (id_type). How can i do this?
You can do a conditional average.
In MySQL, that would be:
select s.id_type, avg(c.date_click < s.date_sent + interval 10 minute) res
from email_sent s
left join email_clicks c on c.id_email = s.id
group by id_type
For each each row in email_sent, the query recovers the corresponding row in email_clicks; if the email was clicked less than 10 minutes, the condition within avg() returns 1, else 0. The average gives you the ratio of emails that were clicked less than 10 minutes after being sent (as a decimal value between 0 and 1).
Note: This assumes SQL Server (one of the original tags).
You can use a subquery to get the first click and then aggregate:
select s.id_type,
avg(case when first_click_date < dateadd(minute, 10, date_sent)
then 1.0 else 0
end) as num_clicks
from (select s.*,
(select min(date_click)
from email_clicks c
where c.id_email = s.id
) as first_click_date
from email_sent s
) s
group by id_type;
This handles users that click multiple times, 10 minutes later or not.
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.
just a quick and easy question, nothing too complicated.
I'm making a function in MySQL that returns total revenue from a specific flight and my question is this; can I COUNT() WHERE the values of paymentType = 1, and then multiply them by the price of a flight ticket? I did try this, but it didn't multiply for some reason.
Example of what I'm thinking:
SELECT COUNT(paymentType * 400) FROM booking
WHERE paymentType = 1;
Here is the code between begin and end$$.
DECLARE revenue INT;
SELECT COUNT(paymentType) INTO revenue FROM booking
INNER JOIN flights
ON booking.flightCode = flights.flightCode
WHERE paymentType = 1
AND flights.flightDate = flight_date
AND flights.flightNumber = flight_number;
RETURN revenue;
count() just checks if the specified field is non-null, and counts that as 1, regardless of the value in there. count(somefield*400) and count(somefield) will basically all give the exact same count
If you want to multiple the count ITSELF by 400, then you'd need
select count(somefield) * 400 as result
I have a report that displays a graph. The X axis uses the date from the below query. Where the query returns no date, I am getting gaps and would prefer to return a value. Is there any way to force a date where there are no records?
SELECT
DATE(instime),
CASE
WHEN direction = 1 AND duration > 0 THEN 'Incoming'
WHEN direction = 2 THEN 'Outgoing'
WHEN direction = 1 AND duration = 0 THEN 'Missed'
END AS type,
COUNT(*)
FROM taxticketitem
GROUP BY
DATE(instime),
CASE
WHEN direction = 1 AND duration > 0 THEN 'Incoming'
WHEN direction = 2 THEN 'Outgoing'
WHEN direction = 1 AND duration = 0 THEN 'Missed'
END
ORDER BY DATE(instime)
One possible way is to create a table of dates and LEFT JOIN your table with them. The table could look something like this:
CREATE TABLE `datelist` (
`date` DATE NOT NULL,
PRIMARY KEY (`date`)
);
and filled with all dates between, say Jan-01-2000 through Dec-31-2050 (here is my Date Generator script).
Next, write your query like this:
SELECT datelist.date, COUNT(taxticketitem.id) AS c
FROM datelist
LEFT JOIN taxticketitem ON datelist.date = DATE(taxticketitem.instime)
WHERE datelist.date BETWEEN `2012-01-01` AND `2012-12-31`
GROUP BY datelist.date
ORDER BY datelist.date
LEFT JOIN and counting not null values from right table's ensures that the count is correct (0 if no row exists for a given date).
You would need to have a set of dates to LEFT JOIN your table to it. Unfortunately, MySQL lacks a way to generate it on the fly.
You would need to prepare a table with, say, 100000 consecutive integers from 0 to 99999 (or how long you think your maximum report range would be):
CREATE TABLE series (number INT NOT NULL PRIMARY KEY);
and use it like this:
SELECT DATE(instime) AS r_date, CASE ... END AS type, COUNT(instime)
FROM series s
LEFT JOIN
taxticketitems ti
ON ti.instime >= '2013-01-01' + INTERVAL number DAY
AND ti.instime < '2013-01-01' + INTERVAL number + 1 DAY
WHERE s.number <= DATEDIFF('2013-02-01', '2013-01-01')
GROUP BY
r_date, type
Had to do something similar before.
You need to have a subselect to generate a range of dates. All the dates you want. Easiest with a start date added to a number:-
SELECT DATE_ADD(SomeStartDate, INTERVAL (a.I + b.1 * 10) DAY)
FROM integers a, integers b
Given a table called integers with a single column called i with 10 rows containing 0 to 9 that SQL will give you a range of 100 days starting at SomeStartDate
You can then left join your actual data against that to get the full range.