Coalesce equivalent for nth not null value - MySQL - mysql

I have been tearing my hair out over this issue. I am working with an existing data set and need to remove all the null values from the columns in table A and shunt them across so they are ordered like in table B
I need something which is equivalent to Coalesce but to retrieve the nth value so I can get the result sorted like in table B
What I have:
Table A
Name CURRENT OCT12 SEPT12 AUG12 JUL12 JUN12 MAY12 APR12
---------------------------------------------------------
A NULL NULL Aug-12 NULL NULL Jun-12 NULL Apr-12
B Nov-12 NULL Aug-12 NULL Jul-12Jun-12 NULL Apr-12
What I need:
Table B
Name Change1 Change2 Change3 Change4 Change5 Change6
----------------------------------------------------
A Aug-12 Jun-12 Apr-12 NULL NULL NULL
B Nov-12 Aug-12 Jul-12 Jun-12 Apr-12 NULL
Code-wise, it would be something like:
Select
first non-null value as Change1
,second non-null value as Change2
,third non-null value as Change3
,fourth non-null value as Change4
,fifth non-null value as Change5...etc..
from Table_A
I am using MySQL and i have no idea how to reference the nth non null value in order to call them into Table_B
Does anyone have any ideas?

I am not sure if I would reccommend using this solution... normalization of your data is always a better choice, but I wanted to answer using plain SQL with some strings functions. This query should return what you are looking for:
SELECT
Name,
Changes,
REVERSE(SUBSTRING_INDEX(REVERSE(SUBSTRING_INDEX(Changes, ',', 1)), ',', 1)) as Change1,
REVERSE(SUBSTRING_INDEX(REVERSE(SUBSTRING_INDEX(Changes, ',', 2)), ',', 1)) as Change2,
REVERSE(SUBSTRING_INDEX(REVERSE(SUBSTRING_INDEX(Changes, ',', 3)), ',', 1)) as Change3,
REVERSE(SUBSTRING_INDEX(REVERSE(SUBSTRING_INDEX(Changes, ',', 4)), ',', 1)) as Change4,
REVERSE(SUBSTRING_INDEX(REVERSE(SUBSTRING_INDEX(Changes, ',', 5)), ',', 1)) as Change5,
REVERSE(SUBSTRING_INDEX(REVERSE(SUBSTRING_INDEX(Changes, ',', 6)), ',', 1)) as Change6
FROM (
SELECT
Name,
CONCAT_WS(',', CURRENT, OCT12, SEPT12, AUG12, JUL12, JUN12, MAY12, APR12, ',') as Changes
FROM
TableA
) s
I'm concatenating all values in a comma separated string, with two commas at the end of the string (one comma would be enough anyway, but it's easier to put two and just ignore the last one...), and since I'm using CONCAT_WS it will automatically skip null values, and the resulting string will be something like Aug-12,Jun-12,Apr-12,,.
Then in the outer query I'm extracting the n-th element of the string, using SUBSTRIG_INDEX. I would recommend to normalize your database, but if you need a quick fix this solution might be a good starting point.
See it working here.
Please notice that I am not returning NULL values where there are no changes, but I am returning empty strings instead. This can be changed if you need.

