UPDATE rows with no match in other table - sql

I have two tables:
TABLE A
id | user_id | name
------+-----------+-------
1 | 122 | 'Bill'
2 | 123 | 'Jim'
3 | 124 | 'Sally'
TABLE B
id | user_id | title
------+-----------+-------
1 | 122 | 'Boss'
2 | 999 | 'Manager'
3 | 124 | 'Worker'
I want to update all of A with name = 'foo' where there is no matching user_id in table B. Do not update the row if user_id exists in table B.
So in this case it would only update Jim to the name 'foo'.

NOT EXISTS should be simplest, safest & fastest:
UPDATE tbl_a a
SET name = 'foo'
WHERE NOT EXISTS (SELECT FROM tbl_b b WHERE b.user_id = a.user_id);
This also works as expected with NULL values. As opposed to NOT IN. See:
https://wiki.postgresql.org/wiki/Don%27t_Do_This#Don.27t_use_NOT_IN

With Rails:
user_ids_in_table_b = MobelB.pluck(:user_id)
ModelA.where.not(user_id: user_ids_in_table_b).update_all(name: 'foo')
You need to change the MobelA & MobelB models name to your models name in your app.
In pure SQL:
UPDATE table_a
SET name = 'foo'
WHERE user_id NOT IN (SELECT user_id FROM table_b);

Related

Update records in SQL by looking up in different table

I am copying data from few tables in SQL server A to B. I have a set of staging tables in B and need to update some of those staging tables based on updated values in final target table in B.
Example:
Server B:
StagingTable1:
ID | NAME | CITY
1 ABC XYZ
2 BCD XXX
StagingTable2:
ID | AGE | Table1ID(FK)
10 15 1
20 16 2
After Copying StagingTable1 to TargetTable1 (ID's get auto polulated and I get new ID's, now ID 1 becomes 2 and ID 2 becomes 3)
TargetTable1:
ID | NAME | CITY
1 PQR YYY (pre-existing record)
2 ABC XYZ
3 BCD XXX
So now before I can copy the StagingTable2 I need to update the Table1ID column in it by correct values from TargetTable1.
StagingTable2 should become:
ID | AGE | Table1ID(FK)
10 15 2
20 16 3
I am writing a stored procedure for this and not sure how do I lookup and update the records in staging tables?
Assuming that (name, city) tuples are unique in StagingTable1 and TargetTable1, you can use an updatable common table expression to generate the new mapping and assign the corresponding values:
with cte as (
select st2.Table1ID, tt1.id
from StagingTable2 st2
inner join StagingTable1 st1 on st1.ID = st2.Table1ID
inner join TargetTable1 tt1 on tt1.name = st1.name and tt1.city = st1.city
)
update cte set Table1ID = id
Demo on DB Fiddle - content of StagingTable2 after the update:
id | age | Table1ID
-: | --: | -------:
10 | 15 | 2
20 | 16 | 3

Comparing different columns in SQL for each row

