MySQL replace NULL to some value - mysql

I have the following stored procedure
BEGIN
SELECT kids.*, SUM(point) as `point_sum`
FROM kids
LEFT JOIN tasks
ON kids.id = tasks.kid_id
WHERE kids.user_id = IN_user_id
GROUP BY kids.name;
END
This statement works fine.
My Question: the SUM(point) for new users are typically NULL because there is no submitted value yet to be summed.
What I want is if SUM(point) is NULL then it should return value like 0 but otherwise it should present the sum. I have looked around and not sure how to fix it, any good ideas?

You could use the coalesce function:
SELECT kids.*, COALESCE(SUM(point), 0) as `point_sum`
FROM kids
LEFT JOIN tasks
ON kids.id = tasks.kid_id
WHERE kids.user_id = IN_user_id
GROUP BY kids.name;

All you really need is IFNULL():
SELECT kids.*, IFNULL(SUM(point), 0) AS point_sum
That converts NULL to the supplied value, in this case 0.

Related

If value exists in other table, return another value from that table

I currently have the following SQL query:
SELECT video_calls.initiated_user_id AS user_id,
(CASE
WHEN EXISTS
(SELECT *
FROM patients
WHERE patients.id = video_calls.initiated_user_id)
THEN 'patient'
ELSE (CASE
WHEN EXISTS
(SELECT *
FROM backend_users
WHERE backend_users.id = video_calls.initiated_user_id)
THEN "%%backend%%"
ELSE "unknown"
END)
END) AS user_type
FROM video_calls
WHERE id='7f350a98-93d3-4d21-80a8-6cda3e47a4c0'
UNION
SELECT user_id,
user_type
FROM channel_joins
WHERE channel_id='7f350a98-93d3-4d21-80a8-6cda3e47a4c0'
In the line, where it currently says THEN "%%backend%%" I'd like to return the column backend_users.backend_type instead, for the corresponding row where the value video_calls.initiated_user_id has been found. I suppose I need to work with a JOIN here, but I currently can't figure out where exactly.
You are already using a correlated subquery. You can use that to get the value:
ELSE (SELECT COALESCE(MAX(bu.backend_type), 'unknown')
FROM backend_users bu
WHERE bu.id = video_calls.initiated_user_id
)
Note the use of MAX(). This ensures that exactly one value is returned. If no rows match, the MAX() returns NULL, so 'unknown' is returned.
This has one slight nuance from your pseudo-code. If the matching row is NULL, then this returns 'unknown' rather than NULL. If that is an issue, the logic in the subquery can be tweaked.

Getting previous row in MySQL

