ActiveRecord Query on text field PostgreSQL - ruby-on-rails

I have a table Products and around 58000 records in it. I run two queries are as follows.
Product.where(description: 'swiss').count
It returns 0 products out of 58000 products. But when I run query below.
Product.where.not(description: 'swiss').count
it returns 2932 products out of 58000 products. I think it should return all 58000 products, because it is the reverse of first query.
I did not understand why it returns only 2932 products.

If you have NULL values in your columns, this could happen, because NULL always compares as NULL, even to itself, but WHERE expression must be true to include a result.
'a' = 'a'; True
'a' = 'b'; False
'a' = NULL; Results in NULL (not false!)
NULL = NULL; Results in NULL (not true!)
'a' != 'a'; False
'a' != 'b'; True
'a' != NULL; NULL
e.g. consider the table null_test containing
id | str
----+-----
1 | <NULL>
2 | a
3 | b
When looking for a column equal to some value, the NULL is never equal, so this will just return row 2.
SELECT id FROM null_test WHERE str = 'a';
But the same applies looking for a column not equal to some value, the NULL is never not equal (its NULL), so this will just return row 3.
SELECT id FROM null_test WHERE str != 'a';
Thus the total of your = and != is not all the rows, because you never included the NULL rows. This is where IS NULL and IS NOT NULL come in (they work like you might expect = NULL and != NULL to work).
SELECT id FROM null_test WHERE str != 'a' OR str IS NULL;
Now you get rows 1 and 3, making this the opposite of str = 'a'.
For ActiveRecord, it treats the Ruby nil value like the SQL NULL and creates the IS NULL checks in certain situations.
NullTest.where(str: 'a')
SELECT * FROM "null_test" WHERE "str" = 'a'
NullTest.where.not(str: 'a')
SELECT * FROM "null_test" WHERE "str" != 'a'
NullTest.where(str: nil)
SELECT * FROM "null_test" WHERE "str" IS NULL
NullTest.where.not(str: nil)
SELECT * FROM "null_test" WHERE "str" IS NOT NULL
NullTest.where(str: nil).or(NullTest.where.not(str: 'a'))
SELECT * FROM "null_test" WHERE "str" IS NULL OR "str" != 'a'

You have likely many records where description is nil and those are not included in the not.
A way to include the 'nil' records as well would be...
Product.where('description <> ? OR description IS NULL', 'swiss')
Or alternatively
Product.where.not(description: 'swiss').or(Product.where(description: nil))

Related

DB2 length of Clob (JSON)

