Replace empty/null string with result from another record - sql

I have a language table and want retrieve specific records for a selected language. However, when there is no translation present I want to get the translation of another language.
TRANSLATIONS
TAG LANG TEXT
"prog1" | 1 | "Programmeur"
"prog1" | 2 | "Programmer"
"prog1" | 3 | "Programista"
"prog2" | 1 | ""
"prog2" | 2 | "Category"
"prog2" | 3 | "Kategoria"
"prog3" | 1 | "Actie"
"prog3" | 2 | "Action"
"prog3" | 3 | "Dzialanie"
PROGDATA
ID | COL1 | COL2
1 | "data" | "data"
2 | "data" | "data"
3 | "data" | "data"
If I want translations from language 3 based on the ID's in table PROGDATA then I can do:
SELECT TEXT FROM TRANSLATIONS, PROGDATA
WHERE TRANSLATIONS.TAG="prog" & PROGDATA.ID
AND TRANSLATIONS.LANG=3
which would give me:
"Programista"
"Kategoria"
"Dzialanie"
In case of language 1 I get an empty string on the second record:
"Programmeur"
""
"Actie"
How can I replace the empty string with, for example, the translation of language 2?
"Programmeur"
"Category"
"Actie"
I tried nesting a new select query in an IIf() function but that obviously did not work.
SELECT
IIf(TEXT="",
(SELECT TEXT FROM TRANSLATIONS, PROGDATA
WHERE TRANSLATIONS.TAG="prog" & PROGDATA.ID
AND TRANSLATIONS.LANG=2),TEXT)
FROM TRANSLATIONS, PROGDATA
WHERE TRANSLATIONS.TAG="prog" & PROGDATA.ID
AND TRANSLATIONS.LANG=3