I'm stucked in a MySQL problem that I was not able to find a solution yet. I have the following query that brings to me the month-year and the number new users of each period in my platform:
select
u.period ,
u.count_new as new_users
from
(select DATE_FORMAT(u.registration_date,'%Y-%m') as period, count(distinct u.id) as count_new from users u group by DATE_FORMAT(u.registration_date,'%Y-%m')) u
order by period desc;
The result is the table:
period,new_users
2016-10,103699
2016-09,149001
2016-08,169841
2016-07,150672
2016-06,148920
2016-05,160206
2016-04,147715
2016-03,173394
2016-02,157743
2016-01,173013
So, I need to calculate for each month-year the difference between the period and the last month-year. I need a result table like this:
period,new_users
2016-10,calculate(103699 - 149001)
2016-09,calculate(149001- 169841)
2016-08,calculate(169841- 150672)
2016-07,So on...
2016-06,...
2016-05,...
2016-04,...
2016-03,...
2016-02,...
2016-01,...
Any ideas: =/
Thankss
You should be able to use a similar approach as I posted in another S/O question. You are on a good track to start. You have your inner query get the counts and have it ordered in the final direction you need. By using inline mysql variables, you can have a holding column of the previous record's value, then use that as computation base for the next result, then set the variable to the new balance to be used for each subsequent cycle.
The JOIN to the SqlVars alias does not have any "ON" condition as the SqlVars would only return a single row anyhow and would not result in any Cartesian product.
select
u.period,
if( #prevCount = -1, 0, u.count_new - #prevCount ) as new_users,
#prevCount := new_users as HoldColumnForNextCycle
from
( select
DATE_FORMAT(u.registration_date,'%Y-%m') as period,
count(distinct u.id) as count_new
from
users u
group by
DATE_FORMAT(u.registration_date,'%Y-%m') ) u
JOIN ( select #prevCount := -1 ) as SqlVars
order by
u.period desc;
You may have to play with it a little as there is no "starting" point in counts, so the first entry in either sorted direction may look strange. I am starting the "#prevCount" variable as -1. So the first record processed gets a new user count of 0 into the "new_users" column. THEN, whatever was the distinct new user count was for the record, I then assign back to the #prevCount as the basis for all subsequent records being processed. yes, it is an extra column in the result set that can be ignored, but is needed. Again, it is just a per-line place-holder and you can see in the result query how it gets its value as each line progresses...
I would create a temp table with two columns and then fill it using a cursor that
does something like this (don't remember the exact syntax - so this is just a pseudo-code):
#val = CURSOR.col2 - (select col2 from OriginalTable t2 where (t2.Period = (CURSOR.Period-1) )))
INSERT tmpTable (Period, NewUsers) Values ( CURSOR.Period, #val)

01427. 00000 - "single-row subquery returns more than one row"

I'm getting 01427. 00000 - "single-row subquery returns more than one row" error while executing below procedure. the issue , what i believe , is in subquery
SELECT paymentterm FROM temp_pay_term WHERE pid = d.xProject_id
but how can i get rid of it.Now, i have added the complete code. please check and let me know the wrong tell me if more info. is to be provided.
CREATE OR REPLACE PROCEDURE paytermupdate IS
recordcount INT;
vardid NUMBER(38);
varpaymentterm VARCHAR2(200 CHAR);
BEGIN
recordcount := 0;
SELECT COUNT(1) INTO recordcount
FROM temp_pay_term;
IF recordcount > 0 THEN
FOR x IN (SELECT DISTINCT r.ddocname
FROM temp_pay_term p, docmeta d, revisions r
WHERE TO_CHAR(p.pid) = d.xproject_id AND r.did = d.did )
LOOP
SELECT MAX(did) INTO vardid
FROM revisions r
WHERE r.ddocname = x.ddocname
GROUP BY r.ddocname;
UPDATE docmeta d
SET paymentterm = (
SELECT paymentterm
FROM temp_pay_term
WHERE pid = d.xproject_id
)
WHERE d.did = vardid;
INSERT INTO documenthistory (dactionmillis, dactiondate, did, drevclassid,
duser, ddocname, daction, dsecuritygroup, paymentterm)
SELECT
to_number(TO_CHAR(systimestamp, 'FF')) AS dactionmillis,
TRUNC(systimestamp, 'dd') AS dactiondate,
did,
drevclassid,
'sysadmin' AS duser,
ddocname,
'Update' AS daction,
dsecuritygroup,
paymentterm
FROM revisions
WHERE did = vardid;
END LOOP;
COMMIT;
END IF;
END paytermupdate;
Do you use something like
select x,y,z, (subquery) from ?
If you are getting ORA-01427 you should think how to make filter conditions in your subquery more restrictive, and these restrictions should be business reasonable, not just simply "and rownum <=1".
As you want to update a record through that sub query you should put more filter conditions in it. You can decide on the filter conditions on the basis of the value you want to update in the table in outer query. If there are more values which satisfy the condition (which I do not believe is ideal but just in case) then rownum <=1 would suffice.
Two basic options come to mind. I'll start with the simplest.
First, add distinct to the subquery.
SET paymentterm =
(SELECT distinct paymentterm
FROM temp_pay_term
WHERE pid = d.xProject_id
)
Second, if you're receiving multiple distinct values from the subquery, then you will either have to (a) rework your script to not use a subquery or (b) limit values returned (as #Baljeet suggested) using more filter criteria or (c) pick which of the multiple distinct values you want using an aggregate function.
Using the aggregate method, I'm guessing PaymentTerm is a number of months or years? Even if it's a n/varchar field (i.e., "6 months"), you can still use the MIN() and MAX() aggregates (or at least you can in t-sql). If it's a numeric field, you could also use average. You'll have to figure out which works best for your business needs.

JOIN data from same table

I have a table containing the names, emails, positions, etc of a students, as well as their "status" (which can be one of Y or N.) I want to write a query that counts the number of each type of position, as well as the number of Y AND the number of N within each type using JOIN. (That is, it would be a table with three columns: Position, StatusIsYes, and StatusIsNo.)
I have already done this using the CASE clause the following way, but I can't figure out how to do it using the JOIN clause.
SELECT position,
COUNT(CASE WHEN status = 'Y' THEN 1 ELSE NULL END) AS StatusIsYes,
COUNT(CASE WHEN status = 'N' THEN 1 ELSE NULL END) AS StatusIsNo
FROM
students GROUP BY crd
I appreciate any suggestions!
EDIT: I know it can be done without using JOIN, but I want to know how it is possible to do it with a JOIN.
You don't need a join:
SELECT
position,
SUM(status = 'Y') AS StatusIsYes,
SUM(status = 'N') AS StatusIsNo
FROM students
GROUP BY position
Note the rather funky dispensing of the CASE, because in mysql (only) true is 1 and false is 0, so sum() of a condition counts how many times it is true :)
You can use SELF JOIN in the case when you want to fetch records from same table.
For ex:
Table Name: employee
Fields : EmpId,EmpName,ManagerId
Now if you want to get the details of Empolyees who are in Manager Position for that we need to write query like this:
SELECT e1.EmpId, e1.EmpName FROM EmployeeDetails e1, EmployeeDetails e2 where e1.EmpId=e2.ManagerId;
Hope it will help you.
Fro more information please check this link.
Try ::
SELECT
position,
COUNT(status = 'Y' ) AS StatusIsYes,
COUNT(status = 'N' ) AS StatusIsNo
FROM
students GROUP BY POSITION

MySQL IF NOT NULL, then display 1, else display 0

I'm working with a little display complication here. I'm sure there's an IF/ELSE capability I'm just overlooking.
I have 2 tables I'm querying (customers, addresses). The first has the main record, but the second may or may not have a record to LEFT JOIN to.
I want to display a zero if there is no record in the addresses table.
And I want to only display 1, if a record exists.
What I've attempted so far:
SELECT c.name, COALESCE(a.addressid,0) AS addressexists
FROM customers c
LEFT JOIN addresses a ON c.customerid = a.customerid
WHERE customerid = 123
This first example does not do it. But I may be utilizing COALESCE wrong.
How can I display a 0, if null, and a 1, if something exists?
Instead of COALESCE(a.addressid,0) AS addressexists, use CASE:
CASE WHEN a.addressid IS NOT NULL
THEN 1
ELSE 0
END AS addressexists
or the simpler:
(a.addressid IS NOT NULL) AS addressexists
This works because TRUE is displayed as 1 in MySQL and FALSE as 0.
SELECT c.name, IF(a.addressid IS NULL,0,1) AS addressexists
FROM customers c
LEFT JOIN addresses a ON c.customerid = a.customerid
WHERE customerid = 123
Careful if you're coming from C/C++ and expecting this to work:
select if(name, 1, 0) ..
Even if 'name' is not NULL, unlike in C, a false-condition still triggers and the above statement returns 0. Thus, you have to remember to explicitly check for NULL or empty string:
select if(name is null or name = '', 0, 1)
PS Eugen's example up above is correct, but I wanted to clarify this nuance as it caught me by surprise.
SELECT
c.name,
CASE WHEN a.addressid IS NULL THEN 0 ELSE 1 END AS addressexists
FROM customers c
LEFT JOIN addresses a ON c.customerid = a.customerid
WHERE customerid = 123
Another method without WHERE, try this..
Will select both Empty and NULL values
SELECT ISNULL(NULLIF(fieldname,'')) FROM tablename
It will set null if it is an empty string, then be true on that also.
You can actually use an IF statement in the latest versions of MySQL.
IF(expr,if_true_expr,if_false_expr)
IE:
SELECT name, IF(ISNULL(name), 'robot', 'human') AS type
FROM visitors
If within TSQL, you can try :
SELECT IIF(a.addressid IS NULL, 0, 1) AS addressexists
SQL Server should work