SQLite - condition check before execute a section of script - sqlite

I understand that SQLite does not have If-Else condition check, and people have been using case statements to get around it. However I want to do a if condition check before executing a certain portion of the script, like the following:
IF (condition = true)
INSERT INTO tableA(A, B)
VALUES (a, b)
....
END
From what I have been trying, case statement doesn't seem to work. Is there any way I can accomplish the above in SQLite?
Thanks for all your help!

You could perhaps use an INSERT SELECT
INSERT INTO table SELECT ...;
The second form of the INSERT statement contains a SELECT statement
instead of a VALUES clause.
A new entry is inserted into the table for
each row of data returned by executing the SELECT statement.
If a
column-list is specified, the number of columns in the result of the
SELECT must be the same as the number of items in the column-list.
Otherwise, if no column-list is specified, the number of columns in
the result of the SELECT must be the same as the number of columns in
the table.
Any SELECT statement, including compound SELECTs and SELECT
statements with ORDER BY and/or LIMIT clauses, may be used in an
INSERT statement of this form.
extract from SQL As Understood By SQLite - INSERT
e.g.
INSERT into xxx
SELECT null as id,
CASE
WHEN filesize < 1024 THEN 'just a little bit'
WHEN filesize >= 1024 THEN 'quite a bit'
END AS othercolumn
FROM filesizes
WHERE filesize < 1024 * 1024
The above will insert rows into table xxx which consists of 2 columns id (rowid alias) and othercolumn according to the results (2 columns id (always set as null) and othercolumn) of the SELECT, which is selecting from the filesizes table where the value of the filesize column is less than 1024 * 1024 (1048576), thus conditionally inserting.
Furthermore, if the filesize is less than 1024 the othercolumn is set to just a little bit, if the filesize is greater than 1023 then the othercolumn is set to quite a bit. So making the conditional insert more complex.
Assuming the filesizes table were :-
The running the above would result in :-

Related

SQLite count number of occurence of word in a string

I want to count number of occurrences of a word in a string for example ,
[{"lastUpdatedDateTime":{"timestamp":1.54867752522E12},"messageStatus":"DELIVERED","phoneNumber":"+916000060000"},{"lastUpdatedDateTime":{"timestamp":1548677525220},"messageStatus":"DELIVERED","phoneNumber":"+916000060000"}]
in above string i want to count no of occurrences of a word 'DELIVERED' here it is 2.
i want to get result 2. pls help me on this. i should have to use only sql query to achieve this.
thanks in advance.
If your table's name is tablea and the column's name is col:
SELECT
(LENGTH(col) - LENGTH(REPLACE(col, '"DELIVERED"', '')))
/
LENGTH('"DELIVERED"') as counter
from tablea
remove every occurrence of "DELIVERED" and subtract the length of the string from the original string and finally divide the result with the length of "DELIVERED"
Assuming your data is in a table something like:
CREATE TABLE example(json TEXT);
INSERT INTO example VALUES('[{"lastUpdatedDateTime":{"timestamp":1.54867752522E12},"messageStatus":"DELIVERED","phoneNumber":"+916000060000"},{"lastUpdatedDateTime":{"timestamp":1548677525220},"messageStatus":"DELIVERED","phoneNumber":"+916000060000"}]');
and your instance of sqlite has the JSON1 extension enabled:
SELECT count(*) AS "Number Delivered"
FROM example AS e
JOIN json_each(e.json) AS j
WHERE json_extract(j.value, '$.messageStatus') = 'DELIVERED';
gives you:
Number Delivered
----------------
2
This will return the total number of matching entries from all rows in the table as a single value. If you want one result per row instead, it's an easy change but the exact details depend on your table definition. Adding GROUP BY e.rowid to the end of the query will work in most cases, though.
In the long run it's probably a better idea to store each object in the array as a single row in a table, broken up into the appropriate columns.

random unique integers avoid duplicates before insert

