How to count the number of commas in a string? - teradata

I use teradata. No access to functions, UDFs, stored procedures
Table
id attribute
1 strength, power
2 ring, wig, puff
3 strength, ring
4 puff, wig
5 power
I need to count the number of attributes
id att_number
1 2
2 3
3 2
4 2
5 1
How can I do this?

Of course your site should install at least the oTranslate and oReplace UDFs. Then it's a simple
CHAR_LENGTH(col) - CHAR_LENGTH(OTRANSLATE(col, ',',''))
Without those functions it's a pain in the <insert your favourite body part here>. Simply try to implement a REPLACE without oREPLACE, complex SQL with a horrible performance.
In your case it's a bit easier. If the maximum number is 12 the most efficient way will be a straight:
CASE
WHEN col LIKE '%,%,%,%,%,%,%,%,%,%,%,%,%' THEN 12
WHEN col LIKE '%,%,%,%,%,%,%,%,%,%,%,%' THEN 11
WHEN col LIKE '%,%,%,%,%,%,%,%,%,%,%' THEN 10
WHEN col LIKE '%,%,%,%,%,%,%,%,%,%' THEN 9
WHEN col LIKE '%,%,%,%,%,%,%,%,%' THEN 8
WHEN col LIKE '%,%,%,%,%,%,%,%' THEN 7
WHEN col LIKE '%,%,%,%,%,%,%' THEN 6
WHEN col LIKE '%,%,%,%,%,%' THEN 5
WHEN col LIKE '%,%,%,%,%' THEN 4
WHEN col LIKE '%,%,%,%' THEN 3
WHEN col LIKE '%,%,%' THEN 2
WHEN col LIKE '%,%' THEN 1
ELSE 0
END
No need for nested CASE/POSITION/SUBSTRING.

Using only index and substring functions, you would need a nested case statement which checks for a max of 12 attributes as show below. You would need to further nest the case statement to able to count upto 12 attributes, currently it can only count to a max of 2 attributes. [note: mytext=attributes]
SELECT id, mytext
,case
when index(mytext, ',')= 0 then 0
when index(mytext, ',')>0 then
case
when index(substring(mytext, index(mytext, ','), length(mytext)), ',')=0 then 1
when index(substring(mytext, index(mytext, ','), length(mytext)), ',')>0 then 2
end
end
from mydb.sd_test
order by id;

SELECT colname
,CHARACTERS(TRIM(colname)) -CHARACTERS(TRIM(OREPLACE(colname,',',''))) +1
SAMPLE 100 FROM tablename

Can use a much simpler and efficient one liner code as below:
select length(trim(regexp_replace(Column_Name,'[^,]+','')));

Related

SQLite: Number rows of query output

I am learning SQLite and I am currently posing the question whether there is a simple way of adding a sequential numbering to the output of a query. Underneath, I provide an example of what I am trying to achieve.
For instance, I have the following query:
SELECT
splTicker AS 'Ticker',
count(splTicker) AS '# of Splits'
FROM Splits
GROUP BY splTicker
ORDER BY count(splTicker) DESC, splTicker ASC;
The output of this query is as follows:
bash-3.2$ sqlite3 myShares < Queries/Split.sql
Ticker # of Splits
---------- -----------
AI.PA 7
ASML.AS 3
BN.PA 3
ALTR.LS 2
BOKA.AS 2
DG.PA 2
...
SON.LS 1
SU.PA 1
SW.PA 1
TEC.PA 1
UMI.BR 1
VIV.PA 1
VPK.AS 1
I am trying to add a sequential number to the rows to obtain the following output:
# Ticker # of Splits
-- ---------- -----------
1 AI.PA 7
2 ASML.AS 3
3 BN.PA 3
4 ALTR.LS 2
5 BOKA.AS 2
6 DG.PA 2
...
Currently, I use a workaround and add the row numbers post-query in Perl. I am posing the question whether I could do this directly in SQLite. The idea seems simple, but I have not found a solution yet. Any help would be appreciated.
Best regards,
GAM
Try this:
SELECT
(SELECT COUNT(*)
FROM Splits AS s2
WHERE s2.splTicker <= s1.splTicker) AS '#',
splTicker AS 'Ticker',
count(splTicker) AS '# of Splits'
FROM Splits s1
GROUP BY s1.splTicker
ORDER BY count(s1.splTicker) DESC, s1.splTicker ASC;

