How to simulate group_concat in plain sql - sql

I am using the hxtt sql driver for csv files. It only supports plain sql. is there a way to simulate group concat using plain sql statements?

How plain? If you can use triggers then you can do it fairly simply. I've used this trick before in SQLite3 when I've needed a group_concat() that allows me to specify the order in which values are to be concatenated (SQLite3 does not provide a way to do that).
So let's say we have a table like this:
CREATE TABLE t(v TEXT NOT NULL, num INTEGER NOT NULL UNIQUE);
and you want to concatenate the values of v ordered by num, with some separator character, let's say a comma.
CREATE TEMP TABLE c(v TEXT);
CREATE TEMP TABLE j(v TEXT);
CREATE TEMP TRIGGER j_ins BEFORE INSERT ON j
FOR EACH ROW
BEGIN
UPDATE c SET v = v || ',' || NEW.v;
INSERT INTO c (v) SELECT NEW.v WHERE NOT EXISTS (SELECT * FROM c);
SELECT RAISE(IGNORE);
END;
Now we can:
INSERT INTO j (c) SELECT v FROM t ORDER BY num;
SELECT v FROM c; -- this should output the concatenation of the values of v in t
DELETE FROM c;
Finally, this is a sqlite3 session showing that this works:
SQLite version 3.7.4
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> CREATE TABLE t(v TEXT NOT NULL, num INTEGER NOT NULL UNIQUE);
sqlite> CREATE TEMP TABLE c(v TEXT);
sqlite> CREATE TEMP TABLE j(v TEXT);
sqlite> CREATE TEMP TRIGGER j_ins BEFORE INSERT ON j
...> FOR EACH ROW
...> BEGIN
...> UPDATE c SET v = v || ',' || NEW.v;
...> INSERT INTO c (v) SELECT NEW.v WHERE NOT EXISTS (SELECT * FROM c);
...> SELECT RAISE(IGNORE);
...> END;
sqlite> insert into t (v, num) values (1, 0);
sqlite> insert into t (v, num) values (31, 1);
sqlite> insert into t (v, num) values (52, 2);
sqlite> insert into t (v, num) values (0, 3);
sqlite> SELECT v FROM c;
sqlite> INSERT INTO j (v) SELECT v FROM t ORDER BY num;
sqlite> SELECT v FROM c;
1,31,52,0
sqlite> SELECT v FROM j;
sqlite> DELETE FROM c;
sqlite>
Now, this isn't pure SQL, because it depends on triggers. Between recursive triggers and all the ways to do conditionals in SQL you have a Turing complete system. But if you don't have triggers, or any procedural extensions, no generator tables... then not so much.
I know nothing about hxtt, so maybe this won't help you. But SQLite3 can deal with CSV, so maybe SQLite3 can help you...

Related

Why does constraint fail when upsert used in conjunction with triggers?

Please could someone explain why this unique constraint fails and if there is any workaround or suggestions.
With this schema:
CREATE TABLE abc
(
a INTEGER NOT NULL,
b INTEGER NOT NULL,
c INTEGER NOT NULL
);
CREATE UNIQUE INDEX unique_a_b ON abc(a, b);
CREATE TABLE xyz
(
x INTEGER NOT NULL,
y INTEGER NOT NULL,
z INTEGER NOT NULL
);
CREATE UNIQUE INDEX unique_x_y ON xyz(x, y);
I have triggers on a table xyz for both insert and update operations, they both perform insert or replace on table abc which has a unique index.
CREATE TRIGGER on_insert_xyz
AFTER INSERT
ON xyz
BEGIN
INSERT OR REPLACE INTO abc(a, b, c)
SELECT * FROM xyz LIMIT 1;
END;
CREATE TRIGGER on_update_xyz
AFTER UPDATE
ON xyz
BEGIN
INSERT OR REPLACE INTO abc(a, b, c)
SELECT * FROM xyz LIMIT 1;
END;
I have an upsert statement on table xyzthat causes the triggers to execute. If the upsert statement causes the update trigger to execute, then the unique index constraint fails on table abc.
INSERT INTO xyz(x, y, z) VALUES(1, 2, 3) ON CONFLICT(x, y) DO UPDATE SET z = excluded.z -- Works;
INSERT INTO xyz(x, y, z) VALUES(1, 2, 4) ON CONFLICT(x, y) DO UPDATE SET z = excluded.z -- Fails;
UNIQUE constraint failed: abc.a, abc.b

Select lines where nested table column meets a condition

v is defined as follows: create or replace type v is table of number and emp is a table which contains a column of type v.
I want to select the lines where v.count is 3, but I will get a compilation error. Is it because v.count is PL/SQL code?
I tried putting the code inside an anonymous block but it still didn't work.
Is using cursors the only solution?
SELECT *
FROM emp
WHERE V.COUNT = 3;
Thanks.
I think you're looking for cardinality():
CARDINALITY returns the number of elements in a nested table. The return type is NUMBER. If the nested table is empty, or is a null collection, then CARDINALITY returns NULL.
So you can do:
SELECT *
FROM emp
WHERE cardinality(V) = 3;
Quick demo:
create or replace type v is table of number
/
create table emp (id number, v v)
nested table v store as v_tab;
insert into emp (id, v) values (1, v(1));
insert into emp (id, v) values (2, v(1,2));
insert into emp (id, v) values (3, v(1,2,3));
insert into emp (id, v) values (4, v(1,2,3,4));
column v format a30
set feedback 1
SELECT *
FROM emp
WHERE cardinality(V) = 3;
ID V
---------- ------------------------------
3 V(1, 2, 3)
1 row selected.
I like Alex's cardinality answer, here is another approach:
create or replace type num_type as table of number;
create table table_with_num_type
(
ids num_type,
val varchar2(100)
)
nested table ids store as ids_tab ;
insert into table_with_num_type(ids, val) values (num_type(1,2,3), 'TEST1');
insert into table_with_num_type(ids, val) values (num_type(4,5,6,7), 'TEST2');
commit;
select t.val, count(t2.column_value) as num_count
from table_with_num_type t, table(t.ids) t2
group by t.val
having count(t2.column_value) = 3;
Result:
VAL NUM_COUNT
TEST1 3