A SWITCH or CASE statement may work well. But try this:
SELECT
IIf(TEXT="",
(SELECT TEXT AS TEXT_OTHER FROM TRANSLATIONS, PROGDATA
WHERE TRANSLATIONS.TAG="prog" & PROGDATA.ID
AND TRANSLATIONS.LANG=2),TEXT) AS TEXT_FINAL
I am using TEXTOTHER and TEXTFINAL to reduce ambiguity in your field names. Sometimes this helps.
You may even need to apply the principle to the table name:
(SELECT TEXT AS TEXT_OTHER FROM TRANSLATIONS AS TRANSLATIONS_ALT...
Also, make sure your criterion is correct: an empty string, not a Null value.
IIf(TEXT="", ...
IIf(ISNULL(TEXT), ...

You can join TRANSLATIONS table again to get a "default" translation and use a CASE in the SELECT Statement.
SELECT
CASE
WHEN ISNULL(Translation.TEXT,"") = "" THEN DefaultLang.TEXT
ELSE Translation.Text
END
FROM TRANSLATIONS AS DefaultLang,TRANSLATIONS as Translation, PROGDATA
WHERE
DefaultLang.TAG="prog" & PROGDATA.ID AND Translation.TAG="prog" & PROGDATA.ID
AND DefaultLang.LANG=2
AND Translation.LANG=3

it is a pseudo-code idea...
I d try to add a checkEmpty function for each value returned. if is not empty, return the same.. if is empty return a new search from other languaje.
You need to cheak that the new value is not empty again of course.
create function checkEmpty( #word varchar(10), #languageNumber integer) returns varchar(10)
as
begin
declare #newWord
declare #newlanguage
if #word <> '' then return #word else
begin
//select new language
case languageNumber of
3 then #newlanguage = 1;
2 then #newlanguage = 3;
1 then #newlanguage = 2;
//search new lenguage
#newWord= SELECT TEXT FROM TRANSLATIONS, PROGDATA
WHERE TRANSLATIONS.TAG="prog" & PROGDATA.ID
AND TRANSLATIONS.LANG=#newlanguage
return #newWord
end;
end;
//FUNCTION CALL
SELECT dbo.checkEmpty(TEXT) FROM TRANSLATIONS, PROGDATA
WHERE TRANSLATIONS.TAG="prog" & PROGDATA.ID
AND TRANSLATIONS.LANG=3

I canabalized the solutions of #fossilcoder and #Smandoli and merged it in one solution:
SELECT
IIf (
NZ(TRANSLATION.Text,"") = "", DEFAULT.TEXT, TRANSLATION.TEXT)
FROM
TRANSLATIONS AS TRANSLATION,
TRANSLATIONS AS DEFAULT,
PROGDATA
WHERE
TRANSLATION.Tag="prog_" & PROGDATA.Id
AND
DEFAULT.Tag="prog" & PROGDATA.Id
AND
TRANSLATION.LanguageId=1
AND
DEFAULT.LanguageId=2
I never thought of referencing a table twice under a different alias

Related

custom aggregate function in postgres return NULL value

in order to create a more complex custom aggregate function, i followed first this amazing tutorial.
Here the data i use :
create table entries(
id serial primary key,
amount float8 not null
);
select setseed(0);
insert into entries(amount)
select (2000 * random()) - 1000
from generate_series(1, 1000000);
So I have this table :
id | amount | running_total
---------+-----------------------+--------------------
1 | -462.016298435628 | -462.016298435628
2 | 162.440904416144 | -299.575394019485
3 | -820.292402990162 | -1119.86779700965
4 | -866.230697371066 | -1986.09849438071
5 | -495.30001822859 | -2481.3985126093
6 | 772.393747232854 | -1709.00476537645
7 | -323.866365477443 | -2032.87113085389
8 | -856.917716562748 | -2889.78884741664
9 | 285.323366522789 | -2604.46548089385
10 | -867.916810326278 | -3472.38229122013
-- snip --
And I would like the max of the running_total column
(I know I can do do it without a new aggregate function, but it's for the demonstration)
So i've made this aggregate function
create or replace function grt_sfunc(agg_state point, el float8)
returns point
immutable
language plpgsql
as $$
declare
greatest_sum float8;
current_sum float8;
begin
current_sum := agg_state[0] + el;
greatest_sum := 40;
/*if agg_state[1] < current_sum then
greatest_sum := current_sum;
else
greatest_sum := agg_state[1];
end if;*/
return point(current_sum, greatest_sum);
/*return point(3.14159, 0);*/
end;
$$;
create or replace function grt_finalfunc(agg_state point)
returns float8
immutable
strict
language plpgsql
as $$
begin
return agg_state[0];
end;
$$;
create or replace aggregate greatest_running_total (float8)
(
sfunc = grt_sfunc,
stype = point,
finalfunc = grt_finalfunc
);
Normally it sould work, but in the end, it gives me a null result :
select greatest_running_total(amount order by id asc)
from entries;
id | running_total
---------+---------------
1 | [NULL]
I tried to change the type of the data, to check the 2 first aggregate functions separately, they are working well. Does someone could help me find a solution please ? :)
Thank you very much !
You need to set a non-NULL initcond for the aggregate. Presumably that would be (0,0), or maybe negative very large numbers for each? Or manually check for the agg_state being NULL.
Also, it seems like your grt_finalfunc should be returning subscript [1], not [0].
So, the solution was to add an initial condition. Indeed, without initial condition, the first value is considered as NULL :D (thank you #jjanes and #The Impaler)
So I corrected ma aggregated function :
create or replace aggregate greatest_running_total (float8)
(
sfunc = grt_sfunc,
stype = point,
finalfunc = grt_finalfunc,
initcond = '(0,0)'
);
And, indeed SQL indexes its tables from 1 and not from 0... Here was my second mistake,
Thank you very much !!

Rails sanitize user input in active record query

Let's say I have following data:
models/supplier.rb
| -- | ---------------- |
| id | name |
| -- | ---------------- |
| 1 | John Doe's Store |
| 2 | Jane |
| -- | ---------------- |
I have following queries which are failing to sanitize user's input from search field:
#term = "John Doe's"
Query 1
Supplier.order("case when name LIKE :term 1 else 2 end, name asc", term: "#{#term}%")
ArgumentError: Direction "one's%" is invalid. Valid directions are: [:asc, :desc, :ASC, :DESC, "asc", "desc", "ASC", "DESC"]
from ~/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/activerecord-4.2.7/lib/active_record/relation/query_methods.rb:1113:in `block (2 levels) in validate_order_args'
Query 2
Vulnerable to SQL Injection attack
Supplier.order("case when name LIKE '#{#term}%' then 1 else 2 end, name ASC").first
Supplier Load (2.6ms) SELECT "suppliers".* FROM "suppliers" ORDER BY case when name LIKE 'John Doe's%' then 1 else 2 end LIMIT 1
ActiveRecord::StatementInvalid: PG::SyntaxError: ERROR: syntax error at or near "s"
LINE 1: ...uppliers" ORDER BY case when name LIKE 'John Doe's%' then 1..
Query 3
It will success for normal inputs which don't have ' (special character) in them, but this query is still vulnerable to SQL injection attack
#term = "John"
Supplier.order("case when name LIKE '#{#term}%' then 1 else 2 end, name ASC").first
#<Supplier:0x007fe4bfd8d758
id: 188,
name: "John Doe's Store">
I am not able to figure out a solution for this problem, Please help me complete this query in secure way.
All you need to escape your input is to use
ActiveRecord::Base.connection.quote(value)
This works for all types and is what rails use.
#term = ActiveRecord::Base.connection.quote("John Doe's" + "%" )
Supplier.order("case when name LIKE #{#term} then 1 else 2 end, name ASC").first
You can use the base connection quote which will sanitize the input
#term = "John Doe's"
like_value = ActiveRecord::Base.connection.quote(#term + '%')
Supplier.order("case when name LIKE #{like_value} 1 else 2 end, name asc")
Read about it here...
http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Quoting.html#method-i-quote
Rails offers sanitize_sql_like() for this.
So your example would look like this:
#term = "John Doe's"
like_value = sanitize_sql_like(#term + '%')
Supplier.order("case when name LIKE #{like_value} 1 else 2 end, name asc")
See:
https://api.rubyonrails.org/classes/ActiveRecord/Sanitization/ClassMethods.html#method-i-sanitize_sql_like
EDIT:
If you need to use it in a Controller, call it like this:
ActiveRecord::Base::sanitize_sql_like()

Removing varchar(s) after a semicolon

So I have a lot of data like this:
pix11co;10.115.0.1
devapp087co;10.115.0.100
old_main-mgr;10.115.0.101
radius03co;10.115.0.110
And I want to delete the stuff after the ; so it just becomes
pix11co
devapp087co
old_main-mgr
radius03co
Since they're all different I can live with the semi-colon staying there.
I have the following query and it runs successfully but doesn't delete anything.
UPDATE dns$ SET [Name;] = REPLACE ([Name;], '%_;%__________%', '%_;');
What wildcards can I use to specify the characters after the ; ?
Can you use CHARINDEX? E.g.:
SELECT LEFT('pix11co;10.115.0.1', CHARINDEX(';', 'pix11co;10.115.0.1') - 1)
You can use SUBSTRING() and CHARINDEX() functions:
CREATE TABLE MyStrings (
STR VARCHAR(MAX)
);
INSERT INTO MyStrings VALUES
('pix11co;10.115.0.1'),
('devapp087co;10.115.0.100'),
('old_main-mgr;10.115.0.101'),
('radius03co;10.115.0.110');
SELECT STR, SUBSTRING(STR, 1, CHARINDEX(';', STR) -1 ) AS Result
FROM MyStrings;
Results:
+---------------------------+--------------+
| STR | Result |
+---------------------------+--------------+
| pix11co;10.115.0.1 | pix11co |
| devapp087co;10.115.0.100 | devapp087co |
| old_main-mgr;10.115.0.101 | old_main-mgr |
| radius03co;10.115.0.110 | radius03co |
+---------------------------+--------------+

PostgreSQL - Combine SELECT and RETURN VALUE of a Function

In my database I have a table "Datapoint" with the two columns "Id" (integer) and "Description" (character varying). Table "Datapoint"
I then have a table "Logging" with the three columns "Id" (integer), "Dt" (timestamp without timezone) and "Value" (double precision).Table "Logging"
I also have the following function:
CREATE OR REPLACE FUNCTION count_estimate(query text)
RETURNS integer AS
$BODY$ DECLARE rec record;ROWS INTEGER;BEGIN FOR rec IN EXECUTE 'EXPLAIN ' || query LOOP ROWS := SUBSTRING(rec."QUERY PLAN" FROM ' rows=([[:digit:]]+)');EXIT WHEN ROWS IS NOT NULL;END LOOP;RETURN ROWS;END $BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
This function returns the estimated count of entries that are found by a SELECT-Query, e.g. SELECT count_estimate('SELECT * FROM "Logging" WHERE "Id" = 3') would return 2.
I would now like to combine a SELECT-query on the table "Datapoint" with the return value of my function, so that my result looks like this:
ID | Description | EstimatedCount
1 | Datapoint 1 | 3
2 | Datapoint 2 | 4
3 | Datapoint 3 | 2
4 | Datapoint 4 | 1
My SELECT-query should look something like this:
SELECT
"Datapoint"."Id",
"Datapoint"."Description",
(SELECT count_estimate ('SELECT * FROM "Logging" WHERE "Logging"."Id" = "Datapoint"."Id"')) AS "EstimatedCount"
FROM
"Datapoint"
So my problem is to write a functioning SELECT-query for my purposes.
What about:
SELECT
"Datapoint"."Id",
"Datapoint"."Description",
count_estimate ('SELECT * FROM "Logging" WHERE "Logging"."Id" = "Datapoint"."Id"') AS "EstimatedCount"
FROM
"Datapoint"
You almost got it right, except that you need to supply the value of "Datapoint"."Id":
SELECT
"Datapoint"."Id",
"Datapoint"."Description",
count_estimate(
'SELECT * FROM "Logging" WHERE "Logging"."Id" = ' || "Datapoint"."Id"
) AS "EstimatedCount"
FROM "Datapoint";

SQLite query for finding file path

I have a column in my SQLite database that contains a file path. Given a portion of the file path, I need to return all the next folders. I would also like to return whether the next portion is the last path in the string (or does not end in a '/'). So if I have the folders:
/my/folder/one
/my/folder/two
/my/folder/path/three
/another/path
/one/two/three
And I have the path:
/my/folder/
The result would return something along the lines of:
+----------+------+
| isLast | item |
+----------+------+
| 1 | one |
| 1 | two |
| 0 | path |
+----------+------+
I've been struggling with this for a while so if anyone can provide any guidance it would be greatly appreciated.
This query:
SELECT item NOT GLOB '*/*' AS isLast,
item
FROM (SELECT substr(MyPath, length('/my/folder/') + 1) AS item
FROM MyTable
WHERE MyPath GLOB '/my/folder/' || '*')
will give you a result like this:
isLast item
------ ----
1 one
1 two
0 path/three
Removing the subpath requires the instr() function, which was only recently introduced in SQLite 3.7.15:
SELECT isLast,
CASE WHEN isLast
THEN item
ELSE substr(item, 1, instr(item, '/') - 1)
END AS item
FROM (SELECT item NOT GLOB '*/*' AS isLast,
item
FROM (SELECT substr(MyPath, length('/my/folder/') + 1) AS item
FROM MyTable
WHERE MyPath GLOB '/my/folder/' || '*'))