How to extract different json elements from the same table in SQL query? - mysql

I am querying from a table with the following format:
id|provider|score
--------------------------------
1 | att | '{"attscore":300}'
1 | verizon | '{"verizonscore":299}'
2 | att | '{"attscore":200}'
3 | verizon | '{"verizonscore":155}'
I am trying to get a table that looks like the following:
id|attscore|verizonscore
-------------------------
1 | 300 | 299
2 | 200 | null
3 | null | 155
Note that used to json in sql

CREATE TABLE table1 (
`id` INTEGER,
`provider` VARCHAR(7),
`score` VARCHAR(22)
);
INSERT INTO table1
(`id`, `provider`, `score`)
VALUES
('1', 'att', '{"attscore":300}'),
('1', 'verizon', '{"verizonscore":299}'),
('2', 'att', '{"attscore":200}'),
('3', 'verizon', '{"verizonscore":155}');
SELECT
id,
GROUP_CONCAT(CASE WHEN provider = 'att' THEN `score`->"$.attscore" ELSe NULL END) attscore
,GROUP_CONCAT(CASE WHEN provider = 'verizon' THEN `score`->"$.verizonscore" ELSe NULL END) verizonscore
FROM table1
GROUP BY id
id | attscore | verizonscore
-: | :------- | :-----------
1 | 300 | 299
2 | 200 | null
3 | null | 155
db<>fiddle here
This works with a fixed number of column quite well, if you have much more of these you need to do something like this

Related

Sort mysql result by two column, but with a “holed” column

I have the following initial situation:
+------------+-------------+
| legacyRank | forcedRank |
+------------+-------------+
| 0 | NULL |
| 1 | 6 |
| 2 | NULL |
| 3 | 1 |
| 4 | NULL |
| 5 | NULL |
| 6 | 2 |
+------------+-------------+
You could generate this table by the following schema:
CREATE TABLE two_column_order (
legacyRank VARCHAR(45),
forcedRank VARCHAR(45)
);
INSERT INTO two_column_order (legacyRank, forcedRank)
VALUES (5, NULL);
INSERT INTO two_column_order (legacyRank, forcedRank)
VALUES (6, 2);
INSERT INTO two_column_order (legacyRank, forcedRank)
VALUES (7, NULL);
INSERT INTO two_column_order (legacyRank, forcedRank)
VALUES (0, NULL);
INSERT INTO two_column_order (legacyRank, forcedRank)
VALUES (1, NULL);
INSERT INTO two_column_order (legacyRank, forcedRank)
VALUES (2, 6);
INSERT INTO two_column_order (legacyRank, forcedRank)
VALUES (3, NULL);
INSERT INTO two_column_order (legacyRank, forcedRank)
VALUES (4, 1);
SELECT * FROM two_column_order
order by
CASE when `forcedRank` <> NULL THEN `forcedRank`
ELSE `legacyRank`
END
The goal is to put each line with no-NULL forcedRank column in the accurate position mentioned in this forcedRank column. The expected rendering is like:
+------------+-------------+
| legacyRank | forcedRank |
+------------+-------------+
0 | 0 | NULL |
1 | 3 | 1 |
2 | 6 | 2 |
3 | 2 | NULL |
4 | 4 | NULL |
5 | 5 | NULL |
6 | 6 | 6 |
+------------+-------------+
As you see, each line take the position ordered by the forcedRank column if not NULL. When the the NULL rows still sorted by the legacyRank column in the positions leaved unoccupied by the non-NULL rows, but never shift the forced rows.
In this order, I tried to use the CASE WHEN syntax inside the ORDER BY like this:
SELECT * FROM two_column_order
order by
CASE WHEN (`forcedRank` is NULL ) THEN `legacyRank`
END ,
-`forcedRank` DESC,
`legacyRank`
But the result doesn’t really feat my expectations:
+------------+-------------+
| legacyRank | forcedRank |
+------------+-------------+
| 3 | 1 |
| 6 | 2 |
| 6 | 6 |
| 0 | NULL |
| 2 | NULL |
| 4 | NULL |
| 5 | NULL |
+------------+-------------+
So how can I make the legacyRank column get order beyond the forcedrank rows without shift them?
NULL can't tbe comapred like that you need to use ISor in your case IS NOT
SELECT * FROM two_column_order
order by
CASE when `forcedRank` IS NOT NULL THEN `forcedRank`
ELSE `legacyRank`
END
legacyRank
forcedRank
0
null
1
null
4
1
6
2
3
null
5
null
2
6
7
null
fiddle
As the first answer won't give you the correct answer.
i have changed the order by adding a decimal point to the original number so that it will be bigger than the new forced rank.
it will keep the order and a the forced number is smaller then the legayrank, it get you follwoing result
SELECT * FROM two_column_order
order by
CASE when `forcedRank` IS NOT NULL THEN `forcedRank`
ELSE `legacyRank` + .1
END
legacyRank
forcedRank
0
null
4
1
1
null
6
2
3
null
5
null
2
6
7
null
fiddle