If you don't want to use strings functions you can try this sql using unpivot and row number partitioning:
CREATE TABLE #TableA
(
"Name" VARCHAR(10),
"CURRENT" VARCHAR(10),
OCT12 VARCHAR(10),
SEPT12 VARCHAR(10),
AUG12 VARCHAR(10),
JUL12 VARCHAR(10),
JUN12 VARCHAR(10),
MAY12 VARCHAR(10),
APR12 VARCHAR(10)
)
INSERT INTO #TableA
("Name", "CURRENT", OCT12, SEPT12, AUG12, JUL12, JUN12, MAY12, APR12)
VALUES
('A', NULL, NULL, 'Aug-12', NULL, NULL, 'Jun-12', NULL, 'Apr-12'),
('B', 'Nov-12', NULL, 'Aug-12', NULL, 'Jul-12', 'Jun-12', NULL, 'Apr-12')
SELECT * FROM #TableA;
Select "Name",
Min(Case row_num When 1 Then data End) Change1,
Min(Case row_num When 2 Then data End) Change2,
Min(Case row_num When 3 Then data End) Change3,
Min(Case row_num When 4 Then data End) Change4,
Min(Case row_num When 5 Then data End) Change5,
Min(Case row_num When 6 Then data End) Change6
From
(
select "Name",data,DBColumnName,
ROW_NUMBER() OVER (PARTITION BY "Name" ORDER BY "Name") row_num
From #TableA
unpivot (data for DBColumnName in ("CURRENT",OCT12,SEPT12,AUG12,JUL12,JUN12,MAY12,APR12) ) as z
) TableB
group by "Name";
References:
-- TSQL Pivot without aggregate function
-- https://www.sqlservertutorial.net/sql-server-window-functions/sql-server-row_number-function/
-- https://learn.microsoft.com/en-us/sql/t-sql/queries/from-using-pivot-and-unpivot?view=sql-server-ver15

Related

Mysql group by included the whitespace as same value

I have this table
CREATE TABLE table1 (
`ID` VARCHAR(100),
`Val` VARCHAR(100),
`Val2` VARCHAR(100)
);
and this value
INSERT INTO table1
(`ID`, `Val`, `Val2`)
VALUES
('1','1234 ','now'), // 1 whitespace
('2','1234 ','now1'), // 2 whitespace
('5','1234 ','now190'), // 2 whitespace
('3','1234 ','now2'), // 3 whitespace
('4','3123123','now3')
I need to group by the data and count how many data that have same value, so i used group by and count
select Val,count(*) from `table1` group by Val
the result not what i expect because the data for ID 1,2,5, and 3 is counted as same value like below result
Val count(*)
1234 4
3123123 1
how could i make the result like expected result below so the value didn't count as same value
Val count(*)
1234 1 // 1 whitespace
1234 2 // 2 whitespace
1234 1 // 3 whitespace
3123123 1
see this fiddle for demo
This is just how MySQL does it by default, unless you use some specific collation. A typical workaround is to use binary:
select binary val, count(*) cnt from table1 group by binary val
Or, if you do want a regular string in the resultset rather than a binary string:
select max(val) as newval, count(*) cnt from table1 group by binary val

CASE with IS NULL not replacing as expected

I have a simple column with some numbers, which looks like;
133
8
55
11
NULL
NULL
235
NULL
I want to put a default in when selecting this column, with the aim of replacing the nulls. I've done;
CASE
WHEN round(avg(s.seconds)) IS NULL THEN 0
ELSE round(avg(s.seconds))
END as 'seconds'
However, my nulls remain. My expectation is that they would be set to 0. Why would this not work?
NULL values are ignored in average calculations.
If you want to replace NULL values for 0 in your average, you need to use COALESCE on the values, inside the average function. Example follows:
DROP TEMPORARY TABLE IF EXISTS TEMP1;
CREATE TEMPORARY TABLE TEMP1
SELECT 1 AS price
UNION ALL SELECT NULL AS price
UNION ALL SELECT NULL AS price
UNION ALL SELECT 3 AS price;
SELECT * FROM TEMP1; # List values (1, NULL, NULL, 3)
SELECT AVG(price) FROM TEMP1; # Returns 2 (NULL values IGNORED)
SELECT AVG(COALESCE(price,0)) FROM TEMP1; # Returns 1 (NULL values REPLACED with 0)
You can do it by 3 ways
1
round(avg(CASE
WHEN s.seconds IS NULL THEN 0
ELSE s.seconds
END)) as 'seconds'
2
The MySQL IFNULL() function lets you return an alternative value if an
expression is NULL:
round(avg(IFNULL(s.seconds,0)))
3
or we can use the COALESCE() function, like this:
round(avg(COALESCE(s.seconds,0)))

get more fields from multiple things in sql

