Update unique table SQL - sql

Hello i have this table.
ADDR | STATE | ID
CRA.20 15 | REP | (null)
CRA.20 15 | REP | (null)
CRA.20 15 | REP | (null)
CRA.20 15 | PRI | RR_88_JK
I need transform this table ..
ADDR | STATE | ID
CRA.20 15 | REP | RR_88_JK
CRA.20 15 | REP | RR_88_JK
CRA.20 15 | REP | RR_88_JK
CRA.20 15 | PRI | RR_88_JK
I have this QRY but not function. You have a one idea?.
UPDATE TABLE_A A
SET a.ID = b.ID
WHERE EXISTS
(SELECT b.ID
FROM TABLE_A B
WHERE a.ADDR = B.ADDR AND b.STATE = 'PRI')
and A.STATE = 'REP';
Thanks.

You need a subquery to set the value:
UPDATE TABLE_A A
SET a.ID = (SELECT b.ID
FROM TABLE_A B
WHERE a.ADDR = B.ADDR AND b.STATE = 'PRI' AND rownum = 1
)
WHERE EXISTS (SELECT b.ID
FROM TABLE_A B
WHERE a.ADDR = B.ADDR AND b.STATE = 'PRI'
) AND
A.STATE = 'REP';
B is only known in the subquery, not in the outer query.

The following will do what you want:
UPDATE TABLE_A A
SET a.ID = (SELECT MIN(ID)
FROM TABLE_A
WHERE ID IS NOT NULL AND
STATE = 'PRI')
WHERE ID IS NULL AND
STATE = 'REP'
Best of luck.

Related

Delete rows from table using JOIN - SQL Server

I have a table_A -
id | name | is_active |
----+---------+------------+
1 | jon | 1 |
2 | ham | 0 |
3 | gary | null |
I have a table_B -
id | name |
----+---------+
1 | jon |
2 | ham |
I want to remove rows from table B that have is_active value as 0 OR null in table A. So I'm thinking about an INNER JOIN on id column and applying a WHERE clause.
DELETE ROWS from table_B B
INNER JOIN table_A A ON B.id = A.id
WHERE A.is_active = 0 OR A.is_active IS NULL
I don't want any additional columns or changes in table B after the above query. Is this the right way to do it?
Instead of JOIN, use exists:
DELETE FROM table_B
WHERE EXISTS (SELECT 1
FROM table_A A
WHERE A.id = table_B.id AND
(A.is_active = 0 OR A.is_active is null)
);
You need to say which table you want to delete from, use the alias if you have one:
DELETE B
FROM table_B B
INNER JOIN table_A A ON B.id = A.id
WHERE (A.is_active = 0 OR A.is_active IS NULL);

Select from multiple table, eliminating duplicates values