Selectively ignoring tuples from a table

The problem that we are tackling with a data mining application is best described with an illustrative example.
There is a sample table myTable, which is defined as follows:
CREATE TABLE myTable
(
id INT UNSIGNED AUTO_INCREMENT,
colA VARCHAR(8),
colB VARCHAR(12),
revFlag CHAR(8), -- 'REVISED' or any other value, including NULL
PRIMARY KEY(id)
);
Any tuple with a revFlag value of REVISED takes precedence over any other tuple with the same value for colA, as long as the revFlag value of the latter tuple is not REVISED. In other words when we select rows from the table we skip all rows for which the revFlag value is not REVISED and there exists a row with the same value for colA for which the revFlag value is REVISED.
We populate the table as follows:
INSERT INTO myTable(colA, colB) VALUES ('XSR0KA3V', 'OLD-O7RAR81X'),
('4F2JG71O', 'OLD-E71BE63L'), ('MML3HN48', 'OLD-B02PFB63'),
('5H0MWVSB', 'OLD-V70XLGHT'), ('JW73ZX0J', 'OLD-KME1GXQF'),
('XZV0EY0G', 'OLD-N06BURDF'), ('9HBQZ88V', 'OLD-76HSPUAL'),
('YI5AT6G4', 'OLD-X8KAWD7Z');
INSERT INTO myTable(colA, colB, revFlag) VALUES
('XSR0KA3V', 'NEW-O7RAR81X', 'REVISED'),
('MML3HN48', 'NEW-B02PFB63', 'REVISED'),
('9HBQZ88V', 'NEW-76HSPUAL', 'REVISED'),
('YI5AT6G4', 'NEW-X8KAWD7Z', 'XYZ'),
('Z8H2B5KY', '3RINJV0K', 'REVISED');
Naturally SELECT * FROM myTable yields the following:
+----+----------+--------------+---------+
| id | colA | colB | revFlag |
+----+----------+--------------+---------+
| 1 | XSR0KA3V | OLD-O7RAR81X | NULL |
| 2 | 4F2JG71O | OLD-E71BE63L | NULL |
| 3 | MML3HN48 | OLD-B02PFB63 | NULL |
| 4 | 5H0MWVSB | OLD-V70XLGHT | NULL |
| 5 | JW73ZX0J | OLD-KME1GXQF | NULL |
| 6 | XZV0EY0G | OLD-N06BURDF | NULL |
| 7 | 9HBQZ88V | OLD-76HSPUAL | NULL |
| 8 | YI5AT6G4 | OLD-X8KAWD7Z | NULL |
| 9 | XSR0KA3V | NEW-O7RAR81X | REVISED |
| 10 | MML3HN48 | NEW-B02PFB63 | REVISED |
| 11 | 9HBQZ88V | NEW-76HSPUAL | REVISED |
| 12 | YI5AT6G4 | NEW-X8KAWD7Z | XYZ |
| 13 | Z8H2B5KY | 3RINJV0K | REVISED |
+----+----------+--------------+---------+
We would like to design a query that does not return any tuples that are REVISED by other tuples. In our case the output should look like this:
+----+----------+--------------+---------+
| id | colA | colB | revFlag |
+----+----------+--------------+---------+
| 2 | 4F2JG71O | OLD-E71BE63L | NULL |
| 4 | 5H0MWVSB | OLD-V70XLGHT | NULL |
| 5 | JW73ZX0J | OLD-KME1GXQF | NULL |
| 6 | XZV0EY0G | OLD-N06BURDF | NULL |
| 8 | YI5AT6G4 | OLD-X8KAWD7Z | NULL |
| 9 | XSR0KA3V | NEW-O7RAR81X | REVISED |
| 10 | MML3HN48 | NEW-B02PFB63 | REVISED |
| 11 | 9HBQZ88V | NEW-76HSPUAL | REVISED |
| 12 | YI5AT6G4 | NEW-X8KAWD7Z | XYZ |
| 13 | Z8H2B5KY | 3RINJV0K | REVISED |
+----+----------+--------------+---------+
You can use a NOT EXISTS clause to filter out all rows for which another row exists which has the same colA value and revFlag = 'REVISED':
SELECT *
FROM myTable t1
WHERE NOT EXISTS (
SELECT *
FROM myTable t2
WHERE t2.id != t1.id AND t2.colA = t1.colA AND t2.revFlag = 'REVISED'
)
Output:
id colA colB revFlag
2 4F2JG71O OLD-E71BE63L (null)
4 5H0MWVSB OLD-V70XLGHT (null)
5 JW73ZX0J OLD-KME1GXQF (null)
6 XZV0EY0G OLD-N06BURDF (null)
8 YI5AT6G4 OLD-X8KAWD7Z (null)
9 XSR0KA3V NEW-O7RAR81X REVISED
10 MML3HN48 NEW-B02PFB63 REVISED
11 9HBQZ88V NEW-76HSPUAL REVISED
12 YI5AT6G4 NEW-X8KAWD7Z XYZ
13 Z8H2B5KY 3RINJV0K REVISED
Demo on dbfiddle
SELECT stuff
FROM somewhere x
LEFT
JOIN somewhere y
ON y.thing = x.thing
AND y.otherthing = x.otherthing
AND y.anotherthing > x.anotherthing
AND y.whatever = 'some value'
WHERE y.anotherthing .... ;
You could use the IN Clause
Schema (MySQL v8.0)
CREATE TABLE table1 (
`id` INTEGER,
`colA` VARCHAR(8),
`colB` VARCHAR(12),
`revFlag` VARCHAR(7)
);
INSERT INTO table1
(`id`, `colA`, `colB`, `revFlag`)
VALUES
('1', 'XSR0KA3V', 'OLD-O7RAR81X', NULL),
('2', '4F2JG71O', 'OLD-E71BE63L', NULL),
('3', 'MML3HN48', 'OLD-B02PFB63', NULL),
('4', '5H0MWVSB', 'OLD-V70XLGHT', NULL),
('5', 'JW73ZX0J', 'OLD-KME1GXQF', NULL),
('6', 'XZV0EY0G', 'OLD-N06BURDF', NULL),
('7', '9HBQZ88V', 'OLD-76HSPUAL', NULL),
('8', 'YI5AT6G4', 'OLD-X8KAWD7Z', NULL),
('9', 'XSR0KA3V', 'NEW-O7RAR81X', 'REVISED'),
('18', 'XSR0KA3V', 'NEW-O7RAR81X', 'ZRNTR'),
('10', 'MML3HN48', 'NEW-B02PFB63', 'REVISED'),
('11', '9HBQZ88V', 'NEW-76HSPUAL', 'REVISED'),
('12', 'YI5AT6G4', 'NEW-X8KAWD7Z', 'XYZ'),
('13', 'Z8H2B5KY', '3RINJV0K', 'REVISED');
Query #1
SELECT
`id`, `colA`, `colB`, `revFlag`
FROM
table1 t1
WHERE
(`colA` , IFNULL(`revFlag`,0)) IN
(SELECT
`colA`, `revFlag`
FROM
table1
WHERE
`revFlag` = 'REVISED' UNION SELECT
`colA`, IFNULL(MAX(`revFlag`),0)
FROM
table1
WHERE
`colA` NOT IN (SELECT
`colA`
FROM
table1
WHERE
`revFlag` = 'REVISED')
GROUP BY `colA`)
ORDER BY id;
id
colA
colB
revFlag
2
4F2JG71O
OLD-E71BE63L
4
5H0MWVSB
OLD-V70XLGHT
5
JW73ZX0J
OLD-KME1GXQF
6
XZV0EY0G
OLD-N06BURDF
9
XSR0KA3V
NEW-O7RAR81X
REVISED
10
MML3HN48
NEW-B02PFB63
REVISED
11
9HBQZ88V
NEW-76HSPUAL
REVISED
12
YI5AT6G4
NEW-X8KAWD7Z
XYZ
13
Z8H2B5KY
3RINJV0K
REVISED
View on DB Fiddle