I need to get multiple information within 1 query if possible.
Lets say this is the row:
Name - c1 - c2 - c3 - c4
What I need to get is the top in each column, so like
Paulus - 50 - 0 - 0 - 0
John - 0 - 50 - 0 - 0
Anne - 0 - 0 - 50 - 0
Chris - 0 - 0 - 0 - 50
And my query should return something like:
Paulus - c1 (50) - John - c2 (50) - Anne - c3 (50) - Chris - c4 (50)
Name - c1 - name - c2
I've tried: SELECT Name, c1, Name, c2 FROM table ORDER BY c1 DESC, c2 DESC
But it just doesn't work, I know it all looks vague but I hope someone is able to understand my question here...
you can create a store procedure or function and use a while loop then create a temp table in it and in each loop alter your table and add new column at the end return your answer.
but i recommend you to use another way to get your answer and change it in view. just change it for show to end-user.
You can do it with a subquery and series of self joins. A subquery will return the max numbers within the c1, ..., c4 columns. Then join your table 4x on the previous subquery to get the record(s) where c1, ..., c4 columns match the maximums.
However, pls heed #strawberry's advice and normalise your data structure.
I'll give you a sample code for 2 fields:
select t1.name as maxc1name, m.maxc1, t2.name as maxc2name, m.maxc2 from
(select max(c1) as maxc1, max(c2) as maxc2 from mytable) m
inner join mytable t1 on m.maxc1=t1.c1
inner join mytable t2 on m.maxc2=t2.c2
The catch: what happens if there is a tie in any of the columns and more than 1 record matches from your table any of the max values. You have not defined what you want to do with these, so I'm not dealing with it either.
Leaving the schema requirement as is, try:
create table `c_data` (
`Name` varchar(24) not null,
`c1` int(5) not null,
`c2` int(5) not null,
`c3` int(5) not null,
`c4` int(5) not null
);
insert c_data values
('Paulus', 50, 50, 0, 0),
('John', 0, 50, 0, 0),
('Anne', 0, 0, 50, 0),
('Chris', 0, 0, 0, 50);
select
name,
case greatest(c1, c2, c3, c4)
when c1 then concat('c1', ' (', c1, ')')
when c2 then concat('c2', ' (', c2, ')')
when c3 then concat('c3', ' (', c3, ')')
when c4 then concat('c4', ' (', c4, ')')
end as top_column
from c_data
order by name;
As #shadow states you still have to decide how to handle ties. This query just takes the first column with the shared max value (see Paulus).
BUT, consider normalizing your table as has been suggested. The data will be stored more efficiently and the table will be more versatile for future queries. Ties still need to be handled. This version takes the first data_type from an ascending alphanumeric sort.
create table c_data
(
name varchar(24),
data_type varchar(8),
data_value int(5)
);
insert into c_data values
('Paulus', 'c1', 50),
('Paulus', 'c2', 50),
('John', 'c2', 50),
('Anne', 'c3', 50),
('Chris', 'c4', 50);
select
name,
concat(min(data_type), ' (', max(data_value), ')') as top_column
from c_data
group by name
order by name;

mysql count distinct value

I have trouble wondering how do I count distinct value. using if on the select column
I have SQLFIDDLE here
http://sqlfiddle.com/#!2/6bfb9/3
Records shows:
create table team_record (
id tinyint,
project_id int,
position varchar(45)
);
insert into team_record values
(1,1, 'Junior1'),
(2,1, 'Junior1'),
(3,1, 'Junior2'),
(4,1, 'Junior3'),
(5,1, 'Senior1'),
(6,1, 'Senior1'),
(8,1, 'Senior2'),
(9,1, 'Senior2'),
(10,1,'Senior3'),
(11,1, 'Senior3'),
(12,1, 'Senior3')
I need to count all distinct value, between Junior and Senior column.
all same value would count as 1.
I need to see result something like this.
PROJECT_ID SENIOR_TOTAL JUNIOR_TOTAL
1 3 3
mysql query is this. but this is not a query to get the result above.
SELECT
`team_record`.`project_id`,
`position`,
SUM(IF(position LIKE 'Senior%',
1,
0)) AS `Senior_Total`,
SUM(IF(position LIKE 'Junior%',
1,
0)) AS `Junior_Total`
FROM
(`team_record`)
WHERE
project_id = '1'
GROUP BY `team_record`.`project_id`
maybe you could help me fix my query above to get the result I need.
thanks
I think you want this:
SELECT
project_id,
COUNT(DISTINCT CASE when position LIKE 'Senior%' THEN position END) Senior_Total,
COUNT(DISTINCT CASE when position LIKE 'Junior%' THEN position END) Junior_Total
FROM team_record
WHERE project_id = 1
GROUP BY project_id
The CASE will return a null if the WHEN is false (ie ELSE NULL is the default, which I omitted for brevity), and nulls aren't counted in DISTINCT.
Also, unnecessary back ticks, brackets and qualification removed.

