Update BigQuery repeated record field with condition - google-bigquery

I have a BigQuery table named 'events' with below structure:
Data example:
My goal is to update all rows where country is US and replace it with 'United States', and where country is CH to replace it with 'China'.
I tried to look at other questions where they update repeated record but I didn't find anything specific to what I want and didn't quite understood the method.
(for example this StackOverflow question)
Any help would be much appreciated.

If you're trying to modify only two country codes into country names, following would be one of possible approach.
Note - below query is assuming that country code and region code can conflict each other.
UPDATE sample
SET attributes = ARRAY (
SELECT AS STRUCT a.name, IFNULL(c.name, a.value)
FROM UNNEST(attributes) a
LEFT JOIN UNNEST([
STRUCT('US' AS code, 'United States' AS name), ('CH', 'China')
]) c ON a = ('country', c.code)
)
WHERE ('country', 'US') IN UNNEST(attributes)
OR ('country', 'CH') IN UNNEST(attributes)
;
More general approach would be using a mapping table.
DECLARE countries ARRAY<STRUCT<code STRING, name STRING>> DEFAULT [
('US', 'United States'),
('CH', 'China') --, you can add more mappings of country code and it's name.
];
With the above mapping table, below query will generate same result as first query.
UPDATE sample
SET attributes = ARRAY(
SELECT AS STRUCT a.name, IFNULL(c.name, a.value)
FROM UNNEST(attributes) a
LEFT JOIN UNNEST(countries) c ON a = ('country', c.code)
)
WHERE TRUE
;

Related

Lookup multiple values from one ID or product type

I have a requirement to lookup a predefined list of value countries for specific IDs or products.
This is a one to many or in some cases many to many relationship, so a simple CASE statement cannot suffice.
For example, when selecting ID 24553489 it should return a list of 5 valid countries associated with that ID. And so on for other examples, the list can be one or many.
How would I approach this in sql? I am thinking the easiest way is to create a linked table and use that to create the mapping but there may be an easier way.
Can anyone suggest the best way to approach this?
Edit: So for example...
24553489 should only be linked with UK, South Africa, Spain, Italy, France
23343097 should only be linked with South Africa, Spain, Italy
Etc
Thanks.
I can't tell if you're comfortable with the many to many table, please forgive me if I'm stating the obvious - but here is an example of how to do it and a couple of examples of how to use it. Note that I set up a couple of CTEs to represent your existing data (country table and products table), and then a CTE to represent the map that holds the many to many relationship. After that I defined 2 examples, one showing how to use the map to go from a product to countries, and the other to show how to go from a country to products.
Feel free to post in comments if you want further clarification!
with cteProducts as ( --You should already have a Products table
SELECT *
FROM (VALUES (100, 'Gizmos'), (101, 'Widgets'), (102, 'Thingies')
) as Products(ProductID, ProductName)
), cteCountries as ( --And already have a Country table
SELECT *
FROM (VALUES ('UK', 'United Kingdom'), ('SA', 'South Africa'), ('SP', 'Spain'), ('IT', 'Italy'), ('FR', 'France')
) as Countries(CountryCode, CountryName)
), cteMap as ( --Make a map table that handles the many to many relationship
SELECT *
FROM (VALUES (100, 'UK'), (100, 'SA'), (100, 'IT')
, (101, 'SA'), (101, 'SP'), (101, 'IT')
, (102, 'UK'), (102, 'SP')
) as ProdMap(ProductID, CountryCode)
), cteProdInSpain as ( -- To use it, you can look for products available in a country
SELECT P.*
FROM cteProducts as P
INNER JOIN cteMap as M on M.ProductID = P.ProductID
INNER JOIN cteCountries as C on C.CountryCode = M.CountryCode
WHERE C.CountryName = 'Spain'
), cteWhereBuy as ( -- or where to buy a product
SELECT P.ProductName, C.CountryName
FROM cteCountries as C
INNER JOIN cteMap as M on M.CountryCode = C.CountryCode
INNER JOIN cteProducts as P on P.ProductID = M.ProductID
WHERE P.ProductName = 'Gizmos'
)
--SELECT * FROM cteWhereBuy
SELECT * FROM cteProdInSpain
Thank you all for responses, but I ended up creating a mapping and populating a table from a combination of a cursor and if statements. This is not ideal but without getting too complex this is the only way I could do this.