How can I concatenate(or merge) values from 2 result sets with the same PK?

I don't know if I'm being dumb here but I can't seem to find an efficient way to do this. I wrote a very long and inefficient query that does what I need, but what I WANT is a more efficient way.
I have 2 result sets that displays an ID (a PK which is generic/from the same source in both sets) and a FLAG (A - approve and V - Validate).
Result Set 1
ID FLAG
1 V
2 V
3 V
4 V
5 V
6 V
Result Set 2
ID FLAG
2 A
5 A
7 A
8 A
I want to "merge" these two sets to give me this output:
ID FLAG
1 V
2 (V/A)
3 V
4 V
5 (V/A)
6 V
7 A
8 A
Neither of the 2 result sets will at any time have all the ID's to make a simple left join with a case statement on the other result set an easy solution.
I'm currently doing a union between the two sets to get ALL the ID's. Thereafter I left join the 2 result sets to get the required '(V/A)' by use of a case statement.
There must be a more efficient way but I just can't seem to figure it out now as I'm running low on amps... I need a holiday... :-/
Thanks in advance!
Use a FULL OUTER JOIN:
SELECT ID,
CASE
WHEN t1.FLAG IS NULL THEN t2.FLAG
WHEN t2.FLAG IS NULL THEN t1.FLAG
ELSE '(' || t1.FLAG || '/' || t2.FLAG || ')'
END AS MERGED_FLAG
FROM TABLE1 t1
FULL OUTER JOIN TABLE2 t2
USING (ID)
ORDER BY ID
See this SQLFiddle.
Share and enjoy.
I think that you can use xmlagg. Here an exemple :
SELECT deptno,
SUBSTR (REPLACE (REPLACE (XMLAGG (XMLELEMENT ("x", ename)
ORDER BY ename),'</x>'),'<x>','|'),2) as concated_list
FROM emp
GROUP BY deptno
ORDER BY deptno;
Bye

combining IDs under GROUP BY?

I have an sqlite table "log" that looks like this:
ID p_id viewer
----------------------
1 1 100
2 1 200
3 1 300
4 3 550
5 3 230
6 5 420
7 2 320
8 2 203
9 9 10
10 9 55
And I want to get the average viewers from each p_id. That'd be
SELECT avg(viewer) FROM log GROUP BY p_id
But I want to treat p_id 1 and 5 as the same, so I'd get the average viewers of p_id 1 and 5 combined. How do I do that? Note that the table is much larger, and I need to treat two p_id's as the same multiple times. Can I still do a "group by" to achieve this, or are there other ways?
I tried this and it works with mySQL at least.
SELECT avg(viewer) FROM log GROUP BY CASE p_id WHEN 5 THEN 1 ELSE p_id END;
Edit: When using an alias-table named 'aliases' with the fields 'alias_from_id' and 'aliased_as':
SELECT
CASE ISNULL((SELECT aliased_as FROM aliases WHERE (alias_from_id = log.pid)))
WHEN 1 THEN log.pid ELSE aliased_as END AS the_id,
SUM(value) AS value_sum
FROM log
LEFT JOIN aliases ON (alias_from_id = pid)
GROUP BY
CASE ISNULL((SELECT aliased_as FROM aliases WHERE (alias_from_id = log.pid)))
WHEN 1 THEN log.pid ELSE aliased_as END
I've tested this with mySQL and it works like a charm. It might be possible to simplify this SQL-query a bit but this is the best I can do at the moment :)
Edit2: Changed ISNULL to the corresponding SQLite IFNULL function
SELECT
IFNULL((SELECT aliased_as FROM aliases WHERE (alias_from_id = log.pid)), log.pid) AS the_id,
SUM(value) AS value_sum
FROM log
LEFT JOIN aliases ON (alias_from_id = pid)
GROUP BY
IFNULL((SELECT aliased_as FROM aliases WHERE (alias_from_id = log.pid)), log.pid)
I don't know if SQLite supports the AS keyword, if it doesn't then just remove that keyword - the functionality should be the same.