Select columns that are not null in any record

I need to analyze a MySQL table and want to determine all columns that never contain NULL, 0 or an empty string in any record in that table.
I do not have a clue how to do that, since MySQL expects me to select the columns at the beginning of the statement. I thought I could maybe rotate the table by 90° and then do something like
SELECT column_header FROM rotated_table WHERE record_1 <> NULL AND record_2 <> NULL AND [...]
But this seems to be a lot of work.
Is there an easier way to get the information i require?
Update example:
Table1:
name street zip
MyName 1st Ave. 1000
OtherName 2nd Street NULL
My statement now should show something like:
name street
MyName 1st Ave.
OtherName 2nd Street
Because the column zip contains a NULL value.
If there was an additional row like
name street zip
MyName 1st Ave. 1000
OtherName 2nd Street NULL
NULL Foo blvd. 3453
It should return
street
1st Ave.
2nd Street
Foo blvd.
Because name and zip contain at least one NULL value.
You can use the behavior of COUNT ignoring NULL values to your advantage.
Subtract the count of the column you're examining from the count of the number of rows. Any column that returns a value of 0 does not contain a NULL value. You'll need to use a CASE statement to convert any values you consider to be "empty" into NULL.
This approach also eliminates the need copying the entire table in order to "rotate" it.
I whipped up an example here in SQLFiddle which should work for you.
Here's the content of my SQLFiddle example in case the link becomes unusable:
CREATE TABLE address
(
address int auto_increment primary key,
street1 varchar(20),
street2 varchar(20),
city varchar(20),
state varchar(20),
zip int,
comment varchar(20)
);
INSERT INTO address
(street1, street2, city, state, zip, comment)
VALUES
('123 Main St.', null, 'Cleveland', 'OH', 44123, ''),
('1313 Mockingbird Ln.', null, 'Cleveland', 'OH', 0, 'Unknown zip'),
('321 Main St.', 'Apt #1', 'Cleveland', 'OH', 44123, ''),
('321 Main St.', 'Apt #2', 'Cleveland', 'OH', 44123, '');
SELECT
COUNT(*) rows, -- not really needed, you can remove this
COUNT(*) - COUNT(CASE ad.street1 WHEN '' THEN NULL ELSE ad.street1 END) empty_street1,
COUNT(*) - COUNT(CASE ad.street2 WHEN '' THEN NULL ELSE ad.street2 END) empty_street2,
COUNT(*) - COUNT(CASE ad.city WHEN '' THEN NULL ELSE ad.city END) empty_city,
COUNT(*) - COUNT(CASE ad.state WHEN '' THEN NULL ELSE ad.state END) empty_state,
-- Change the value being compared based on the column type. Strings '', numbers 0, etc.
COUNT(*) - COUNT(CASE ad.zip WHEN 0 THEN NULL ELSE ad.zip END) empty_zip,
COUNT(*) - COUNT(CASE ad.comment WHEN '' THEN NULL ELSE ad.comment END) empty_comment
FROM address ad;
Which results in:
ROWS EMPTY_STREET1 EMPTY_STREET2 EMPTY_CITY EMPTY_STATE EMPTY_ZIP EMPTY_COMMENT
4 0 2 0 0 1 3
The correct comparison to NULL is IS NULL or IS NOT NULL. So:
SELECT column_header
FROM rotated_table
WHERE record_1 IS NOT NULL AND record_2 IS NOT NULL AND [...];
However "record" is an odd name for columns. You want to be sure the column names are in the WHERE clause.