How do I do a look up from a user table where the user can be in two columns of the record?

This is a very simple idea and want to validate my approach.
I have a record that has the following:
RESERVATIONS
ID
OWNER
RESIDENT
1
VLL
MLL
2
MLL
CVLL
The lookup table looks like this:
USER_TABLE
PLID
USER_CD
BRANCH
1a
VLL
USA
2a
MLL
UK
I want to look up the value in the second table for owner and resident.
You cannot join the ID's together because they are not related. The only relation is from owner to USER_CD
Currently, I do the following:
Select CASE
when reservations.owner = 'VLL' then 1.user_cd
when reservations.owner = 'MLL' then 2.user_cd
end as 'Location'
FROM RESERVATIONS r
Join USER_TABLE 1
on RESERVATIONS.OWNER = 1.USER_CD
join USER_TABLE 2
ON RESERVATIONS.RESIDENT = 2.USER_CD
Is this a correct way to do it or is there another way?
Here is an example implementation:
https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=92f6178194a7e67f15652cbe7cc549c1
First, don't use numerals as table aliases. That's even worse than using arbitrary letters. The aliases should still Mean something to anyone reading or debugging the code.
Second, if you want to translate short branch names to long brnach names, the best way is to have another table with those lookups. In the code below I create that as an inline view (sub-query), though a real table with indexes would be Significantly better.
Then, I believe you're very close already...
WITH
branch_long_name(
name,
long_name
)
AS
(
SELECT 'USA', 'United States'
UNION ALL SELECT 'UK', 'United Kingdom'
-- Note; the UK and Great Britain are not the same
),
branch
AS
(
SELECT
t.*,
COALESCE(n.long_name, t.branch) AS long_name
FROM
user_table AS t
LEFT JOIN
branch_long_name AS n
ON n.name = t.branch
)
SELECT
r.*,
b_o.long_name AS owner_location,
b_r.long_name AS resident_location
FROM
reservations AS r
LEFT JOIN
branch AS b_o -- branch_owner
ON b_o.user_cd = r.owner
LEFT JOIN
branch AS b_r -- branch_resident
ON b_r.user_cd = r.resident
Demo : https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=7cc940ddf40e7cd085cff0fa02b65449

Oracle update and change few columns records in two tables with a single query

I have two tables with names of Locations and Countries in My Oracle database.
Locations table has columns with names location_id(fk), street_address, state_province, and country_id. Countries table has columns with names country_name, country_id, location_id(fk).
I wanna update some columns of Locations and Countries table in a single query.
I did it with the below query but it doesn't work.
update (select l.street_address, l.postal_code, l.city, l.state_province, c.country_name
from hr.countries c, hr.locations l where l.country_id = c.country_id )
set l.street_address = '147 Spadina Ave',
l.postal_code = '122000215',
l.city = 'Toronto',
l.state_province = 'Ontario',
c.country_name = 'US'
where l.location_id = 1200;
but I faced with the error from PL/SQL (ORA-00911 : invalid character).
please help me to fix this issue.
That error, ORA-00911 : invalid character has nothing to do with your update statement and may be coming from another part of your PL/SQL ?
What's actually wrong with your update statement is that you cannot update more than one table through an inline view. So, If you remove the alias l in the set clause and run the statement, you should get ORA-01776:cannot modify more than one base table through a join view
You may however be able to do it using an INSTEAD OF TRIGGER with a view.
In your case, since you are updating the tables based on a given primary key (location_id = 1200), running these two simple updates should be fine. There's no need of joins
UPDATE hr.locations
SET street_address = '147 Spadina Ave',
postal_code = '122000215',
city = 'Toronto',
state_province = 'Ontario'
WHERE location_id = 1200;
UPDATE hr.countries
SET country_name = 'US' -- Toronto in United states, how did that happen?
WHERE country_id IN ( SELECT country_id
FROM hr.locations
WHERE location_id = 1200
);

SQL JOIN when at least one table contains the desired value: Can this be achieved in a single query?

