Updating a column in PL/SQL - sql

(Using PL/SQL anonymous program block)
I have a table tblROUTE2 of Mexican state highways:
+-----------------+------------+---------+----------+----------+----------------------------+-----------+--------+
| TYPE | ADMN_CLASS | TOLL_RD | RTE_NUM1 | RTE_NUM2 | STATEROUTE | LENGTH_KM | STATE |
+-----------------+------------+---------+----------+----------+----------------------------+-----------+--------+
| Paved Undivided | Federal | N | 81 | | Tamaulipas Federal Hwy 81 | 124.551 | NULL |
| Paved Undivided | Federal | N | 130 | | Hidalgo Federal Hwy 130 | 76.347 | NULL |
| Paved Undivided | Federal | N | 130 | | Mexico Federal Hwy 130 | 68.028 | NULL |
+-----------------+------------+---------+----------+----------+----------------------------+-----------+--------+
and tblSTATE2 of Mexican states:
+------+-----------------------+---------+-----------+
| CODE | NAME | POP1990 | AREA_SQMI |
+------+-----------------------+---------+-----------+
| MX02 | Baja California Norte | 1660855 | 28002.325 |
| MX03 | Baja California Sur | 317764 | 27898.191 |
| MX18 | Nayarit | 824643 | 10547.762 |
+------+-----------------------+---------+-----------+
I need to update the STATE field in tblROUTE2 with the CODE field found in tblSTATE2, based on the route name in tblROUTE2. Basically, I need to somehow take the first string or two (some routes have two names)-- before the string 'Federal'-- of the STATEROUTE field in tblROUTE2 and make sure it matches with the string in the NAME field in tblSTATE2. Then since the states are matched with a CODE, update those codes in the STATE field of tblROUTE2.
I have started a code:
DECLARE
state_code
tblROUTE2.STATE%TYPE;
state_name
tblSTATE2.NAME%TYPE;
BEGIN
SELECT STATE, NAME
INTO state_code
FROM tblROUTE2 r, tblSTATE2 s
WHERE STATEROUTE LIKE '%Federal';
END;
As well, I will need to remove the state name from the route name. For example, the string in STATEROUTE 'Tamaulipas Federal Hwy' becomes 'Federal Hwy'. I have started a code, not sure if it's right:
UPDATE tblROUTE2
SET STATEROUTE = TRIM(LEADING FROM 'Federal');

Using MERGE update :
MERGE INTO tblROUTE2 A
USING
(
SELECT CODE, NAME FROM tblSTATE2
) B
ON
(
upper(SUBSTR(A.STATEROUTE, 0, INSTR(UPPER(A.STATEROUTE), UPPER('FEDERAL'))-2)) = upper(B.NAME)
)
WHEN MATCHED THEN UPDATE
SET A.STATE = B.CODE;
Here in FIDDLE I've replicated your tables and added additional record where STATEROUTE matches one of the records in NAME. Although Fiddle return an error, I ran it in my Oracle DB, and one record was updated correctly as the following screenshot:

Related

Postgres: How do I count occurrences of each enum value when they exist in columns as an array?

I have an enum State which can contain values like CA, NY, etc.
If I have a table Users , with a column states that contains an array of State values, so for example {CA, NY} how can I write a query to count the users grouped by each State value? so for {CA, NY} that should count 1 for CA and 1 for NY
So If I had records like:
| id | states |
| -- | ------- |
| 1 | {CA,NY} |
| 2 | {CA} |
| 3 | {NV,CA} |
I would expect a query to output:
| State | count |
| ----- | ----- |
| CA | 3 |
| NV | 1 |
| NY | 1 |
The first piece of advice is to normalise your data. You are breaking 2nd Normal form by holding multiple pieces of information in a single column.
Assuming you can't change that, then you will need to SPLIT the data like this
enter link description here
and you can then COUNT() and group it.

Using Snowflake SQL how do you find two records and then change another one based on those records to a predefined record using a local variable?

