I have the following thing I want to achieve:
A DB that stores translations in different languages. With a single query get all strings in the desired language and if that translation doesn't exist use the second best language as fallback etc. For each language the fallbacks may differ (e.g. FR-DE-EN vs. DE-EN-FR).
I've implemented it with pgsql via a crosstab query and wanted to translate this to SQL Server but got kind of stuck there. I think that PIVOT would be the language feature to achieve my desired outcome but haven't yet figured out how to use it properly.
MWE definitions and testdata:
-- load tablefunc extension for crosstab
drop extension if exists tablefunc;
create extension tablefunc;
-- crosstab only allows single column - define int and varchar tuples for this purpose
DROP TYPE IF EXISTS intT; CREATE TYPE intT AS (module int, id int );
DROP TYPE IF EXISTS strT; CREATE TYPE strT AS (lang varchar, txt varchar);
drop table if exists texts;
drop table if exists langs;
create table texts
( module int not null
, id int not null
, lang varchar not null
, txt varchar not null);
create table langs -- for each language (first) store up to 3 languages (lang) and their priority (lower = would be used first)
( first varchar not null
, lang varchar not null
, priority int not null);
insert into texts (module, id, lang, txt) values
(0,0,'def','HelloDEF'),
(0,1,'def','WorldDEF'),
(0,0,'en','Hello'),
(0,1,'en','World'),
(0,0,'de','Hallo'),
(0,1,'de','Welt'),
(0,0,'jp','Konnichiwa'),
(0,1,'fr','Monde'),
(1,0,'def','Switzerland'),
(1,0,'de','Schweiz'),
(1,0,'fr','Suisse'),
(1,0,'jp','Suisu');
insert into langs (first, lang, priority) values
('jp','jp',0),
('jp','en',1),
('jp','def',2),
('en','en',0),
('en','def',1),
('en','def',2),
('de','de',0),
('de','en',1),
('de','def',2),
('fr','fr',0),
('fr','de',1),
('fr','def',2);
Query (pgsql):
select (mod_id).*, (coalesce(a,b,c)).* -- unpack tuple types here to get nice table
from crosstab($$
select (module,id) as mod_id, priority, (lang,txt) as lang_txt
from texts
join langs using (lang)
where first = 'fr' --! language goes here
and module = 0 --! module integer goes here
order by id, priority asc
$$,$$
select generate_series(0,2) -- always return 0,1,2 here.
$$) as ct (mod_id intT, a strT, b strT, c strT);
Output:
module | id | lang | txt
--------+----+------+-------
0 | 0 | de | Hallo
0 | 1 | fr | Monde
As far as I understood the question, this result can be achieved with standard SQL without the need for pivoting the data. Simple ROW_NUMBER() should be enough. The query below will work in SQL Server and Postgres.
Sample data
create table #texts
( module int not null
, id int not null
, lang varchar(50) not null
, txt varchar(50) not null);
create table #langs -- for each language (first) store up to 3 languages (lang) and their priority (lower = would be used first)
( first varchar(50) not null
, lang varchar(50) not null
, priority int not null);
insert into #texts (module, id, lang, txt) values
(0,0,'def','HelloDEF'),
(0,1,'def','WorldDEF'),
(0,0,'en','Hello'),
(0,1,'en','World'),
(0,0,'de','Hallo'),
(0,1,'de','Welt'),
(0,0,'jp','Konnichiwa'),
(0,1,'fr','Monde'),
(1,0,'def','Switzerland'),
(1,0,'de','Schweiz'),
(1,0,'fr','Suisse'),
(1,0,'jp','Suisu');
insert into #langs (first, lang, priority) values
('jp','jp',0),
('jp','en',1),
('jp','def',2),
('en','en',0),
('en','def',1),
('en','def',2),
('de','de',0),
('de','en',1),
('de','def',2),
('fr','fr',0),
('fr','de',1),
('fr','def',2);
Query
I have taken your inner query and added a ROW_NUMBER there. It is clear that we simply need to pick only the row with the highest priority for each id (that's why there is PARTITION BY id and ORDER BY priority in the ROW_NUMBER definition). If you want results for several modules at once, not just for one specific module, then add module to PARTITION BY clause.
SELECT
#texts.module
,#texts.id
,#langs.priority
,#langs.lang
,#texts.txt
,ROW_NUMBER() OVER (PARTITION BY #texts.id ORDER BY #langs.priority) AS rn
FROM
#texts
INNER JOIN #langs ON #langs.lang = #texts.lang
WHERE
#langs.first = 'fr' --! language goes here
AND #texts.module = 0 --! module integer goes here
ORDER BY
#texts.id, #langs.priority asc
;
Result
+--------+----+----------+------+----------+----+
| module | id | priority | lang | txt | rn |
+--------+----+----------+------+----------+----+
| 0 | 0 | 1 | de | Hallo | 1 |
| 0 | 0 | 2 | def | HelloDEF | 2 |
| 0 | 1 | 0 | fr | Monde | 1 |
| 0 | 1 | 1 | de | Welt | 2 |
| 0 | 1 | 2 | def | WorldDEF | 3 |
+--------+----+----------+------+----------+----+
Final Query
WITH
CTE
AS
(
SELECT
#texts.module
,#texts.id
,#langs.priority
,#langs.lang
,#texts.txt
,ROW_NUMBER() OVER (PARTITION BY #texts.id ORDER BY #langs.priority) AS rn
FROM
#texts
INNER JOIN #langs ON #langs.lang = #texts.lang
WHERE
#langs.first = 'fr' --! language goes here
AND #texts.module = 0 --! module integer goes here
)
SELECT
module
,id
,lang
,txt
FROM CTE
WHERE rn = 1
ORDER BY
id
;
Result
+--------+----+------+-------+
| module | id | lang | txt |
+--------+----+------+-------+
| 0 | 0 | de | Hallo |
| 0 | 1 | fr | Monde |
+--------+----+------+-------+
Clean up
drop table #texts;
drop table #langs;
I am trying to select and trim all the entries from a table using the following statement:
SELECT TRIM(*) FROM TABLE
But I get an error. Is there a way to return all entries selected so they are trimmed for blank characters at the beginning and end of each string?
You need to specify each string column by hand:
SELECT TRIM(col1), --LTRIM(RTRIM(...)) If RDBMS is SQL Server
TRIM(col2),
TRIM(col3),
TRIM(col4)
-- ...
FROM table
There is another problem with your proposal. * is placeholder for each column in table so there will be problem with trimming date/decimal/spatial data ....
Addendum
Using Oracle 18c Polymorphic Table Functions(provided code is just PoC, there is a space for a lot of improvements):
CREATE TABLE tab(id INT, d DATE,
v1 VARCHAR2(100), v2 VARCHAR2(100), v3 VARCHAR2(100) );
INSERT INTO tab(id, d,v1, v2, v3)
VALUES (1, SYSDATE, ' aaaa ', ' b ', ' c');
INSERT INTO tab(id, d,v1, v2, v3)
VALUES (2, SYSDATE+1, ' afasd', ' ', ' d');
COMMIT;
SELECT * FROM tab;
-- Output
.----.-----------.-----------.-----------.-----.
| ID | D | V1 | V2 | V3 |
:----+-----------+-----------+-----------+-----:
| 1 | 02-MAR-18 | aaaa | b | c |
:----+-----------+-----------+-----------+-----:
| 2 | 03-MAR-18 | afasd | | d |
'----'-----------'-----------'-----------'-----'
And table function:
CREATE OR REPLACE PACKAGE ptf AS
FUNCTION describe(tab IN OUT dbms_tf.table_t)RETURN dbms_tf.describe_t;
PROCEDURE FETCH_ROWS;
END ptf;
/
CREATE OR REPLACE PACKAGE BODY ptf AS
FUNCTION describe(tab IN OUT dbms_tf.table_t) RETURN dbms_tf.describe_t AS
new_cols DBMS_TF.COLUMNS_NEW_T;
BEGIN
FOR i IN 1 .. tab.column.count LOOP
IF tab.column(i).description.type IN ( dbms_tf.type_varchar2) THEN
tab.column(i).pass_through:=FALSE;
tab.column(i).for_read:= TRUE;
NEW_COLS(i) :=
DBMS_TF.COLUMN_METADATA_T(name=> tab.column(i).description.name,
type => tab.column(i).description.type);
END IF;
END LOOP;
RETURN DBMS_TF.describe_t(new_columns=>new_cols, row_replication=>true);
END;
PROCEDURE FETCH_ROWS AS
inp_rs DBMS_TF.row_set_t;
out_rs DBMS_TF.row_set_t;
rows PLS_INTEGER;
BEGIN
DBMS_TF.get_row_set(inp_rs, rows);
FOR c IN 1 .. inp_rs.count() LOOP
FOR r IN 1 .. rows LOOP
out_rs(c).tab_varchar2(r) := TRIM(inp_rs(c).tab_varchar2(r));
END LOOP;
END LOOP;
DBMS_TF.put_row_set(out_rs, replication_factor => 1);
END;
END ptf;
And final call:
CREATE OR REPLACE FUNCTION trim_col(tab TABLE)
RETURN TABLE pipelined row polymorphic USING ptf;
SELECT *
FROM trim_col(tab); -- passing table as table function argument
.----.-----------.-------.-----.----.
| ID | D | V1 | V2 | V3 |
:----+-----------+-------+-----+----:
| 1 | 02-MAR-18 | aaaa | b | c |
:----+-----------+-------+-----+----:
| 2 | 03-MAR-18 | afasd | - | d |
'----'-----------'-------'-----'----'
db<>fiddle demo
I have to handle a table PRODUCTS which is created to accommodate tree structure of products. It is done to handle situations when one product can contain several others (e.g. one package product holds several other positions). So, I'm making a function that takes OrderDetails, and it must iterate through all PRODUCTS and list out the child products for each product listed. I am facing an issue that I have to iterate through tree of unknown depth. Please, give me an idea how to do it.
I've implemented it in the table below with the function listed along with it. But in that solution the depth of listing is limited to 1, and what i want to do is to fetch all depth of the tree.
Here is the code:
CREATE OR REPLACE FUNCTION foo()RETURNS text AS
$body$
DECLARE _row RECORD;
_result text := '';
_child_row RECORD;
_count integer := -1;
_marker integer := 1;
BEGIN
FOR _row IN SELECT * FROM tree_products
LOOP
_result := _result || _marker || ' ' || _row.name;
_count := (SELECT count(product_id) FROM tree_products WHERE parent_id = _row.product_id);
IF _count > 0 THEN
FOR _child_row IN SELECT * FROM tree_products WHERE parent_id = _row.product_id
LOOP
_result := _result || ' ' || _child_row.name;
END LOOP;
END IF;
_marker := _marker =1;
END LOOP;
END;
$body$
LANGUAGE plpgsql
UPD Done this usign WITH CTE, but the groupiing problem occured:
CREATE OR REPLACE FUNCTION public.__foo (
)
RETURNS SETOF refcursor AS
$body$
DECLARE _returnvalue refcursor;
_q text;
BEGIN
_q :='
WITH RECURSIVE r_p (product_id, name, parent_id) AS -- 1
(SELECT t_p.product_id, t_p.name , t_p.parent_id -- 2
FROM tree_products t_p
WHERE t_p.product_id = 1
UNION ALL
SELECT t_c.product_id, t_c.name, t_c.parent_id -- 3
FROM r_p t_p, tree_products t_c
WHERE t_c.parent_id = t_p.product_id)
SELECT product_id, name, parent_id -- 4
FROM r_p;';
OPEN _returnvalue FOR EXECUTE (_q);
RETURN NEXT _returnvalue;
END
$body$
LANGUAGE 'plpgsql'
VOLATILE
CALLED ON NULL INPUT
SECURITY INVOKER
COST 100 ROWS 1000;
I want to sibling products be under their respectiveparents, I wonder how to write Grouping statement...
UPD Sorry, the definition of the tree_products is the following:
CREATE TABLE public.tree_products (
product_id INTEGER DEFAULT nextval('ree_products_product_id_seq'::regclass) NOT NULL,
name VARCHAR,
parent_id INTEGER,
CONSTRAINT ree_products_pkey PRIMARY KEY(product_id)
)
WITH (oids = false);
UPD: SAMPLE OUTPUT:
product_id | name | parent_id
---------------------------------------
1 | promo | NULL
3 | fork | 1
4 | spoon | 1
6 | can | 1
10 | big can | 3
11 | small can | 4
12 | large spoon | 6
13 | mega fork | 3
14 | super duper | 6
DESIRED OUTPUT:
product_id | name | parent_id
---------------------------------------
1 | promo | NULL
3 | fork | 1
10 | big can | 3
13 | mega fork | 3
4 | spoon | 1
11 | small can | 4
6 | can | 1
12 | large spoon | 6
14 | super duper | 6
So, the fetched table has structure of the real tree, like the follwing:
- promo
- fork
- big can
- mega fork
- spoon
- small can
- can
- large can
- super duper
This SQLFiddle traverses the tree top-down, keeping an list of parent row numbers in an array, essentially a "parent row position list".
It then sorts the results by the parent-list.
WITH RECURSIVE tree(product_id, name, parentlist) AS (
SELECT product_id, name, ARRAY[ row_number() OVER (ORDER BY product_id) ]
FROM tree_products
WHERE parent_id IS NULL
UNION
SELECT tp.product_id, tp.name, array_append(parentlist, row_number() OVER (ORDER BY tp.product_id))
FROM tree_products tp
INNER JOIN tree t
ON (tp.parent_id = t.product_id)
)
SELECT *
FROM tree
ORDER BY parentlist;
I have 2 tables: errorlookup and errors.
errorlookup has 2 columns: codes and description.
The codes are of length 2.
errors has 2 columns id and errorcodes.
The errorcodes are of length 40 meaning they code store 20 error codes for each id.
I need to display all the description associated with the id by substring the errorcodes and matching with code in errorlookup table.
Sample data for errorlookup:
codes:description
12:Invalid
22:Inactive
21:active
Sample data for errors:
id:errorcodes
1:1221
2:2112
3:1222
I cant use LIKE as it would result in too many errors. I want the errorcodes column to be broken down into strings of length 2 and then joined with the errorlookup.
How can it be done?
If you really cannot alter the tables structure, here's another approach:
Create an auxilary numbers table:
CREATE TABLE numbers
( i INT NOT NULL
, PRIMARY KEY (i)
) ;
INSERT INTO numbers VALUES
( 1 ) ;
INSERT INTO numbers VALUES
( 2 ) ;
--- ...
INSERT INTO numbers VALUES
( 100 ) ;
Then you could use this:
SELECT err.id
, err.errorcodes
, num.i
, look.codes
, look.descriptionid
FROM
( SELECT i, 2*i-1 AS pos --- odd numbers
FROM numbers
WHERE i <= 20 --- 20 pairs
) num
CROSS JOIN
errors err
JOIN
errorlookup look
ON look.codes = SUBSTR(err.errorcodes, pos, 2)
ORDER BY
err.errorcodes
, num.i ;
Test at: SQL-Fiddle
ID ERRORCODES I CODES DESCRIPTIONID
1 1221 1 12 Invalid
1 1221 2 21 Active
3 1222 1 12 Invalid
3 1222 2 22 Inactive
2 2112 1 21 Active
2 2112 2 12 Invalid
I think the cleanest solution is to "normalize" your errocodes table using a PL/SQL function. That way you can keep the current (broken) table design, but still access its content as if it was properly normlized.
create type error_code_type as object (id integer, code varchar(2))
/
create or replace type error_table as table of error_code_type
/
create or replace function unnest_errors
return error_table pipelined
is
codes_l integer;
i integer;
one_row error_code_type := error_code_type(null, null);
begin
for err_rec in (select id, errorcodes from errors) loop
codes_l := length(err_rec.errorcodes);
i := 1;
while i < codes_l loop
one_row.id := err_rec.id;
one_row.code := substr(err_rec.errorcodes, i, 2);
pipe row (one_row);
i := i + 2;
end loop;
end loop;
end;
/
Now with this function you can do something like this:
select er.id, er.code, el.description
from table(unnest_errors) er
join errorlookup el on el.codes = er.code;
You can also create a view based on the function to make the statements a bit easier to read:
create or replace view normalized_errorcodes
as
select *
from table(unnest_errors);
Then you can simply reference the view in the real statement.
(I tested this on 11.2 but I believe it should work on 10.x as well)
I think you're on the right track with LIKE. MySQL has an RLIKE function that allows matching by regular expression (I don't know if it's present in Oracle.) You could use errorlookup.code as a pattern to match against errors.errorcodes. The (..)* pattern is used to prevent things like "1213" from matching, for example, "21".
SELECT *
FROM error
JOIN errorlookup
WHERE errorcodes RLIKE CONCAT('^(..)*',code)
ORDER BY id;
+------+----------+------+
| id | errorcode| code |
+------+----------+------+
| 1 | 11 | 11 |
| 2 | 1121 | 11 |
| 2 | 1121 | 21 |
| 3 | 21313245 | 21 |
| 3 | 21313245 | 31 |
| 3 | 21313245 | 32 |
| 4 | 21 | 21 |
+------+----------+------+