Increment value depending other one

Let's assume I have the table:
id | val_1 | val_2
1 | 1 | 0
2 | 1 | 1
3 | 1 | 2
4 | 2 | 0
val_2 should be zero at first if there was no rows with val_1 before. Otherwise it should be previous val_2 + 1 for this val_1.
I can't figure it out by myself the best way to do it. The one thing I've invented is trigger after insert, but I think here maybe some other way to do it cleaner and faster?
My code is something like:
DELIMITER $$
CREATE TRIGGER after_table_insert
AFTER INSERT
ON table FOR EACH ROW
BEGIN
UPDATE table SET val_2 = t.val_2 + 1
FROM (
SELECT val_2 FROM table WHERE val_1 = new.val_1 ORDER BY id DESC LIMIT 1
) t
WHERE id = new.id;
END$$
DELIMITER ;
I will appreciate for any help!
Have a great day/night.
You have couple of issues with such setup:
What's going on if you UPDATE or DELETE rows? It can mess up everything with val2. Be careful with that.
Val2 can always be calculated and there is no need to store it.
Having said that, below I will show you a setup with which I will store only id and val1. Then val2 will be calculated within the SELECT statement (so it will always be correct).
CREATE TABLE vals(
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
val INT NOT NULL
);
INSERT INTO vals(val) VALUES(1),(1),(1),(2);
Now what I am going to do is to use the ROW_NUMBER() function (which prints the row number) and run it over a PARTITION BY val:
SELECT id, val,
ROW_NUMBER() OVER (
PARTITION BY val
) AS val2
FROM vals;
We are almost there. Sadly it will offset them by 1 compared to what you need:
+----+-----+------+
| id | val | val2 |
+----+-----+------+
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 1 | 3 |
| 4 | 2 | 1 |
+----+-----+------+
The fix is simple. Just add "-1" to it and you are ready.
SELECT id, val,
-1+ROW_NUMBER() OVER (
PARTITION BY val
) AS val2
FROM vals;
This will produce:
+----+-----+------+
| id | val | val2 |
+----+-----+------+
| 1 | 1 | 0 |
| 2 | 1 | 1 |
| 3 | 1 | 2 |
| 4 | 2 | 0 |
+----+-----+------+
With this solution there is no need to store val2 at all (you can create it as a VIEW if you wish) and it is not vulnerable to the issue when you delete a row (it will continue to work properly).
This is one possibility
Schema (MySQL v5.7)
CREATE TABLE table1 (
`id` INTEGER,
`val_1` INTEGER,
`val_2` INTEGER
);
DELIMITER //
CREATE TRIGGER after_table_insert
BEFORE INSERT
ON table1 FOR EACH ROW
BEGIN
SET #maxval2 = 0;
SELECT max(val_2) + 1 into #maxval2 FROM table1 WHERE val_1 = new.val_1;
IF #maxval2 IS NULL THEN
SET #maxval2 = 0;
END IF;
SET NEW.val_2 = #maxval2;
END//
DELIMITER ;
INSERT INTO table1
(`id`, `val_1`, `val_2`)
VALUES
('1', '1', '0'),
('2', '1', '0'),
('3', '1', '0'),
('4', '2', '0'),
('4', '2', '0');
Query #1
SELECT * FROM table1;
| id | val_1 | val_2 |
| --- | ----- | ----- |
| 1 | 1 | 0 |
| 2 | 1 | 1 |
| 3 | 1 | 2 |
| 4 | 2 | 0 |
| 4 | 2 | 1 |
View on DB Fiddle