Using SQL how do you use two records to find a place, hold onto that place and use that record to replace 'Nonsense' value with that held onto place? I am going to show what I have been able to write so far, but then write out what I am still trying to figure out:
SELECT * FROM "TABLES". "ACCTS_OF_SUPERHEROS".;
DECLARE #count_rows INT = 0;
DECLARE #row_total INT = 0;
DECLARE #refAcctNum INT = 0;
DECLARE #selectedPlaceName TINYTEXT;
SET #row_total = SELECT COUNT (*)
WHILE countRows < row_total
for each acct_num store value in refAcctNum.
Using refAcctNum find place: "Gotham City", "Central City", "Metropolis", "Smallville", "Star City", "Fawcett City" store that in selectedPlaceName.
If refAccountNumber has Nonsense then replace with selectedPlaceName record
otherwise add + 1 to countRows and repeat.
END
Current table data; "ACCTS_OF_SUPERHEROS" table:
| row | acct_num | exact_address | place
| --- | -------- |------------------|--------
| 1 | 049403 | 344 Clinton Str | Metropolis
| 2 | 049403 | 344 Clinton Str | Nonsense
| 3 | 049206 | 1007 Mountain Dr | Gotham City
| 4 | 049206 | 1007 Mountain Dr | Gotham City
| 5 | 049206 | 1096 Show Dr. | Fawcett City
| 6 | 049206 | 1096 Show Dr. | Nonsense
| 7 | 049206 | NULL | Nonsense
| 8 | 049291 | 1938 Sullivan Pl | Smallville
| 9 | 049293 | 700 Hamilton Str | Central City
| 10 | 049396 | 800 Nonsense Way | Nonsense
| 11 | 049396 | NULL | Nonsense
Desired output:
| row | acct_num | exact_address | place
| --- | -------- |------------------|--------
| 1 | 049403 | 344 Clinton Str | Metropolis
| 2 | 049403 | 344 Clinton Str | Metropolis
| 3 | 049206 | 1007 Mountain Dr | Gotham City
| 4 | 049206 | 1007 Mountain Dr | Gotham City
| 5 | 049206 | 1096 Show Dr. | Fawcett City
| 6 | 049206 | 1096 Show Dr. | Fawcett City
| 7 | 049206 | NULL | Fawcett City
| 8 | 049291 | 1938 Sullivan Pl | Smallville
| 9 | 049293 | 700 Hamilton Str | Central City
| 10 | 049396 | 800 Tidal Way | Star City
| 11 | 049396 | NULL | Star City
You can use window functions:
select t.*,
max(case when place <> 'Nonsense' then place end) over (partition by acct_num) as imputed_place
from t;
This returns NULL if all the rows are 'Nonsense' for a given acct_num. You can use COALESCE() to replace the value with something else.
I was reading through the available list of window functions in Snowflake and think you're going to need a new window function for this. Perhaps someone can find a more built-in way, but anyway here's a user defined table function REPLACE_WITH_LKG implemented as a window function that will replace a bad value with the last known good value. As long as I was going to write it, I thought it may as well be general purpose, so it matches "bad" values using a regular expression and JavaScript RegExp options.
create or replace function REPLACE_WITH_LKG("VALUE" string, "REGEXP" string, "REGEXP_OPTIONS" string)
returns table(LKG_VALUE string)
language javascript
strict immutable
as
$$
{
initialize: function (argumentInfo, context) {
this.lkg = "";
},
processRow: function (row, rowWriter, context) {
const rx = new RegExp(row.REGEXP, row.REGEXP_OPTIONS);
if (!rx.test(row.VALUE)) {
this.lkg = row.VALUE;
}
rowWriter.writeRow({LKG_VALUE: this.lkg});
},
finalize: function (rowWriter, context) {},
}
$$;
select S.*, LKG.LKG_VALUE as PLACE
from superhero S, table(REPLACE_WITH_LKG(PLACE, 'Nonsense', 'ig')
over(partition by null order by "ROW")) LKG;
;
A note on performance; the way the data shows this the're no partition other than the entire table. That's because the one obvious place to partition, by account, won't work. Row 10 is getting its value from what would be a different window if using account, so the way the sample data appears it needs to be a window that spans the entire table. This will not parallelize well and should be avoided for very large tables.

sqlite3: Create tables with many rows or one table with more columns