Filtering in Oracle based on a group of values contained in a list of values

I have following two tables:
ID_PERSON NAME
-----------------
1 John
2 Joe
3 Peter
ID_PERSON ID_SPECIALIZATION
------------------------------
1 5
1 6
1 7
2 5
2 1
3 6
3 10
I need to filter data based on group of ids ID_SPECIALIZATION that will be provided. For example
I want to display only those persons who has specialization in 5 and 6 so it will return only first person. In ASP.NET Web form there will be two listboxes, left and right button, in first LB there will be all possible specializations and user will choose some of them to second LB as filtering options. I have no idea how to put this filtering condition in sql query. Thanks for help.
You could use the following:
SQL> SELECT p.id_person, p.NAME
2 FROM person p
3 JOIN person_spe s ON p.id_person = s.id_person
4 WHERE id_specialization IN (5, 6)
5 GROUP BY p.id_person, p.NAME
6 HAVING COUNT(*) = 2;
ID_PERSON NAME
---------- -----
1 John
One way to do it:
SELECT
ID_PERSON
, NAME
FROM
Person AS p
WHERE EXISTS
( SELECT *
FROM
PersonSpecialization AS ps
WHERE ps.ID_PERSON = p.ID_PERSON
AND ps.ID_SPECIALIZATION = 5
)
AND EXISTS
( SELECT *
FROM
PersonSpecialization AS ps
WHERE ps.ID_PERSON = p.ID_PERSON
AND ps.ID_SPECIALIZATION = 6
)
SELECT d1.id_person, d1.name FROM tbl_table1 d1
INNER JOIN tbl_table2 d1
ON d1.ID_PERSON=d2.ID_PERSON
WHERE ID_SPECILIZATION = ?
Theres the query but I'm not sure how asp.net works and passing in the value. It might be work looking up bind variables which allows you to use place holders in the sql which oracle then caches the query and just uses the values that you pass in at run tuime using EXECUTE IMMEDIATE.

generate 6 numbers between 1 and 2 in a 2:1 ratio in SQL or PL/SQL

how can i generate 6 numbers between 1 and 2 where 4 of the numbers will be 1 and the other 2 will be 2 in a random order i.e.
results
1
2
1
1
1
2
and also in a different ratio i.e. 3:2:1 for numbers between 1 and 3 for 12 numbers
i.e.
results
1
1
2
3
1
2
1
3
1
1
3
3
results don't have to be in this order but in the ratios as above in oracle SQL or PL/SQL
To get the ratios perfect you could do something like this - generate all the numbers, then sort in random order:
SELECT r
FROM (SELECT CASE
WHEN ROWNUM <=4 THEN 1
ELSE 2
END AS r
FROM DUAL
CONNECT BY LEVEL <= 6)
ORDER BY DBMS_RANDOM.value;
R
----------------------
2
1
1
2
1
1
I think this will work in straight SQL; it's horrifically inefficient, and a PL/SQL one might be less so. It's also completely static; differing ratios call for a different number of values selected.
select value
from (
select mod(value, 2) + 1 as value,
row_number() over (partition by
case mod(value, 2) = 1
then 1
else 0
end) as twos_row,
row_number() over (partition by
case mod(value, 2) = 0
then 1
else 0
end) as ones_row
from (select dbms_crypto.randominteger as value
from dba_objects
order by object_id
)
)
where twos_rows <= 2
or ones_rows <= 4
The inner-most select grabs a big stack of random numbers. The next query out determines whether that random value would be a 2 or a 1 by mod'ing the earlier random value. The last level of nesting just filters out all the rows after the correct number of that type of row has been returned.
This is untested and fragile. If you need a solution that's reliable and performance, I'd recommend PL/SQL, where you
loop
pick off random numbers
determine what partition in your set of values they'd fit into
keep them if that partition hasn't been satisfied
exit when all partitions have been satisfied.

Resources