How to get partial sum of column and finds which rows has partial sum in mysql

<!-- language: lang-none -->
+----------------------------------------+
| Here is my sample table |
+----------------------------------------+
| Date Person Column_1 Column_2 |
| 15-03-13 A 100 NULL |
| 15-03-13 B NULL 100 |
| 16-03-13 A 100 50 |
| 16-03-13 B NULL NULL |
| 17-03-13 A 100 50 |
| 17-03-13 B 20 30 |
+----------------------------------------+
Now i wanted to do sum(column_1) by date. But i also need to which sum of row includes NULL data.
here is the output result I wanted to achieve
+---------------------------------+
| Date SUM includesNullval |
+---------------------------------+
| 15-03-13 100 true |
| 16-03-13 100 true |
| 17-03-13 120 false |
+---------------------------------+
I don't know to achieve above output. Can anybody give me any idea or solution of this problem?
Note both the form and content of the answer.
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(date DATE NOT NULL
,person CHAR(1) NOT NULL
,column_1 INT NULL
,column_2 INT NULL
,PRIMARY KEY(date,person)
);
INSERT INTO my_table VALUES
('2013-03-15','A', 100, NULL),
('2013-03-15','B',NULL, 100),
('2013-03-16','A', 100, 50),
('2013-03-16','B',NULL, NULL),
('2013-03-17','A', 100, 50),
('2013-03-17','B', 20, 30);
SELECT date, SUM(column_1), MAX(column_2 IS NULL) incnull FROM my_table GROUP BY date;
+------------+---------------+---------+
| date | SUM(column_1) | incnull |
+------------+---------------+---------+
| 2013-03-15 | 100 | 1 |
| 2013-03-16 | 100 | 1 |
| 2013-03-17 | 120 | 0 |
+------------+---------------+---------+
Using a case statement will help you here:
Select Date_Column, sum(column_A),
MAX(CASE when column_A is NULL then 'True' else 'False' end) "Includes_NULL_Value"
from table_x
group by Date_column;
REX TESTER