How to insert same random value into two columns within one SQL Statement (Oracle)?

My example doesn't work:
INSERT INTO test_table
(column_1, column_2)
VALUES (DBMS_CRYPTO.RANDOMBYTES(16), column_1);
I'm not interested in PL/SQL solutions.
One option is to use scalar subquery caching:
INSERT INTO test_table (column_1, column_2)
SELECT random, random FROM
(SELECT (SELECT dbms_crypto.randombytes(16) FROM dual) random FROM dual);
Or using PL/SQL:
DECLARE
random RAW(16) := dbms_crypto.randombytes(16);
BEGIN
INSERT INTO test_table (column_1, column_2) VALUES (random, random);
END;
Use a subquery:
INSERT INTO test_table(column_1, column_2)
SELECT val, val
FROM (SELECT DBMS_CRYPTO.RANDOMBYTES(16) as val FROM dual) x;
Or, this can be written as:
INSERT INTO test_table(column_1, column_2)
WITH x AS (SELECT DBMS_CRYPTO.RANDOMBYTES(16) as val FROM dual)
SELECT val, val
FROM x;
You could create a temp table to store it?
set echo on
create table test_table (column_1 varchar2(100), column_2 varchar2(100));
create global temporary table rando_val (c1 varchar2(100));
insert
into rando_val
values ( dbms_crypto.randombytes(16) );
INSERT INTO test_table (column_1, column_2)
select c1,
c1
from rando_val;
commit;
select *
from test_table;
Not all that elegant, but it should work.
Try this weird query
select /*+ NO_XML_QUERY_REWRITE */ UTL_RAW.CAST_TO_RAW(VAL1),UTL_RAW.CAST_TO_RAW(VAL2) from
xmltable('for $c in . return <r><val1>{$c}</val1><val2>{$c}</val2></r>'
passing (SELECT UTL_RAW.CAST_TO_VARCHAR2(DBMS_CRYPTO.RANDOMBYTES(16)) as val FROM dual)
columns
"VAL1" varchar2(300) path '/r/val1',
"VAL2" varchar2(300) path '/r/val2' )
If you want then don't covert varchar2 to raw again in last step. Mean this
UTL_RAW.CAST_TO_RAW(VAL1)
This solution works for me:
INSERT INTO test_table(column_1, column_2)
WITH x AS (SELECT /*+ MATERIALIZE */ DBMS_CRYPTO.RANDOMBYTES(16) as val FROM dual)
SELECT val, val
FROM x;
Thanks to Husqvik & Gordon Linoff.

Stored Procedure Insert (Select and Values)

I'm looking to have a stored procedure that will:
run through Table A and retrieve all IDs.
insert into Table B all IDs (loop) but also static values which aren't found in Table A.
How do I approach this?
CREATE OR REPLACE PROCEDURE TEST AS
BEGIN
select ID from TABLE A;
INSERT INTO TABLE B
(
created_date,
created_by,
ID
)
VALUES ('sysdate', '1', 'RESULTS FROM SELECT QUERY');
END TEST;
Not sure how to merge static data ('sysdate' and '1') with results from a query.
No need for 2 separate queries. This should work with INSERT INTO SELECT:
INSERT INTO TABLEB
(
created_date,
created_by,
ID
)
SELECT 'sysdate', '1', id
FROM TABLEA

select forall array in sql statement(PL/SQL)

Type tabArray IS TABLE OF TABLE%ROWTYPE;
tableArray tabArray ;
--fill array
SELECT *
BULK COLLECT INTO tableArray
FROM TABLE
WHERE TABLE.field = ....
--work
FOR pos IN 1..tableArray .count
LOOP
dbms_output.put_line(pos||' '||audArray(pos).field);
end loop;
--doesn't work
SELECT * TABLE2
WHERE TABLE2.field in (SELECT filed FROM FORALL tableArray );
Main question: how can I use my array in sql statement (in) ?
First you have to create a type in SQL then can use as given below
CREATE TYPE FRUIT_TT AS TABLE OF VARCHAR2(100)
SELECT column_value AS val
FROM TABLE(FRUIT_TT('Apple','Banana','Apricot'))
WHERE column_value NOT LIKE 'A%';
Here a type FRUIT_TT is created and using it in SQL query.
Here is an example, you just need to adjust your SQL statement.
CREATE TYPE col_ntt IS TABLE OF NUMBER;
CREATE TABLE num_tab
(
col NUMBER
);
INSERT INTO num_tab VALUES(1);
INSERT INTO num_tab VALUES(2);
INSERT INTO num_tab VALUES(4);
DECLARE
l_col1 col_ntt := col_ntt(1, 2, 3);
l_col2 col_ntt;
BEGIN
SELECT *
BULK COLLECT INTO l_col2
FROM num_tab
WHERE col IN (SELECT column_value FROM TABLE(l_col1));
FOR indx IN 1..l_col2.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(l_col2(indx));
END LOOP;
END;
/*
1
2
*/