I have these tables and values:
Person Account
------------------ -----------------------
ID | CREATED_BY ID | TYPE | DATA
------------------ -----------------------
1 | 1 | T1 | USEFUL DATA
2 | 2 | T2 |
3 | 3 | T3 |
4 | 4 | T2 |
Person_account_link
--------------------------
ID | PERSON_ID | ACCOUNT_ID
--------------------------
1 | 1 | 1
2 | 1 | 2
3 | 2 | 3
4 | 3 | 4
I want to select all persons with T1 account type and get the data column, for the others persons they should be in the result without any account information.
(I note that person 1 has two accounts : account_id_1 and account_id_2 but only one row must be displayed (priority for T1 type if exist otherwise null)
The result should be :
Table1
-----------------------------------------------------
PERSON_ID | ACCOUNT_ID | ACCOUNT_TYPE | ACCOUNT_DATA
-----------------------------------------------------
1 | 1 | T1 | USEFUL DATA
2 | NULL | NULL | NULL
3 | NULL | NULL | NULL
4 | NULL | NULL | NULL
You can do conditional aggregation :
SELECT p.id,
MAX(CASE WHEN a.type = 'T1' THEN a.id END) AS ACCOUNT_ID,
MAX(CASE WHEN a.type = 'T1' THEN 'T1' END) AS ACCOUNT_TYPE,
MAX(CASE WHEN a.type = 'T1' THEN a.data END) AS ACCOUNT_DATA
FROM person p LEFT JOIN
Person_account_link pl
ON p.id = pl.person_id LEFT JOIN
account a
ON pl.account_id = a.id
GROUP BY p.id;
You would need an outer join, starting with Person and then to the other two tables. I would also aggregate with group by and min to tackle the situation where a person would have two or more T1 accounts. In that case one of the data is taken (the min of them):
select p.id person_id,
min(a.id) account_id,
min(a.type) account_type,
min(a.data) account_data
from Person p
left join Person_account_link pa on p.id = pa.person_id
left join Account a on pa.account_id = a.id and a.type = 'T1'
group by p.id
In Postgres, I like to use the FILTER keyword. In addition, the Person table is not needed if you only want persons with an account. If you want all persons:
SELECT p.id,
MAX(a.id) FILTER (a.type = 'T1') as account_id,
MAX(a.type) FILTER (a.type = 'T1') as account_type,
MAX(a.data) FILTER (a.type = 'T1') as account_data
FROM Person p LEFT JOIN
Person_account_link pl
ON pl.person_id = p.id LEFT JOIN
account a
ON pl.account_id = a.id
GROUP BY p.id;

Counting Records with Unique Field Value

Source Table
Assuming I have a table called MyTable with the content:
+----------+------+
| Category | Code |
+----------+------+
| A | A123 |
| A | B123 |
| A | C123 |
| B | A123 |
| B | B123 |
| B | D123 |
| C | A123 |
| C | E123 |
| C | F123 |
+----------+------+
I'm trying to count the number of Code values which are unique to each category.
Desired Result
For the above example, the result would be:
+----------+-------------+
| Category | UniqueCodes |
+----------+-------------+
| A | 1 |
| B | 1 |
| C | 2 |
+----------+-------------+
Since C123 is unique to A, D123 is unique to B, and E123 & F123 are unique to C.
What I've Tried
I'm able to obtain the result for a single category (e.g. C) using a query such as:
SELECT COUNT(a.Code) AS UniqueCodes
FROM
(
SELECT MyTable.Code
FROM MyTable
WHERE MyTable.Category = "C"
) a
LEFT JOIN
(
SELECT MyTable.Code
FROM MyTable
WHERE MyTable.Category <> "C"
) b
ON a.Code = b.Code
WHERE b.Code IS NULL
However, whilst I can hard-code a query for each category, I cannot seem to construct a single query to calculate this for every possible Category value.
Here is what I've tried:
SELECT c.Category,
(
SELECT COUNT(a.Code)
FROM
(
SELECT MyTable.Code
FROM MyTable
WHERE MyTable.Category = c.Category
) a
LEFT JOIN
(
SELECT MyTable.Code
FROM MyTable
WHERE MyTable.Category <> c.Category
) b
ON a.Code = b.Code
WHERE b.Code IS NULL
) AS UniqueCodes
FROM
(
SELECT MyTable.Category
FROM MyTable
GROUP BY MyTable.Category
) c
Though, the c.Category is not defined within the scope of the nested SELECT query.
Could anyone advise how I could obtain the desired result?
I would use NOT EXISTS & do aggregation :
select category, count(*)
from MyTable t
where not exists (select 1 from MyTable t1 where t1.code = t.code and t1.category <> t.category)
group by category;
You can use two levels of aggregation:
select minc as category, count(*)
from (select code, min(category) as minc, max(category) as maxc
from t
group by code
) as c
where minc = maxc
group by minc;
This would also work:
select category, count(*) from(
select a.category, b.count from mytable a join (
select code, count(category) as count
from mytable
group by code
having count(category) = 1
) b on b.code = a.code
) c group by category
Learning from #isaace's answer, I also came up with this -
SELECT MyTable.Category, COUNT(*)
FROM
MyTable INNER JOIN
(SELECT Code FROM MyTable GROUP BY Code HAVING COUNT(Category) = 1) a
ON MyTable.Code = a.Code
GROUP BY MyTable.Category

Joining two tables with condition

I have tables like this
TABLE A
id | name
1 | a
2 | b
3 | c
TABLE B
id | ida | prp | prpval
1 | 1 | visible | true
2 | 1 | active | true
3 | 2 | visible | false
4 | 2 | active | true
5 | 3 | visible | true
6 | 3 | active | true
Table A and Table B are connected by id-ida.
I want to display all names(name) from Table A that have property visible(prp) from TABLE B set to 'true' (prpval). Without any duplicates. So in this example i want to display 'a' and 'b'. How to do it?
select a.name
from a
join b on a.id = b.aid
group by a.name
having sum(case when prp = 'visible' and prpvalue = 'true' then 1 else 0 end) > 0
A simple answer would be:
SELECT A.name
FROM A
INNER JOIN B
ON A.id = B.ida
WHERE B.prp = 'visible'
AND B.prpval = 'true'
SELECT DISTINCT a.name
FROM A a join B b on a.id = b.ida
WHERE b.prp = 'visible' and b.prpval = 'true'
Because you don't want duplicates, I would approach this using an exists clause:
select *
from tableA a
where exists (select 1
from tableB b
where b.ida = a.id and b.prp = 'visitor' and b.prpval = 'true'
);
For performance, create an index on tableB(ida, prp, prpval).
select a.name
from A as a
join B as b on a.id=b.ida
and b.prpval='True' and b.prp='visible'

SQL query uses "wrong" join

I have an query which gives me the wrong result.
Tables:
A
+----+
| id |
+----+
| 1 |
| 2 |
+----+
B
+----+----+
| id | x | B.id = A.id
+----+----+
| 1 | 1 |
| 1 | 1 |
| 1 | 0 |
+----+----+
C
+----+----+
| id | y | C.id = A.id
+----+----+
| 1 | 1 |
| 1 | 2 |
+----+----+
What I want to do: Select all rows from A. For each row in A count in B all x with value 1 and all x with value 0 with B.id = A.id. For each row in A get the minimum y from C with C.id = A.id.
The result I am expecting is:
+----+------+--------+---------+
| id | min | count1 | count 2 |
+----+------+--------+---------+
| 1 | 1 | 2 | 1 |
| 2 | NULL | 0 | 0 |
+----+------+--------+---------+
First Try:
This doesn't work.
SELECT a.id,
MIN(c.y),
SUM(IF(b.x = 1, 1, 0)),
SUM(IF(b.x = 0, 1, 0))
FROM a
LEFT JOIN b
ON ( a.id = b.id )
LEFT JOIN c
ON ( a.id = c.id )
GROUP BY a.id
+----+------+--------+---------+
| id | min | count1 | count 2 |
+----+------+--------+---------+
| 1 | 1 | 4 | 2 |
| 2 | NULL | 0 | 0 |
+----+------+--------+---------+
Second Try:
This works but I am sure it has a bad performance.
SELECT a.id,
MIN(c.y),
b.x,
b.y
FROM a
LEFT JOIN (SELECT b.id, SUM(IF(b.x = 1, 1, 0)) x, SUM(IF(b.x = 0, 1, 0)) y FROM b) b
ON ( a.id = b.id )
LEFT JOIN c
ON ( a.id = c.id )
GROUP BY a.id
+----+------+--------+---------+
| id | min | count1 | count 2 |
+----+------+--------+---------+
| 1 | 1 | 2 | 1 |
| 2 | NULL | 0 | 0 |
+----+------+--------+---------+
Last Try:
This works too.
SELECT x.*,
SUM(IF(b.x = 1, 1, 0)),
SUM(IF(b.x = 0, 1, 0))
FROM (SELECT a.id,
MIN(c.y)
FROM a
LEFT JOIN c
ON ( a.id = c.id )
GROUP BY a.id) x
LEFT JOIN b
ON ( b.id = x.id )
GROUP BY x.id
Now my question is: Is the last one the best choise or is there a way to write this query with just one select statement (like in the first try)?
Your joins are doing cartesian products for a given value, because there are multiple rows in each table.
You can fix this by using count(distinct) rather than sum():
SELECT a.id, MIN(c.y),
count(distinct (case when b.x = 1 then b.id end)),
count(distinct (case when b.x = 0 then b.id end))
FROM a
LEFT JOIN b
ON ( a.id = b.id )
LEFT JOIN c
ON ( a.id = c.id )
GROUP BY a.id;
You can also fix this by pre-aggregating b (and/or c). And you would need to take that approach if your aggregation function were something like the sum of a column in b.
EDIT:
You are correct. The above query counts the distinct values of B, but B contains rows that are exact duplicates. (Personally, I think having a column with the name id that has duplicates is a sign of poor design, but that is another issue.)
You could solve it by having a real id in the b table, because then the count(distinct) would count the correct values. You can also solve it by aggregating the two tables before joining them in:
SELECT a.id, c.y, x1, x0
FROM a
LEFT JOIN (select b.id,
sum(b.x = 1) as x1,
sum(b.x = 0) as x0
from b
group by b.id
) b
ON ( a.id = b.id )
LEFT JOIN (select c.id, min(c.y) as y
from c
group by c.id
) c
ON ( a.id = c.id );
Here is a SQL Fiddle for the problem.
EDIT II:
You can get it in one statement, but I'm not so sure that it would work on similar data. The idea is that you can count all the cases where x = 1 and then divide by the number of rows in the C table to get the real distinct count:
SELECT a.id, MIN(c.y),
coalesce(sum(b.x = 1), 0) / count(distinct coalesce(c.y, -1)),
coalesce(sum(b.x = 0), 0) / count(distinct coalesce(c.y, -1))
FROM a
LEFT JOIN b
ON ( a.id = b.id )
LEFT JOIN c
ON ( a.id = c.id )
GROUP BY a.id;
It is a little tricky, because you have to handle NULLs to get the right values. Note that this is counting the y value to get a distinct count from the C table. Your question re-enforces why it is a good idea to have a unique integer primary key in every table.