after some transformation I have a result from a cross join (from table a and b) where I want to do some analysis on. The table for this looks like this:
+-----+------+------+------+------+-----+------+------+------+------+
| id | 10_1 | 10_2 | 11_1 | 11_2 | id | 10_1 | 10_2 | 11_1 | 11_2 |
+-----+------+------+------+------+-----+------+------+------+------+
| 111 | 1 | 0 | 1 | 0 | 222 | 1 | 0 | 1 | 0 |
| 111 | 1 | 0 | 1 | 0 | 333 | 0 | 0 | 0 | 0 |
| 111 | 1 | 0 | 1 | 0 | 444 | 1 | 0 | 1 | 1 |
| 112 | 0 | 1 | 1 | 0 | 222 | 1 | 0 | 1 | 0 |
+-----+------+------+------+------+-----+------+------+------+------+
The ids in the first column are different from the ids in the sixth column.
In a row are always two different IDs that are matched with each other. The other columns always have either 0 or 1 as a value.
I am now trying to find out how many values(meaning both have "1" in 10_1, 10_2 etc) two IDs have on average in common, but I don't really know how to do so.
I was trying something like this as a start:
SELECT SUM(CASE WHEN a.10_1 = 1 AND b.10_1 = 1 then 1 end)
But this would obviously only count how often two ids have 10_1 in common. I could make something like this for example for different columns:
SELECT SUM(CASE WHEN (a.10_1 = 1 AND b.10_1 = 1)
OR (a.10_2 = 1 AND b.10_1 = 1) OR [...] then 1 end)
To count in general how often two IDs have one thing in common, but this would of course also count if they have two or more things in common. Plus, I would also like to know how often two IDS have two things, three things etc in common.
One "problem" in my case is also that I have like ~30 columns I want to look at, so I can hardly write down for each case every possible combination.
Does anyone know how I can approach my problem in a better way?
Thanks in advance.
Edit:
A possible result could look like this:
+-----------+---------+
| in_common | count |
+-----------+---------+
| 0 | 100 |
| 1 | 500 |
| 2 | 1500 |
| 3 | 5000 |
| 4 | 3000 |
+-----------+---------+
With the codes as column names, you're going to have to write some code that explicitly references each column name. To keep that to a minimum, you could write those references in a single union statement that normalizes the data, such as:
select id, '10_1' where "10_1" = 1
union
select id, '10_2' where "10_2" = 1
union
select id, '11_1' where "11_1" = 1
union
select id, '11_2' where "11_2" = 1;
This needs to be modified to include whatever additional columns you need to link up different IDs. For the purpose of this illustration, I assume the following data model
create table p (
id integer not null primary key,
sex character(1) not null,
age integer not null
);
create table t1 (
id integer not null,
code character varying(4) not null,
constraint pk_t1 primary key (id, code)
);
Though your data evidently does not currently resemble this structure, normalizing your data into a form like this would allow you to apply the following solution to summarize your data in the desired form.
select
in_common,
count(*) as count
from (
select
count(*) as in_common
from (
select
a.id as a_id, a.code,
b.id as b_id, b.code
from
(select p.*, t1.code
from p left join t1 on p.id=t1.id
) as a
inner join (select p.*, t1.code
from p left join t1 on p.id=t1.id
) as b on b.sex <> a.sex and b.age between a.age-10 and a.age+10
where
a.id < b.id
and a.code = b.code
) as c
group by
a_id, b_id
) as summ
group by
in_common;
The proposed solution requires first to take one step back from the cross-join table, as the identical column names are super annoying. Instead, we take the ids from the two tables and put them in a temporary table. The following query gets the result wanted in the question. It assumes table_a and table_b from the question are the same and called tbl, but this assumption is not needed and tbl can be replaced by table_a and table_b in the two sub-SELECT queries. It looks complicated and uses the JSON trick to flatten the columns, but it works here:
WITH idtable AS (
SELECT a.id as id_1, b.id as id_2 FROM
-- put cross join of table a and table b here
)
SELECT in_common,
count(*)
FROM
(SELECT idtable.*,
sum(CASE
WHEN meltedR.value::text=meltedL.value::text THEN 1
ELSE 0
END) AS in_common
FROM idtable
JOIN
(SELECT tbl.id,
b.*
FROM tbl, -- change here to table_a
json_each(row_to_json(tbl)) b -- and here too
WHERE KEY<>'id' ) meltedL ON (idtable.id_1 = meltedL.id)
JOIN
(SELECT tbl.id,
b.*
FROM tbl, -- change here to table_b
json_each(row_to_json(tbl)) b -- and here too
WHERE KEY<>'id' ) meltedR ON (idtable.id_2 = meltedR.id
AND meltedL.key = meltedR.key)
GROUP BY idtable.id_1,
idtable.id_2) tt
GROUP BY in_common ORDER BY in_common;
The output here looks like this:
in_common | count
-----------+-------
2 | 2
3 | 1
4 | 1
(3 rows)

I want to assign the name of table1 in table 2 thanks

UPDATE table1, (select top 1 [ID],chef,formateur,techni from table1 order by Num desc)
AS x SET table2.ID = x.[ID], table2.Nom = x.[chef], table2.Nom = x.[formateur],
table2.Nom = x.[techni]
WHERE table2.mission="chef" and table2.mission="Formateur" and table2.mission="techni" ;
returns
table2 table 1
ID | mission |Nom| ID |chef|Formateur|techni|
-------------------------------- ----------------------------
1 | chef | | 1 |nom1| nom2 | nom3|
2 | Formateur | | **Result**
3 | techni | | ID | mission | Nom|
--------------------------
1 | chef | nom1|
2 | Formateur| nom2|
3 | techni | nom3|
I want to assign the name of table1 in table 2.
Please i need your help :)
Data structure does not make sense. Table2 has only 3 records?
Your attempted example is updating Table1 when it appears you really want to update Table2. Why do you want to pull from just last record of Table1?
But try:
UPDATE Table2,
(SELECT TOP 1 Table1.ID, Table1.chef, Table1.formateur, Table1.techni
FROM Table1 ORDER BY Table1.ID DESC) AS Q
SET Table2.nom = Switch([mission]="chef",[chef],[mission]="formateur",[formateur],[mission]="techni",[techni]);

Sql select query based on a column value