Update the next row of the target row in MySQL

Suppose I have a table that tracks if a payment is missed like this:
+----+---------+------------+------------+---------+--------+
| id | loan_id | amount_due | due_at | paid_at | missed |
+----+---------+------------+------------+---------+--------+
| 1 | 1 | 100 | 2013-08-17 | NULL | NULL |
| 5 | 1 | 100 | 2013-09-17 | NULL | NULL |
| 7 | 1 | 100 | 2013-10-17 | NULL | NULL |
+----+---------+------------+------------+---------+--------+
And, for example, I ran a query that checks if a payment is missed like this:
UPDATE loan_payments
SET missed = 1
WHERE DATEDIFF(NOW(), due_at) >= 10
AND paid_at IS NULL
Then suppose that the row with id = 1 gets affected. I want the amount_due of row with id = 1 be added to the amount_due of the next row so the table would look like this:
+----+---------+------------+------------+---------+--------+
| id | loan_id | amount_due | due_at | paid_at | missed |
+----+---------+------------+------------+---------+--------+
| 1 | 1 | 100 | 2013-08-17 | NULL | 1 |
| 5 | 1 | 200 | 2013-09-17 | NULL | NULL |
| 7 | 1 | 100 | 2013-10-17 | NULL | NULL |
+----+---------+------------+------------+---------+--------+
Any advice on how to do it?
Thanks
Take a look at this :
SQL Fiddle
MySQL 5.5.32 Schema Setup:
CREATE TABLE loan_payments
(`id` int, `loan_id` int, `amount_due` int,
`due_at` varchar(10), `paid_at` varchar(4), `missed` varchar(4))
;
INSERT INTO loan_payments
(`id`, `loan_id`, `amount_due`, `due_at`, `paid_at`, `missed`)
VALUES
(1, 1, 100, '2013-09-17', NULL, NULL),
(3, 2, 100, '2013-09-17', NULL, NULL),
(5, 1, 100, '2013-10-17', NULL, NULL),
(7, 1, 100, '2013-11-17', NULL, NULL)
;
UPDATE loan_payments AS l
LEFT OUTER JOIN (SELECT loan_id, MIN(ID) AS ID
FROM loan_payments
WHERE DATEDIFF(NOW(), due_at) < 0
GROUP BY loan_id) AS l2 ON l.loan_id = l2.loan_id
LEFT OUTER JOIN loan_payments AS l3 ON l2.id = l3.id
SET l.missed = 1, l3.amount_due = l3.amount_due + l.amount_due
WHERE DATEDIFF(NOW(), l.due_at) >= 10
AND l.paid_at IS NULL
;
Query 1:
SELECT *
FROM loan_payments
Results:
| ID | LOAN_ID | AMOUNT_DUE | DUE_AT | PAID_AT | MISSED |
|----|---------|------------|------------|---------|--------|
| 1 | 1 | 100 | 2013-09-17 | (null) | 1 |
| 3 | 2 | 100 | 2013-09-17 | (null) | 1 |
| 5 | 1 | 200 | 2013-10-17 | (null) | (null) |
| 7 | 1 | 100 | 2013-11-17 | (null) | (null) |
Unfortunately I don't have time at the moment to write out full-blown SQL, but here's the psuedocode I think you need to implement:
select all DISTINCT loan_id from table loan_payments
for each loan_id:
set missed = 1 for all outstanding payments for loan_id (as determined by date)
select the sum of all outstanding payments for loan_id
add this sum to the amount_due for the loan's next due date after today
Refer to this for how to loop using pure MySQL: http://dev.mysql.com/doc/refman/5.7/en/cursors.html
I fixed my own problem by adding a missed_at field. I put the current timestamp ($now) in a variable before I update the first row to missed = 1 and missed_at = $now then I ran this query to update the next row's amount_due:
UPDATE loan_payments lp1 JOIN loan_payments lp2 ON lp1.due_at > lp2.due_at
SET lp1.amount_due = lp2.amount_due + lp1.amount_due
WHERE lp2.missed_at = $now AND DATEDIFF(lp1.due_at, lp2.due_at) <= DAYOFMONTH(LAST_DAY(lp1.due_at))
I wish I could use just use LIMIT 1 to that query but it turns out that it's not possible for an UPDATE query with a JOIN.
So all in all, I used two queries to achieve what I want. It did the trick.
Please advise if you have better solutions.
Thanks!