I'm asking for a best practive when creating some tables for localization of an Web Interface in sqlite3
In my first intetion I wanted to create a table with the different languages, and another on for the Messeage Code and Entries.
tblLanguage
+------------+-------------+---------+
| idLangCode | txtLangName | txtCode |
+------------+-------------+---------+
| 1 | English | en |
| 2 | German | de |
| 3 | French | fr |
| 4 | Spanish | es |
| 5 | Chinese | zh |
+------------+-------------+---------+
tblMessageText
+----+-------+--------------------------+------------+
| Id | Code | Message | LanguageID |
+----+-------+--------------------------+------------+
| 1 | 20500 | Set Point changed | 1 |
| 2 | 20500 | Sollwert geändert | 2 |
| 3 | 20500 | Punto de ajuste cambiado | 5 |
+----+-------+--------------------------+------------+
So in the second table I would have several rows with the same Message Code but whith an different language text.
The other possibility would be to have just one table with just one row for each Message Code but an Column for each language.
tblMessageTextMulti
+----+-------+-------------------+-------------------+--------------------------+
| id | Code | txtMessageText_EN | txtMessageText_DE | txtMessageText_ES |
+----+-------+-------------------+-------------------+--------------------------+
| 1 | 20500 | Set Point changed | Sollwert geändert | Punto de ajuste cambiado |
+----+-------+-------------------+-------------------+--------------------------+
My team likes the second solution with just one table more, because it just has one entry for each Message Code, and you see all Language text side by side.
What I like on the first solution is, that I could dynmically Query the langugage with just on line in php:
$query = 'SELECT * FROM qryInfoMessage WHERE idLangCode=' .$Language;
For the second solution with one table I can not store the query itself in the database, because I have to change the Column name in my query dynmically. So I have to put this together in php.
$query = 'SELECT Code, txtMessageText_EN FROM tblMessageTextMulti;
What I dont show here is that my query is much more complex, whith string substitution and date time conversion.
Beside that, what are the advantages or disadvantages of this solutions. Which one should be more perfomant and what is the best practice?

Can I merge SQL Server tables if they have not exactly the same structure?

I have two tables, source and target.
source:
+--------+------+-------------+
| Name | Year | City |
+--------+------+-------------+
| Toyota | 2002 | Los Angeles |
| Seat | 2012 | Madrid |
+--------+------+-------------+
target:
+----+---------+------+----------+
| ID | Name | Year | City |
+----+---------+------+----------+
| 1 | Bentley | 1969 | Budapest |
| 2 | Toyota | 1988 | New York |
| 3 | Ford | 2001 | Tokyo |
| 4 | Seat | 1995 | Madrid |
| 5 | Bugatti | 1995 | London |
+----+---------+------+----------+
I want to merge source into target. I know the MERGE command, it's fine. The issue is that the source has no column ID so that it won't match.
Since Name column in both are unique I only need to match if they are equal, then if not exists insert into target, if exists update target.
I could do it using NOT EXIST statement, but we are talking about billions of rows so MERGE would be a much quicker solution.
So can I somehow set the MERGE command to take only that column into account when matching?
Yes, you can:
MERGE target t
USING source s
ON t.name = s.name
WHEN NOT MATCHED
INSERT (Name, Year, City)
VALUES (s.Name, s.Year, s.City)
WHEN MATCHED THEN
UPDATE SET Year = s.Year,
City = s.City;
If your ID column in target is not IDENTITY column you can create sequence to populate it.

SQL for calculated column that chooses from value in own row

I have a table in which several indentifiers of a person may be stored. In this table I would like to create a single calculated identifier column that stores the best identifier for that record depending on what identifiers are available.
For example (some fictional sample data) ....
Table = "Citizens"
Id | LastName | DL-No | SS-No | State-Id-No | Calculated
------------------------------------------------------------------------
1 | Smith | NULL | 374-784-8888 | 7383204848 | ?
2 | Jones | JG892435262 | NULL | NULL | ?
3 | Trask | TSK73948379 | NULL | 9276542119 | ?
4 | Clinton | CL231429888 | 543-123-5555 | 1840430324 | ?
I know the order in which I would like choose identifiers ...
Drivers-License-No
Social-Security-No
State-Id-No
So I would like the calculated identifier column to be part of the table schema. The desired results would be ...
Id | LastName | DL-No | SS-No | State-Id-No | Calculated
------------------------------------------------------------------------
1 | Smith | NULL | 374-784-8888 | 7383204848 | 374-784-8888
2 | Jones | JG892435262 | NULL | 4537409273 | JG892435262
3 | Trask | NULL | NULL | 9276542119 | 9276542119
4 | Clinton | CL231429888 | 543-123-5555 | 1840430324 | CL231429888
IS this possible? If so what SQL would I use to calculate what goes in the "Calculated" column?
I was thinking of something like ..
SELECT
CASE
WHEN ([DL-No] is NOT NULL) THEN [DL-No]
WHEN ([SS-No] is NOT NULL) THEN [SS-No]
WHEN ([State-Id-No] is NOT NULL) THEN [State-Id-No]
AS "Calculated"
END
FROM Citizens
The easiest solution is to use coalesce():
select c.*,
coalesce([DL-No], [SS-No], [State-ID-No]) as calculated
from citizens c
However, I think your case statement will also work, if you fix the syntax to use when rather than where.