Given a SQL database with the following tables
TABLE Branch
branch_Id INT primary key
TABLE Department
dept_Id INT primary key
branch_Id INT
Table Branch_Desc
branch_Id INT primary key
branch_Desc VARCHAR
TABLE Department_Desc
department_Id INT primary key
department_Desc VARCHAR
Is it possible to return a list of values in the tables above that match the following requirements:
" List all branches departments, branch descriptions and department descriptions where at least one of the descriptions (branch or department) matches a desired value " ?
The trick here is that the query is supposed to return a ONLY a matching descriptions so if we have the following scenario:
Branch: 1, Desc: test
Branch: 2, Desc: another
Department: 1, Desc: another
Department: 1, Desc: something else
With the desired value being 'another' the query should return:
Branch: 2, Branch Desc: another, Department: 1, Dept Desc: another
Branch Desc: 'test' and Dept Desc 'something else' should not be returned, nor should be Branch 1.
Assuming that the table structure cannot be changed is it possible to write a SQL query that would return the correct results?
so far the closest I got was:
SELECT br.branch_id, bd.branch_desc, de.dept_id, dd.dept_desc
FROM branch br
LEFT JOIN branch_desc bd
ON bd.branch_id = br.branch_id
AND UPPER(br.branch_desc) = 'value'
JOIN department de
ON br.branch_id = de.branch_id
LEFT JOIN department_desc dd
ON de.dept_id = dd.dept_id
AND UPPER(dd.dept_desc) = 'value'
This returns the correct values if at least one department contains a description for 'value' however when no departments contain the desired description then no rows are returned (even if there is a branch descriptions that matches 'value')
At this point I think two separate queries are required to achieve the correct results in all four possible scenarios:
Both Branch and Department contain descriptions that match 'value'
Only Branch contains descriptions that match 'value'
Only Department contains descriptions that match 'value'
Neither Branch nor Department contain descriptions that match value
If this is possible (and I have a feeling that it must be) I would appreciate any guidance towards the right direction.
Thanks in advance!
When you have a spec that reads "match this or that" consider in SQL UNION is analoguous to logical OR:
SELECT dept_Id, branch_Id,
Branch_Desc AS branch_or_department_description
FROM Department
NATURAL JOIN
Branch_Desc
UNION
SELECT dept_Id, branch_Id,
department_Desc AS branch_or_department_description
FROM Department
NATURAL JOIN
( SELECT department_Id AS dept_id, department_Desc FROM Department_Desc ) AS d;
To apply the search condition (branch_or_department_description = '<search text>') you hve various choices e.g. create a VIEW, use a derived table, etc.
You have two errors in joins.
Below I fixed those and used OR in the WHERE clause.
SELECT br.branch_id, bd.branch_desc, de.dept_id, dd.dept_desc
FROM branch br
INNER JOIN branch_desc bd ON db.branch_id = br.branch_id
INNER JOIN department de ON de.branch_id = br.branch_id
--note error in your code above and below
INNER JOIN department_desc dd ON dd.dept_id = de.dept_id
WHERE UPPER(br.branch_desc) = 'value'
OR UPPER(dd.dept_desc) = 'value';

Modifying a SELECT query

I have a query like this:
SELECT initials, name
FROM employee e, projects p
WHERE e.country = p.country
Until now, both tables used an abbreviation for the country columns. Like "SWE" for Sweden and "ITA" for Italy.
In the future, the employee table will use names for the country columns. Like "Sweden" and "Italy".
Is it somehow possible to change my query so it can match abbreviations with names? Like "SWE" = "Sweden" and "ITA" = "Italy".
Thanks.
It would be better to have an own country table and the other tables referencing to that.
country table
-------------
id
name
abbreviation
I'd say the best solution is creating a third table where you match the current abbreviation with the full country name. You can then join both tables on that.
CountryTable (countryAbbreviation, countryName)
The select would then be something like this:
SELECT initials, name
FROM employee e JOIN countryTable c ON c.countryName = c.country
JOIN projects p ON p.country = c.countryAbbreviation
Although I fafor the solution by juergen, another solution will be altering the two tables to the new format.
UPDATE employee
SET country = "SWEDEN"
WHERE country = "SWE"
Do this for all the countries you have.
Always in country table, if country name starts with first three letters of the country in the employee table then you can use substring operator
SELECT initials, name
FROM employee e, projects p
WHERE upper(substring(e.country,1,3)) = upper(p.country)