I am generating a json inside a stored procedure as like
Declare res CLOB(5M);
Set res = (values (json_array(select json_object…
Json Looks like
[{pk: 1, name1: xyz, name: 2}, {pk: 2, name1: cvc, name2: vcc}]
At the end I Need the Information what is the length of the json, means How many entries do it have, beginning from Root.
I need something like this,
Declare counter SMALLINT;
Set Counter = xyz —should be 2
So How can I find out from res, that there are two rows?
--# SET TERMINATOR #
-- Create a generic function to deal with JSON arrays
CREATE OR REPLACE FUNCTION UNNEST_JSON (P_DOC CLOB(1M), P_PATH VARCHAR(128))
RETURNS TABLE
(
INDEX INT
, ITEM CLOB (1M)
)
DETERMINISTIC
NO EXTERNAL ACTION
BEGIN
DECLARE L_IDX INT DEFAULT 0;
L1:
WHILE TRUE DO
IF NOT JSON_EXISTS (P_DOC, P_PATH || '[' || L_IDX || ']') THEN LEAVE L1; END IF;
PIPE (L_IDX, JSON_QUERY (P_DOC, P_PATH || '[' || L_IDX || ']'));
SET L_IDX = L_IDX + 1;
END WHILE L1;
RETURN;
END
#
SELECT COUNT (1) AS ELEM_NUM
FROM TABLE (UNNEST_JSON (
-- You give your unnamed array some name you will refer in the 2-nd arg
JSON_OBJECT
(
KEY 'items'
VALUE
JSON_ARRAY
(
JSON_OBJECT (KEY 'pk' VALUE 1, KEY 'name1' VALUE 'xyz', KEY 'name' VALUE 2) FORMAT JSON
, JSON_OBJECT (KEY 'pk' VALUE 2, KEY 'name1' VALUE 'cvc', KEY 'name2' VALUE 'vcc') FORMAT JSON
)
FORMAT JSON
)
, '$.items'
))
#
ELEM_NUM
2

Understanding query method where in ActiveRecord

I want to return the count of users that that have a boolean value, show, not set to true. show is a boolean but is initialized to nil.
Because of this I'm getting some results that I did not expected. For example,
[{show: nil}].where.not(show: true).count -> 0
[{show: nil}].where(show: [nil, false]).count -> 1
Shouldn't this query's return the same thing?
This is a general misunderstanding of NULL (which is not uncommon by the way).
NULL is not comparable, it is neither true or false, nor equal or not equal, nor less than or greater than, etc., NULL cannot even be compared to NULL. e.g. NULL = NULL is false but NULL != NULL is also false.
Your First case (.where.not(show: true)) is show != true (or NOT(show =true)) which is the equivalent of show = false.
Your second example (.where(show: [nil, false])) is show IS NULL OR show = false. While this syntax show: [x,y] usually results in an IN() clause Arel is smart enough to realize that one of these values is nil and converts the SQL generation accordingly because show IN (NULL,false) would be equivalent to show = NULL OR show = false (this is how IN() works) but as mentioned NULL = NULL is false thus why IS NULL and IS NOT NULL exist.
The result of your query is because there are no rows where show = false but there is one row where show IS NULL.

How to do mathematical computation in stored procedure in snowflake?

I am getting a count(*) after joining two Snowflake tables. This is done inside a stored procedure. If the count is greater than zero, I need to pass a value. My stored procedure gets called from a NiFi Processor and I have to return the value to NiFi so that an email can be sent from NiFi.
I am getting 'NaN' as output for the below code.
CREATE OR REPLACE PROCEDURE test_Delete_excep()
returns float not null
language javascript
as
$$
var rs;
var return_value = 0;
var SQL_JOIN = "select count(*) from (Select GT.VARIANTDATA from GOV_TEST GT inner join GOV_TEST_H GTH on GT.VARIANTDATA:COL1::String = GTH.VARIANTDATA:COL1::String where to_char(GT.VARIANTDATA) != to_char(GTH.VARIANTDATA));";
var stmt = snowflake.createStatement({sqlText: SQL_JOIN});
rs = stmt.execute();
rs.next();
return_value += JSON.stringify(rs.getColumnValue(1));
if (return_value > 0) { return 'email required';}
$$;
Here is the result:
Row TEST_DELETE_EXCEP
1 NaN
How can I do the arithmetic calculation and return a value to NiFi processor?
You are never returning a float value, which the SP defines as the return type. If return_value is greater than 0, it will try to return the string 'email required.', which is not a float. That will generate a NaN. If return_value is not greater than 0, the code will never return a value of any kind. That will return NULL. Since you specify NOT NULL for the return, that will force it to NaN
Also, I'm not sure why you're trying to stringify the rs.getColumnValue(1). The select count(*) will produce an integer value, which you can read directly.
You probably want something like this:
CREATE OR REPLACE PROCEDURE test_Delete_excep()
returns float not null
language javascript
as
$$
var rs;
var return_value = 0;
var SQL_JOIN = "select count(*) from (Select GT.VARIANTDATA from GOV_TEST GT inner join GOV_TEST_H GTH on GT.VARIANTDATA:COL1::String = GTH.VARIANTDATA:COL1::String where to_char(GT.VARIANTDATA) != to_char(GTH.VARIANTDATA));";
var stmt = snowflake.createStatement({sqlText: SQL_JOIN});
rs = stmt.execute();
if(rs.next()) {
return_value = rs.getColumnValue(1);
} else {
return -1;
}
return return_value;
$$;
This will return the row count produced by your join SQL. If you want something different, please clarify the desired output.

Parse description string to populate NULL fields

Informix 12.10
tblItems
(
Type SMALLINT, {Precious Metal = 1, Other = 2}
Description VARCHAR,
Quantity SMALLINT,
Name VARCHAR,
Weight DECIMAL(5,1),
Purity SMALLINT,
Brand VARCHAR,
Model VARCHAR,
SerialNum VARCHAR
);
EDIT UPDATE: Sample data below is stored in tblItems.Type and tblItems.Description. Please note that the contents in Description column are all uppercase characters and may also include punctuation character.
2|1LAPTOP APPLE 15.5" MODEL MACKBOOK PRO,S/N W80461WCAGX, WITH CHARGER||||||||
1|1RING 2.3PW 14K||||||||
2|DRILL RIOBY, MODEL D5521 S/N77720||||||||
2|TRIMMER TORO, MODEL 0242 S/N 66759||||||||
2|CELL SAMSUNG NOTE3, MODEL SM-N900T S/N RV8F90YLZ9W||||||||
I need to parse the sample item descriptions into the columns below, using the rules mentioned in the comments :
Quantity, {if description string does not start with a number, then Quantity = 1}
Name, {Always the first element if description has no quantity, second element if quantity present]
Weight, {Always before "PW" if Type = 1, Default to zero if Type = 2}
Purity, {Always before "K" if Type = 1, Default to NULL if Type = 2}
Brand, {Always the second element in description, if present}
Model, {Always after "MODEL", with or without a space}
Serial Number {Always after "S/N", with or without a space}
I would like to do this with an UPDATE statement, but if Informix has an import utility tool like SQL-Server's SSIS, then that could be a better option.
UPDATE, Expected Results:
Quantity 1 1 1 1 1
Name LAPTOP RING DRILL TRIMMER CELL
Weight 0.0 2.3 0.0 0.0 0.0
Purity 14
Brand APPLE RIOBY TORO SAMSUNG
Model MACKBOOK PRO D5521 0242 SM-N900T
SerialNum W8046WCAGX 77720 66759 RV8F90YLZ9W
Assuming you are using Informix 12.10.XC8 or above, you can try using regular expressions to parse the description string (see the online documentation here).
For the serial number, for example, you can do:
UPDATE tblitems
SET
serialnum =
DECODE
(
regex_match(description, '(.*)(S\/N)(.*)', 3)
, 't'::BOOLEAN, regex_replace(description, '(.*)(S\/N)([[:blank:]]?)([[:alnum:]]*)(.*)', '\4', 0, 3)
, 'f'::BOOLEAN, ''
)
So in the previous example I am testing if the description contains the S/N string and if that is true I use regex_replace to return the value after it, in this case the 4th matching group in the regular expression (I am not using regex_extract to get the value because it seems to return multiple values and I get error -686).
You can extend this approach to the rest of the columns and see if regular expressions are enough to parse the description column.
If you're looking for a SQL Server option and open to a Split/Parse function which maintains the sequence
Example
Select A.Type
,A.Description
,C.*
From YourTable A
Cross Apply (values ( replace(
replace(
replace(
replace(A.Description,',',' ')
,' ',' ')
,'Model ','Model')
,'S/N ','S/N')
)
)B(CleanString)
Cross Apply (
Select Quantity = IsNull(left(max(case when RetSeq=1 then RetVal end),NullIf(patindex('%[^0-9]%',max(case when RetSeq=1 then RetVal end)) -1,0)),1)
,Name = substring(max(case when RetSeq=1 then RetVal end),patindex('%[^0-9]%',max(case when RetSeq=1 then RetVal end)),charindex(' ',max(case when RetSeq=1 then RetVal end)+' ')-1)
,Weight = IIF(A.Type=2,null,try_convert(decimal(5,1),replace(max(case when RetVal like '%PW' then RetVal end),'PW','')))
,Purity = try_convert(smallint ,replace(max(case when RetVal like '%K' then RetVal end),'K',''))
,Brand = IIF(A.Type=1,null,max(case when RetSeq=2 then RetVal end))
,Model = replace(max(case when RetVal Like 'Model[0-9,A-Z]%' then RetVal end),'Model','')
,SerialNum = replace(max(case when RetVal Like 'S/N[0-9,A-Z]%' then RetVal end),'S/N','')
From [dbo].[tvf-Str-Parse](CleanString,' ') B1
) C
Returns
The TVF if Interested
CREATE FUNCTION [dbo].[tvf-Str-Parse] (#String varchar(max),#Delimiter varchar(10))
Returns Table
As
Return (
Select RetSeq = Row_Number() over (Order By (Select null))
,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>' + replace((Select replace(#String,#Delimiter,'§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
);
EDIT - If you don't want or can't use a TVF
dbFiddle
Select A.Type
,A.Description
,C.*
From YourTable A
Cross Apply (values ( replace(
replace(
replace(
replace(A.Description,',',' ')
,' ',' ')
,'Model ','Model')
,'S/N ','S/N')
)
)B(CleanString)
Cross Apply (
Select Quantity = IsNull(left(max(case when RetSeq=1 then RetVal end),NullIf(patindex('%[^0-9]%',max(case when RetSeq=1 then RetVal end)) -1,0)),1)
,Name = substring(max(case when RetSeq=1 then RetVal end),patindex('%[^0-9]%',max(case when RetSeq=1 then RetVal end)),charindex(' ',max(case when RetSeq=1 then RetVal end)+' ')-1)
,Weight = IIF(A.Type=2,null,try_convert(decimal(5,1),replace(max(case when RetVal like '%PW' then RetVal end),'PW','')))
,Purity = try_convert(smallint ,replace(max(case when RetVal like '%K' then RetVal end),'K',''))
,Brand = IIF(A.Type=1,null,max(case when RetSeq=2 then RetVal end))
,Model = replace(max(case when RetVal Like 'Model[0-9,A-Z]%' then RetVal end),'Model','')
,SerialNum = replace(max(case when RetVal Like 'S/N[0-9,A-Z]%' then RetVal end),'S/N','')
From (
Select RetSeq = row_number() over (Order By (Select null))
,RetVal = ltrim(rtrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>' + replace((Select replace(CleanString,' ','§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
) B1
) C

Traversing a singly list

While traversing a singly list, inside the loop condition, whats the difference between temp != NULL and temp-next != NULL? For Ex
while(temp != NULL)
{
......
......
}
and
while(temp->next != NULL)
{
......
......
}
I don't understand the difference between the two.
From context this answer assumes that temp is a node in the linked list.
temp->next != NULL returns true when there is a node after temp (i.e. when temp is not the last node in the list). As an example, consider this list:
a -> b -> c -> NULL
if we do temp = a->next then temp is b, and temp->next != NULL evaluates to true since c (not NULL) comes after b.
temp != NULL returns true when temp itself is a node in the list. This might not be the case if, for some reason, you have traversed too far down the list. Using the same list as above: if we do temp = c->next, then temp != NULL evaluates to false.
Understanding that, the difference between the lists is as follows:
while(temp != NULL)... executes until temp is NULL
while(temp->next != NULL) executes until the node after temp is NULL

Resources