I have a Table1 like this:
ApplicableTo IdApplicable
---------------------------
Dept 1
Grade 3
section 1
Designation 2
There other tables like:
tblDept:
ID Name
1 dept1
2 baking
3 other
tblGrade:
ID Name
1 Grd1
2 Manager
3 gr3
tblSection:
id Name
1 Sec1
2 sec2
3 sec3
tblDesignation:
id Name
1 Executive
2 Developer
3 desig3
What I need is a query for table1 in such a way that gives me
ApplicableTo (table1)
Name (from the relevant table based on the value in `ApplicableTo` column)
Is this possible?
Desired Result:
eg: ApplicableTo IdApplicable Name
Dept 1 dept1
grade 3 gr3
Section 1 sec1
Designation 2 Developer.
This is the result I desire.
You could do something like the following so the applicable to becomes part of the JOIN predicate:
SELECT t1.ApplicableTo, t1.IdApplicable, n.Name
FROM Table1 AS t1
INNER JOIN
( SELECT ID, Name, 'Dept' AS ApplicableTo
FROM tblDept
UNION ALL
SELECT ID, Name, 'Grade' AS ApplicableTo
FROM tblGrade
UNION ALL
SELECT ID, Name, 'section' AS ApplicableTo
FROM tblSection
UNION ALL
SELECT ID, Name, 'Designation' AS ApplicableTo
FROM tblDesignation
) AS n
ON n.ID = t1.IdApplicable
AND n.ApplicableTo = t1.ApplicableTo
I would generally advise against this approach, although it may seem like a more consice approach, you would be better having 4 separate nullable columns in your table:
ApplicableTo | IdDept | IdGrade | IdSection | IdDesignation
-------------+--------+---------+-----------+---------------
Dept | 1 | NULL | NULL | NULL
Grade | NULL | 3 | NULL | NULL
section | NULL | NULL | 1 | NULL
Designation | NULL | NULL | NULL | 2
This allows you to use foreign keys to manage your referential integrity properly.
You can use CASE here,
SELECT ApplicableTo,
IdApplicable,
CASE
WHEN ApplicableTo = 'Dept' THEN (SELECT Name FROM tblDept WHERE tblDept.ID = IdApplicable)
WHEN ApplicableTo = 'Grade' THEN (SELECT Name FROM tblGrade WHERE tblGrade.ID = IdApplicable)
WHEN ApplicableTo = 'Section' THEN (SELECT Name FROM tblSection WHERE tblSection.ID = IdApplicable)
WHEN ApplicableTo = 'Designation' THEN (SELECT Name FROM tblDesignation WHERE tblDesignation.ID = IdApplicable)
END AS 'Name'
FROM Table1
The easiest way to achieve that is to add an extra column in table1 to keep the table where id is refferred to. Otherwise you can't know in which table the applicable id is reffered to.
Or you can create the applicable id in a way that you can extract the table afterwords from it for example a1 for id 1 in tblDept. And then use [case] (http://dev.mysql.com/doc/refman/5.0/en/case.html) (for mysql) in order to make the correct Join.

Select records not in another table with additional criteria

I am working on an ACCESS DB.
I have 1 table (tblData) with 1 column ( DataId) and 3 entries:
tblData (A)
+--------+
| DataId |
+--------+
| 1 |
| 2 |
| 3 |
+--------+
Another table (tblSelections) contains 3 columns (id, dataid, userid) and has 3 entries:
tblSelections (B)
+----+--------+---------+
| id | dataid | userid |
+----+--------+---------+
| 1 | 1 | 5 |
| 2 | 2 | 5 |
| 3 | 3 | 2 |
+----+--------+---------+
How can I select the records from table A (tblData) which are not in tbl B (tblSelections) for a certain 'userid'?
For 'userid' 5 the query must return 'DataId' 3 from table A as dataid 1 & 2 are already present in table B for userid 5.
For 'userid' 2 the query must return 'DataId' 1 & 2 from table A as dataid 3 is already present in table B for userid 2.
For 'userid' 1 the query must return 'DataId' 1, 2 & 3 from table A as no records are present in table B for userid 1
Use EXISTS or IN for queries like yours:
SELECT *
FROM tblData
WHERE DataId NOT IN
(
SELECT dataid
FROM tblSelections
WHERE userid = 5
);
SELECT *
FROM tblData
WHERE NOT EXISTS
(
SELECT *
FROM tblSelections
WHERE tblSelections.dataid = tblData.DataId AND tblSelections.userid = 5
);
You can use an outer join to select all records, then put a condition in the where clause that a non-nullable column in b is null. This will give you all records in a that do not have a matching row in b according to the join conditions.
This query assumes that you have a parameter or variable named #userid that represents the user ID to search against.
select
a.*
from tblData a
left join tblSelections b on b.dataid = a.dataid and b.userid = #userid
where b.id is null