How to determine what's changed between database records - mysql

Presume first, that the following table exists in a MySQL Database
|----|-----|-----|----|----|-----------|--------------|----|
| id | rid | ver | n1 | n2 | s1 | s2 | b1 |
|----|-----|-----|----|----|-----------|--------------|----|
| 1 | 1 | 1 | 0 | 1 | Hello | World | 0 |
| 2 | 1 | 2 | 1 | 1 | Hello | World | 0 |
| 3 | 1 | 3 | 0 | 0 | Goodbye | Cruel World | 0 |
| 4 | 2 | 1 | 0 | 0 | Hello | Doctor | 1 |
| 5 | 2 | 2 | 0 | 0 | Hello | Nurse | 1 |
| 6 | 3 | 1 | 0 | 0 | Dippity | Doo-Dah | 1 |
|----|-----|-----|----|----|-----------|--------------|----|
Question
How do I write a query to determine whether for any given rid, what changed between the most recent version and the version immediately preceding it (if any) such that it produces something like this:
|-----|-----------------|-----------------|-----------------|
| rid | numbers_changed | strings_changed | boolean_changed |
|-----|-----------------|-----------------|-----------------|
| 1 | TRUE | TRUE | FALSE |
| 2 | FALSE | TRUE | FALSE |
| 3 | n/a | n/a | n/a |
|-----|-----------------|-----------------|-----------------|
I think that I should be able to do this by doing a cross-join between the table and itself but I can't resolve how to perform this join to get the desired output.
I need to generate this "report" for a table with 10's of columns and 1-10 versions of 100's of records (resulting in 1000's of rows). Note the particular design of the database is not my own and altering the structure of the database (at this time) is not an acceptable approach.
The actual format of the output isn't important - and if it simplifies the query getting a "full breakdown" of what changed for each "change set" would also be acceptable, for example
|-----|-----|-----|----|----|----|----|----|
| rid | old | new | n1 | n2 | s1 | s2 | b1 |
|-----|-----|-----|----|----|----|----|----|
| 1 | 1 | 2 | Y | N | N | N | N |
| 1 | 2 | 3 | Y | Y | Y | Y | N |
| 2 | 4 | 5 | N | N | N | Y | N |
|-----|-----|-----|----|----|----|----|----|
Note that it is also ok, in this case to omit rid records which only have a single version, as for the purposes of this report I only care about records that have changed and getting a separate list of records that haven't changed is an easy query

You can join every row with the following one with
select *
from history h1
join history h2
on h2.rid = h1.rid
and h2.id = (
select min(h.id)
from history h
where h.rid = h1.rid
and h.id > h1.id
);
Then you just need to compare every column from the two rows like h1.n1 <> h2.n1 as n1.
The full query would be:
select h1.rid, h1.id as old, h2.id as new
, h1.n1 <> h2.n1 as n1
, h1.n2 <> h2.n2 as n2
, h1.s1 <> h2.s1 as s1
, h1.s2 <> h2.s2 as s2
, h1.b1 <> h2.b1 as b1
from history h1
join history h2
on h2.rid = h1.rid
and h2.id = (
select min(h.id)
from history h
where h.rid = h1.rid
and h.id > h1.id
);
Result:
| rid | old | new | n1 | n2 | s1 | s2 | b1 |
|-----|-----|-----|----|----|----|----|----|
| 1 | 1 | 2 | 1 | 0 | 0 | 0 | 0 |
| 1 | 2 | 3 | 1 | 1 | 1 | 1 | 0 |
| 2 | 4 | 5 | 0 | 0 | 0 | 1 | 0 |
Demo: http://sqlfiddle.com/#!9/2e5d12/5
If the columns can contain NULLs, You might need something like NOT h1.n1 <=> h2.n1 as n1. <=> is a NULL-save equality check.
If the version within a rid group is guaranteed to be consecutive, you can simplify the JOIN to
from history h1
join history h2
on h2.rid = h1.rid
and h2.ver = h1.ver + 1
Demo: http://sqlfiddle.com/#!9/2e5d12/7

Related

Hierarchical query in MySQL

Having that table structure & data:
| ID | PARENT | FIELD_1 | FIELD_2 | RATING |
+------------------------------------------+
| 1 | NULL | F1V1 | F2V1 | 10 |
| 2 | 1 | F1V2 | F2V2 | 20 |
| 3 | 2 | F1V3 | F2V3 | 30 |
| 4 | 3 | F1V4 | F2V4 | 40 |
Is there a way of getting a result like this one:
| ID | F_1 | F_2 | P_F_1 | P_F_2 | G_F_1 | G_F_2 | S_R |
+-------------------------------------------------------------+
| 1 | F1V1 | F2V1 | NULL | NULL | NULL | NULL | 10 |
| 2 | F1V2 | F2V2 | F1V1 | F2V1 | NULL | NULL | 30 |
| 3 | F1V3 | F2V3 | F1V2 | F2V2 | F1V1 | F2V1 | 60 |
| 4 | F1V4 | F2V4 | F1V3 | F2V3 | F1V2 | F2V2 | 90 |
What I actually want, as you can see, is for every record if there are parent (P), grandparent (G), etc records (the recursion may go for 4 levels or any other finite number that is already known), the fields of their ancestors prefixed (that can happen programmatically outside of the query) and a SUM (or any other GROUP function) that calculates the values recursively as well.
ex record #4:
ID = 4
FIELD_1 AS F_1 = F1V4
FIELD_2 AS F_2 = F2V4
PARENT_FIELD_1 AS P_F_1 = F1V3
...
GRANDPARENT_FIELD_2 AS G_F_2 = F2V2
SUM_RATING AS S_M = (40 + 30 + 20) = 90**
NOTE:
Even though record #1 is an ancestor of record #4 (grand-grandparent) its rating is not calculated in the sum because it is not contained in the query.
This simplest way:
SELECT t.id,
t.field_1 f_1,
t.field_2 f_2,
p.field_1 p_f_1,
p.field_2 p_f_2,
g.field_1 g_f_1,
g.field_2 g_f_2,
t.rating + COALESCE(p.rating,0) + COALESCE(g.rating,0) s_r
FROM table_name t
LEFT JOIN table_name p
ON p.id = t.parent
LEFT JOIN table_name g
ON g.id = p.parent
And add left joins, additions and field selections to the known level of recursion.

MySQL: how to show rows where count is 0 using group by?

MySQL
I have a table, two columns column'N' and column'V', the value of 'V' is either 1 or 0 (may be other values, so don't use SUM()).
mysql> select * from counter;
+------+------+
| N | V |
+------+------+
| A | 0 |
......
| D | 1 |
+------+------+
I wish to count how many 0 and how many 1, as below:
mysql> select N, V, count(V) from counter group by N,V;
+------+------+----------+
| N | V | count(V) |
+------+------+----------+
| A | 0 | 2 |
| A | 1 | 7 |
| B | 0 | 7 |
| B | 1 | 2 |
| C | 0 | 3 |
| D | 1 | 3 |
+------+------+----------+
The problem is I want to show rows where the count(V) is 0. In this case, my expected result should look like as below:
+------+------+----------+
| N | V | count(V) |
+------+------+----------+
| A | 0 | 2 |
| A | 1 | 7 |
| B | 0 | 7 |
| B | 1 | 2 |
| C | 0 | 3 |
| C | 1 | 0 | **
| D | 0 | 0 | **
| D | 1 | 3 |
+------+------+----------+
How can I achieve this? And if the table is large, how to get the best performance?
Here's one option creating a cartesian product between N and V with a cross join. Then you can use an outer join to get all results:
select t.n, t.v, count(c.n)
from (select distinct t1.n, t2.v
from counter t1 cross join counter t2) t left join
counter c on t.n = c.n and t.v = c.v
group by t.n, t.v
SQL Fiddle Demo

how can I calculate from two tables in mysql

I have 2 tables bellow
0 --> Pending
1 --> Success
2 --> Fail
table : mntnc
+-------+-------+-------+
| id | own | sts |
+-------+-------+-------+
| 1 | BN | 1 |
| 2 | BB | 2 |
| 3 | BN | 1 |
| 4 | BD | 1 |
| 5 | BD | 0 |
table : istlsi
+-------+-------+-------+
| id | own | sts |
+-------+-------+-------+
| 1 | BN | 1 |
| 2 | BB | 1 |
| 3 | BB | 1 |
| 4 | BC | 0 |
| 5 | BD | 2 |
of the two tables above, I want to add both of them to be the table below
+-------+-----------+-----------+-----------+
| own | success | fail | pending |
+-------+-----------+-----------+-----------+
| BN | 3 | 0 | 0 |
| BB | 2 | 1 | 0 |
| BD | 1 | 1 | 1 |
| BC | 0 | 0 | 1 |
The two key points here:
Union tables (I aliased result to B)
Use sum(case...) for each column.
First we union both tables together as an inline view.
We then use a case statement for each desired column and evaluate the status setting the value to 1 or 0 depending on sts value. and then sum those...
SELECT own
, sum(case when sts=1 then 1 else 0 end) as Success
, sum(case when sts=2 then 1 else 0 end) as Fail
, sum(case when sts=0 then 1 else 0 end) as Pending
FROM ( SELECT ID, own, sts
FROM mntnc
UNION ALL
SELECT id, own, sts
FROM istlsi
) B
GROUP BY own

How to check if specific set of ID's exists?

I have a source table (piece of it):
+--------------------+
| E M P L O Y E E |
+--------------------+
| ID | EQUIPMENT |
+--------------------+
| 1 | tv,car,phone |
| 2 | car,phone |
| 3 | tv,phone |
+----+---------------+
After normalization process I ended with two new tables:
+----------------+
| DICT_EQUIPMENT |
+----------------+
| ID | EQUIPMENT |
+----------------+
| 1 | tv |
| 2 | car |
| 3 | phone |
+----+-----------+
+---------------------+
| SET_EQUIPMENT |
+----+--------+-------+
| ID | SET_ID | EQ_ID |
+----+--------+-------+
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 1 | 3 |
| 4 | 2 | 2 |
| 5 | 2 | 3 |
| 6 | 3 | 1 |
| 7 | 3 | 3 |
+----+--------+-------+
(the piece/part)
+-----------------+
| E M P L O Y E E |
+-----------------+
| ID | EQ_SET_ID |
+-----------------+
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
+----+------------+
And now when I want to find correct SET_ID I can write something like this:
SELECT SET_ID
FROM SET_EQUIPMENT S1,
SET_EQUIPMENT S2,
SET_EQUIPMENT S3
WHERE S1.SET_ID = S2.SET_ID
AND S2.SET_ID = S3.SET_ID
AND S1.EQ_ID = 1
AND S2.EQ_ID = 2
AND S3.EQ_ID = 3;
Maybe any ideas for optimize this query? how find the correct set?
First, you should use explicit join syntax for the method you are using:
SELECT S1.SET_ID
FROM SET_EQUIPMENT S1 JOIN
SET_EQUIPMENT S2
ON S1.SET_ID = S2.SET_ID JOIN
SET_EQUIPMENT S3
ON S2.SET_ID = S3.SET_ID
WHERE S1.EQ_ID = 1 AND
S2.EQ_ID = 2 AND
S3.EQ_ID = 3;
Commas in a from clause are quite outdated. (And, this fixes a syntax error in your query.)
An alternative method is to use group by with a having clause:
SELECT S.SET_ID
FROM SET_EQUIPMENT S
GROUP BY S.SET_ID
HAVING SUM(CASE WHEN S.EQ_ID = 1 THEN 1 ELSE 0 END) > 0 AND
SUM(CASE WHEN S.EQ_ID = 2 THEN 1 ELSE 0 END) > 0 AND
SUM(CASE WHEN S.EQ_ID = 3 THEN 1 ELSE 0 END) > 0;
Which method works better depends on a number of factors -- for instance, the database engine you are using, the size of the tables, the indexes on the tables. You have to test which method works better on your system.
You've normalised wrongly. Get rid of set_equipment
Change to have three tables: employee, equipment, employee_equipment.
If you're looking for the equipment for a given employee you want to use:
select id, equipment
from equipment eq
inner join employee_equipment ee on eq.id = ee.eq_id
inner join employee emp on emp.id = ee.emp_id
where emp.id = 2

Having difficulty with the WHERE IN clause for MySQL

Ok, I realize this may be incredibly simple, but my brain is frozen right now. Need a bit of assistance with this query. Let's break it down.
I have two tables (per this example) and I want to update a single table "undeliverable" status
Customers Table (tbl_customers):
+------------+-------------+
| customerID | custAcctNum |
+------------+-------------+
| 1 | 100100121 |
| 2 | 100100122 |
| 3 | 100100123 |
| 4 | 100100124 |
| 5 | 100100125 |
+------------+-------------+
Address Table (tbl_address):
+-----------+------------+---------------+
| addressID | customerID | undeliverable |
+-----------+------------+---------------+
| 1 | 1 | 0 |
| 2 | 2 | 0 |
| 3 | 3 | 0 |
| 4 | 4 | 0 |
| 5 | 5 | 0 |
+-----------+------------+---------------+
Dataset with "undeliverable" Customer Account numbers (custAcctNum)
100100121, 100100123, 100100124
And the query will update the Address Table to this
+-----------+------------+---------------+
| addressID | customerID | undeliverable |
+-----------+------------+---------------+
| 1 | 1 | 1 |
| 2 | 2 | 0 |
| 3 | 3 | 1 |
| 4 | 4 | 1 |
| 5 | 5 | 0 |
+-----------+------------+---------------+
This is the query that I have tried to use
UPDATE tbl_address
SET undeliverable = 1 WHERE
( SELECT custAcctNum FROM tbl_customers AS c
INNER JOIN tbl_address AS a ON a.customerID = c.customerID )
IN ( 100100121, 100100123, 100100124);
Any suggestions? Thanks!
Use mysql's multiple-table update syntax:
update tbl_Address t
join custAcctNum c
on c.customerid = t.customerid
set t.undeliverable = 1
where c.custAcctNum in (100100121, 100100123, 100100124)
UPDATE tbl_address
SET (undeliverable = 1)
WHERE customerID IN (
SELECT customerID
FROM tbl_customers
WHERE custAcctNum IN (100100121, 100100123, 100100124)
);