Given a need to store 4 digit random unique integers, how would I do a efficient insert of a large quality new numbers.
If the values are created outside sqlite there's a chance the values already existing in the database
What would be the best method to do such a task?
You could make the column where the numbers will be stored UNIQUE and use INSERT OR IGNORE on a SINGLE INSERT with multiple values (for efficiency). e.g. :-
INSERT OR IGNORE INTO rndm_id VALUES
('0001'),('0027'),('9999'),('0412'),('2108'),
('0001'), -- duplicate will be skipped
('3085') -- and so on
;
Note values have been enclosed in quotes to maintain 4 numerics. The Table was defined using :-
CREATE TABLE IF NOT EXISTS rndm_id (myid TEXT UNIQUE);
If you are considering a large number of values then may need to consider :-
Maximum Length Of An SQL Statement
The maximum number of bytes in the text of an SQL statement is limited
to SQLITE_MAX_SQL_LENGTH which defaults to 1000000. You can redefine
this limit to be as large as the smaller of SQLITE_MAX_LENGTH and
1073741824.
If an SQL statement is limited to be a million bytes in length, then
obviously you will not be able to insert multi-million byte strings by
embedding them as literals inside of INSERT statements. But you should
not do that anyway. Use host parameters for your data. Prepare short
SQL statements like this:
INSERT INTO tab1 VALUES(?,?,?); Then use the sqlite3_bind_XXXX()
functions to bind your large string values to the SQL statement. The
use of binding obviates the need to escape quote characters in the
string, reducing the risk of SQL injection attacks. It is also runs
faster since the large string does not need to be parsed or copied as
much.
The maximum length of an SQL statement can be lowered at run-time
using the sqlite3_limit(db,SQLITE_LIMIT_SQL_LENGTH,size) interface.
Limits In SQLite
Considering the comment
Is there a way I can do a select to give a a set of new values which I
can use then do a insert later?
So assuming that you wanted 1000 4 digit unique random values then the following may suffice :-
DROP TABLE IF EXISTS save_for_later; -- Drop the table
CREATE TEMP TABLE IF NOT EXISTS save_for_later (four_digit_random_value UNIQUE); -- Create a temporary table
-- Create a table with 1500 random rows
WITH RECURSIVE cte1 AS (
SELECT CAST((abs(random() % 10)||abs(random() % 10)||abs(random() % 10)||abs(random() % 10)) AS TEXT)
UNION ALL SELECT CAST((abs(random() % 10)||abs(random() % 10)||abs(random() % 10)||abs(random() % 10)) AS TEXT)
FROM cte1 LIMIT 1500
)
INSERT OR IGNORE INTO save_for_later SELECT * FROM cte1;
-- Later on extract the 1000 required rows.
SELECT * FROM save_for_later LIMIT 1000;
Suspected requirement
If the question were how can I insert a set number (300) of random 4 numeric unique values into a table (master) with existing data where the new values should also be unique in conjunction with the existing values
Then the following could do that (see note re limitations) :-
DROP TABLE IF EXISTS master; --
CREATE TABLE IF NOT EXISTS master (random_value TEXT UNIQUE);
-- Master (existing) Table populated with some values
INSERT OR IGNORE INTO master VALUES
('0001'),('0027'),('9999'),('0412'),('2108'),
('0001'), -- duplicate will be skipped
('3085') -- and so on
;
SELECT * FROM master; -- Result 1 show what's in the master table
-- Create a table to save the values for later
DROP TABLE IF EXISTS save_for_later; -- Drop the table
CREATE TEMP TABLE IF NOT EXISTS save_for_later (four_digit_random_value UNIQUE); -- Create a temporary table
-- Populate the vales to be saved for later excluding any values that already exist
-- 1500 rows perhaps excessive but very likely to result in 300 unique values
WITH RECURSIVE cte1(rndm) AS (
SELECT
CAST((abs(random() % 10)||abs(random() % 10)||abs(random() % 10)||abs(random() % 10)) AS TEXT)
UNION ALL
SELECT
CAST((abs(random() % 10)||abs(random() % 10)||abs(random() % 10)||abs(random() % 10)) AS TEXT)
FROM cte1
LIMIT 1500 --<<<<<< LIMIT important otherwise would be infinite
)
INSERT OR IGNORE INTO save_for_later
SELECT * FROM cte1
WHERE rndm NOT IN(SELECT * FROM master)
;
-- Later on extract the required rows (300 here) and insert them.
INSERT INTO master
SELECT * FROM save_for_later
LIMIT 300;
SELECT * FROM master; -- Should be 6 original/existing rows + 300 so 306 rows (although perhaps a chance that 300 weren't generated)
Note with 4 numerics there is a limitation of 10,000 possible values (0000-9999), so the more values that exist in the original table the greater the chance that there will be issues finding values that would be unique.
The above would result in :-
The first result the master table before generation of the new values :-
The result after adding the new values (original 6 rows + new 300 rows) :-

Sqlite doesn't use some indexes

Executing the following code creates a table with two columns and adds 1 million rows. One column is INT and one is TEXT. Then it creates one index per column and one collate nocase index per column. Then it executes three queries.
The first query uses the index t2 as expected.
The second query is the same as the first one, but it adds the ESCAPE clause and doesn't use the index. The presence of unescaped % or _ should prevent the index from being (fully) used, but the presence of the ESCAPE clause itself shouldn't.
Why does the ESCAPE clause prevent the index from being used?
The third query is the same as the first one, but it doesn't use the index. The only difference is that the query uses column col_i instead of col_t which is defined as INT instead of TEXT. Sqlite doesn't prevent me from creating the index, so I would expect for it to be used.
Why isn't the index i2 used?
.timer on
DROP TABLE IF EXISTS tab;
CREATE TABLE tab (col_t TEXT, col_i INT);
INSERT INTO tab (col_i, col_t) WITH RECURSIVE cte (x, y) AS (SELECT hex(randomblob(16)), hex(randomblob(16)) UNION ALL SELECT hex(randomblob(16)), hex(randomblob(16)) FROM cte LIMIT 1000000) SELECT x, y FROM cte;
CREATE INDEX t ON tab (col_t);
CREATE INDEX t2 ON tab (col_t COLLATE nocase);
CREATE INDEX i ON tab (col_i);
CREATE INDEX i2 ON tab (col_i COLLATE nocase);
SELECT * FROM tab WHERE col_t LIKE 'abcabcabc';
SELECT * FROM tab WHERE col_t LIKE 'abcabcabc' ESCAPE '\';
SELECT * FROM tab WHERE col_i LIKE 'abcabcabc';
The documentation documents when the index can be used for LIKE:
The left-hand side … must be the name of an indexed column with TEXT affinity.
The right-hand side … must be … a string literal … that does not begin with a wildcard character.
The ESCAPE clause cannot appear on the LIKE operator.
The built-in functions used to implement LIKE … must not have been overloaded using the sqlite3_create_function() API.
[…]
… the column must indexed using built-in NOCASE collating sequence.
The query optimizer has to prove that using the index cannot change the meaning of the query. These rules implement the proof.
While there exist queries that would work with the index despite violating these rules, it would be necessary to extend the optimizer to be able to prove that they work.

SUM totals by FOR ALL ENTRIES itab keys

I want to execute a SELECT query on a database table that has 6 key fields, let's assume they are keyA, keyB, ..., keyF.
As input parameters to my ABAP function module I do receive an internal table with exactly that structure of the key fields, each entry in that internal table therefore corresponds to one tuple in the database table.
Thus I simply need to select all tuples from the database table that correspond to the entries in my internal table.
Furthermore, I want to aggregate an amount column in that database table in exactly the same query.
In pseudo SQL the query would look as follows:
SELECT SUM(amount) FROM table WHERE (keyA, keyB, keyC, keyD, keyE, keyF) IN {internal table}.
However, this representation is not possible in ABAP OpenSQL.
Only one column (such as keyA) is allowed to state, not a composite key. Furthermore I can only use 'selection tables' (those with SIGN, OPTIOn, LOW, HIGH) after they keyword IN.
Using FOR ALL ENTRIES seems feasible, however in this case I cannot use SUM since aggregation is not allowed in the same query.
Any suggestions?
For selecting records for each entry of an internal table, normally the for all entries idiom in ABAP Open SQL is your friend. In your case, you have the additional requirement to aggregate a sum. Unfortunately, the result set of a SELECT statement that works with for all entries is not allowed to use aggregate functions. In my eyes, the best way in this case is to compute the sum from the result set in the ABAP layer. The following example works in my system (note in passing: using the new ABAP language features that came with 7.40, you could considerably shorten the whole code).
report zz_ztmp_test.
start-of-selection.
perform test.
* Database table ZTMP_TEST :
* ID - key field - type CHAR10
* VALUE - no key field - type INT4
* Content: 'A' 10, 'B' 20, 'C' 30, 'D' 40, 'E' 50
types: ty_entries type standard table of ztmp_test.
* ---
form test.
data: lv_sum type i,
lt_result type ty_entries,
lt_keys type ty_entries.
perform fill_keys changing lt_keys.
if lt_keys is not initial.
select * into table lt_result
from ztmp_test
for all entries in lt_keys
where id = lt_keys-id.
endif.
perform get_sum using lt_result
changing lv_sum.
write: / lv_sum.
endform.
form fill_keys changing ct_keys type ty_entries.
append :
'A' to ct_keys,
'C' to ct_keys,
'E' to ct_keys.
endform.
form get_sum using it_entries type ty_entries
changing value(ev_sum) type i.
field-symbols: <ls_test> type ztmp_test.
clear ev_sum.
loop at it_entries assigning <ls_test>.
add <ls_test>-value to ev_sum.
endloop.
endform.
I would use FOR ALL ENTRIES to fetch all the related rows, then LOOP round the resulting table and add up the relevant field into a total. If you have ABAP 740 or later, you can use REDUCE operator to avoid having to loop round the table manually:
DATA(total) = REDUCE i( INIT sum = 0
FOR wa IN itab NEXT sum = sum + wa-field ).
One possible approach is simultaneous summarizing inside SELECT loop using statement SELECT...ENDSELECT statement.
Sample with calculating all order lines/quantities for the plant:
TYPES: BEGIN OF ls_collect,
werks TYPE t001w-werks,
menge TYPE ekpo-menge,
END OF ls_collect.
DATA: lt_collect TYPE TABLE OF ls_collect.
SELECT werks UP TO 100 ROWS
FROM t001w
INTO TABLE #DATA(lt_werks).
SELECT werks, menge
FROM ekpo
INTO #DATA(order)
FOR ALL ENTRIES IN #lt_werks
WHERE werks = #lt_werks-werks.
COLLECT order INTO lt_collect.
ENDSELECT.
The sample has no business sense and placed here just for educational purpose.
Another more robust and modern approach is CTE (Common Table Expressions) available since ABAP 751 version. This technique is specially intended among others for total/subtotal tasks:
WITH
+plants AS (
SELECT werks UP TO 100 ROWS
FROM t011w ),
+orders_by_plant AS (
SELECT SUM( menge )
FROM ekpo AS e
INNER JOIN +plants AS m
ON e~werks = m~werks
GROUP BY werks )
SELECT werks, menge
FROM +orders_by_plant
INTO TABLE #DATA(lt_sums)
ORDER BY werks.
cl_demo_output=>display( lt_sums ).
The first table expression +material is your internal table, the second +orders_by_mat quantities totals selected by the above materials and the last query is the final output query.

Query a manual list of data items

I would like to run a query involving joining a table to a manually generated list but am stuck trying to generate the manual list. There is an example of what I am attempting to do below:
SELECT
*
FROM
('29/12/2014', '30/12/2014', '30/12/2014') dates
;
Ideally I would want my output to look like:
29/12/2014
30/12/2014
31/12/2014
What's your Teradata release?
In TD14 there's STRTOK_SPLIT_TO_TABLE:
SELECT *
FROM TABLE (STRTOK_SPLIT_TO_TABLE(1 -- any dummy value
,'29/12/2014,30/12/2014,30/12/2014' -- any delimited string
,',' -- delimiter
)
RETURNS (outkey INTEGER
,tokennum INTEGER
,token VARCHAR(20) CHARACTER SET UNICODE) -- modify to match the actual size
) AS d
You can easily put this in a Derived Table and then join to it.
inkey (here the dummy value 1) is a numeric or string column, usually a key. Can be used for joining back to the original row.
outkey is the same as inkey.
tokennum is the ordinal position of the token in the input string.
token is the extracted substring.
Try this:
select '29/12/2014'
union
select '30/12/2014'
union
...
It should work in Teradata as well as in MySql.

Resources