Count vowels and consonants in an array - function

I am trying to write a function which will return number of vowels
and consonants. Using the IF statement function will successfully
compile, however when I call it in the select it shows the message :
"Conversion failed when converting the varchar value 'MAMAMIA' to
data type int."`
I tried with the CASE statement, but there are too many syntax
errors and i think it is not the best method of solving the problem
using CASE ...
CREATE FUNCTION VOW_CONS(#ARRAY VARCHAR(20))
RETURNS INT
BEGIN
DECLARE #COUNTT INT;
DECLARE #COUNTT1 INT;
SET #COUNTT=0;
SET #COUNTT1=0;
WHILE (#ARRAY!=0)
BEGIN
IF(#ARRAY LIKE '[aeiouAEIOU]%')
SET #COUNTT=#COUNTT+1
ELSE SET #COUNTT1=#COUNTT1+1
/*
DECLARE #C INT;
SET #C=(CASE #SIR WHEN 'A' THEN #COUNTT=#COUNTT+1;
WHEN 'E' THEN #COUNTT=#COUNTT+1
WHEN 'I' THEN #COUNTT=#COUNTT+1
WHEN 'O' THEN #COUNTT=#COUNTT+1
WHEN 'U' THEN #COUNTT=#COUNTT+1
WHEN 'A' THEN #COUNTT=#COUNTT+1
WHEN ' ' THEN ' '
ELSE #COUNTT1=#COUNTT1+1
END)
*/
END
RETURN #COUNTT;
END
SELECT DBO.VOW_CONS('MAMAMIA')

Without knowing what version of SQL Server you are using, I am going to assume you are using the latest version, meaning you have access to TRANSLATE. Also I'm going to assume you do need access to both the number of vowels and consonants, so a table-value function seems the better method here. As such, you could do something like this:
CREATE FUNCTION dbo.CountVowelsAndConsonants (#String varchar(20))
RETURNS table
AS RETURN
SELECT DATALENGTH(#String) - DATALENGTH(REPLACE(TRANSLATE(#String,'aeiou','|||||'),'|','')) AS Vowels, --Pipe is a character that can't be in your string
DATALENGTH(#String) - DATALENGTH(REPLACE(TRANSLATE(#String,'bcdfghjklmnpqrstvwxyz','|||||||||||||||||||||'),'|','')) AS Consonants --Pipe is a character that can't be in your string
GO
And then you can use the function like so:
SELECT *
FROM (VALUES('MAMAMIA'),('Knowledge is power'))V(YourString)
CROSS APPLY dbo.CountVowelsAndConsonents(V.YourString);

Another option would be to split the string into its individual letters using a tally table, join that to a table of vowels/consonants and then get your counts.
Here is a working example, you'll have to update/change for your specific needs but should give you an idea on how it works.
DECLARE #string NVARCHAR(100)
SET #string = 'Knowledge is power';
--Here we build a table of all the letters setting a flag on which ones are vowels
DECLARE #VowelConsonants TABLE
(
[Letter] CHAR(1)
, [IsVowel] BIT
);
--We load it with the data
INSERT INTO #VowelConsonants (
[Letter]
, [IsVowel]
)
VALUES ( 'a', 1 ) , ( 'b', 0 ) , ( 'c', 0 ) , ( 'd', 0 ) , ( 'e', 1 ) , ( 'f', 0 ) , ( 'g', 0 ) , ( 'h', 0 ) , ( 'i', 1 ) , ( 'j', 0 ) , ( 'k', 0 ) , ( 'l', 0 ) , ( 'm', 0 ) , ( 'n', 0 ) , ( 'o', 1 ) , ( 'p', 0 ) , ( 'q', 0 ) , ( 'r', 0 ) , ( 's', 0 ) , ( 't', 0 ) , ( 'u', 1 ) , ( 'v', 0 ) , ( 'w', 0 ) , ( 'x', 0 ) , ( 'y', 0 ) , ( 'z', 0 );
--This tally table example gives 10,000 numbers
WITH
E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
)
SELECT SUM( CASE WHEN [vc].[IsVowel] = 1 THEN 1
ELSE 0
END
) AS [VowelCount]
, SUM( CASE WHEN [vc].[IsVowel] = 0 THEN 1
ELSE 0
END
) AS [ConsonantCount]
FROM [cteTally] [t] --Select from tally cte
INNER JOIN #VowelConsonants [vc] --Join to VowelConsonants table on the letter.
ON LOWER([vc].[Letter]) = LOWER(SUBSTRING(#string, [t].[N], 1)) --Using the number from the tally table we can easily split out each letter using substring
WHERE [t].[N] <= LEN(#string);
Giving you results of:
VowelCount ConsonantCount
----------- --------------
6 10

Related

Split comma separated values in a particular comma postion using SQL or SSRS report

I have a field in SSRS that is concatenated values like
1234,1456,3456,7890,3457,3245,4345
I need to break/split after 8th comma or in a particular position in next row like:
1234,1456,3456,
7890,3457,3245,
4345
Here values are dynamic but, we have to split/break at every 8th or particular comma
In your example text, all the values have four characters. If that is the case, a simple recursive CTE does what you want:
with cte as (
select convert(varchar(max), NULL) as val, convert(varchar(max), field) as rest, 0 as lev
from t
union all
select left(rest, 15) as val, stuff(rest, 1, 15, '') as rest, lev+1
from cte
where rest <> ''
)
select val
from cte
where lev > 0;
Here is a db<>fiddle.
Once you've grabbed a copy of DelimitedSplit8K_LEAD (as STRING_SPLIT has no concept of ordinal positions) you can split the string and then "re-aggregate" it.
Using SQL Server 2017+:
DECLARE #YourString varchar(8000) = '1234,1456,3456,7890,3457,3245,4345';
WITH Split AS(
SELECT DS.Item,
DS.ItemNumber,
(DS.ItemNumber - 1) / 3 AS Grp
FROM dbo.DelimitedSplit8K_LEAD(#YourString,',') DS)
SELECT STRING_AGG(S.Item,',') WITHIN GROUP (ORDER BY S.ItemNumber ASC) AS NewString
FROM Split S
GROUP BY S.Grp;
SQL Server 2016-:
DECLARE #YourString varchar(8000) = '1234,1456,3456,7890,3457,3245,4345';
WITH Split AS(
SELECT DS.Item,
DS.ItemNumber,
(DS.ItemNumber - 1) / 3 AS Grp
FROM dbo.DelimitedSplit8K_LEAD(#YourString,',') DS)
SELECT STUFF((SELECT ',' + sq.Item
FROM Split sq
WHERE sq.Grp = S.Grp
ORDER BY sq.ItemNumber
FOR XML PATH(''),TYPE).value('.','varchar(8000)'),1,1,'') AS MewString
FROM Split S
GROUP BY S.Grp;
If you use SQL Server 2016+, you may use an approach, based on JSON. Just transform the input text into a valid JSON array and parse this array with OPENJSON():
Example with text:
Statement:
DECLARE #json nvarchar(max) = N'1234,1456,3456,7890,3457,3245,4345'
SELECT CONCAT(
MAX(CASE WHEN CONVERT(int, [key]) % 3 = 0 THEN [value] END),
MAX(CASE WHEN CONVERT(int, [key]) % 3 = 1 THEN [value] END),
MAX(CASE WHEN CONVERT(int, [key]) % 3 = 2 THEN [value] END)
) AS OutputText
FROM OPENJSON(CONCAT(N'["', REPLACE(#json, N',', N',","'), N'"]'))
GROUP BY (CONVERT(int, [key]) / 3)
Result:
---------------
OutputText
---------------
1234,1456,3456,
7890,3457,3245,
4345
Example with table:
Table:
CREATE TABLE Data (TextData nvarchar(max))
INSERT INTO Data (TextData)
VALUES (N'1234,1456,3456,7890,3457,3245,4345')
Statement:
SELECT d.TextData, c.OutputData
FROM Data d
CROSS APPLY (
SELECT CONCAT(
MAX(CASE WHEN CONVERT(int, [key]) % 3 = 0 THEN [value] END),
MAX(CASE WHEN CONVERT(int, [key]) % 3 = 1 THEN [value] END),
MAX(CASE WHEN CONVERT(int, [key]) % 3 = 2 THEN [value] END)
) AS OutputData
FROM OPENJSON(CONCAT(N'["', REPLACE(d.TextData, N',', N',","'), N'"]'))
GROUP BY (CONVERT(int, [key]) / 3)
) c
Result:
---------------------------------------------------
TextData OutputData
---------------------------------------------------
1234,1456,3456,7890,3457,3245,4345 1234,1456,3456,
1234,1456,3456,7890,3457,3245,4345 7890,3457,3245,
1234,1456,3456,7890,3457,3245,4345 4345

MySQL: Why would a query run faster with literal conditions compared to variables

Not sure whether the actually query matters but, I have a MySQL Stored Procedure where I commented out the other parts of the proc except the following query...
INSERT INTO temp_attribution (`attribute_type`, `domain`, `id`, `name`, `score`, `rank`, `partner_match`, `person_match`, `sponsor_match`, `date_match`)
SELECT 'Campaign' AS attribute_type, domain, id, name, score, (#proc_counter := #proc_counter + 1) AS rank,
partner_match, person_match, sponsor_match, date_match
FROM (
SELECT m_c.domain, m_c.campaign_id AS id, m_c.name, m_c.client_id, m_c.sent_date,
proc_sponsors AS invoice_sponsor, bs.sponsor AS campaign_sponsor,
proc_email AS invoice_email, aes_decrypt(m_r.email, in_encrypt_key) as campaign_email,
if (m_c.client_id = proc_client_id COLLATE latin1_general_ci, 'Yes', 'No') AS partner_match,
if (aes_encrypt(proc_email, in_encrypt_key) = m_r.email, 'Exact Email', 'Email Domain') AS person_match,
if (LOCATE(CONVERT(bs.sponsor USING utf8mb4), proc_sponsors) > 0, 'Sponsor',
if (CONVERT(bs.vendor USING utf8mb4) = proc_vendor, 'Vendor', 'No') ) AS sponsor_match,
if (datediff(proc_invoice_date, m_c.sent_date) BETWEEN 0 AND 92, 'Within Three', 'Within Six') AS date_match,
(
if (m_c.client_id = proc_client_id COLLATE latin1_general_ci, 45, 10) + 30 +
if (LOCATE(CONVERT(bs.sponsor USING utf8mb4), proc_sponsors) > 0, 10,
if (CONVERT(bs.vendor USING utf8mb4) = proc_vendor, 5, 0) ) +
if (datediff(proc_invoice_date, m_c.sent_date) BETWEEN 0 AND 92, 15, 5)
) AS score
FROM campaign_table m_c
INNER JOIN recipient_table m_r ON m_c.domain = m_r.domain AND m_c.campaign_id = m_r.campaign_id
LEFT JOIN booking_sponsor bs ON m_c.domain = bs.domain AND m_c.campaign_id = bs.campaign_id
WHERE datediff(proc_invoice_date, m_c.sent_date) BETWEEN 0 AND 185
AND ( aes_encrypt(proc_email, in_encrypt_key) = m_r.email OR m_r.email_domain = proc_email_domain )
) T ORDER BY score DESC, sent_date DESC LIMIT 5;
The fields starting with 'proc_' are actually variables declared at the beginning of the procedure and this only takes 0.385 seconds to initialise whereas the entire proc takes 15 seconds.
On a separate query window, I copied the relevant query and substituted variables starting with 'proc_' to test speed and optimise, like so...
INSERT INTO temp_attribution (`attribute_type`, `domain`, `id`, `name`, `score`, `rank`, `partner_match`, `person_match`, `sponsor_match`, `date_match`)
SELECT 'Campaign' AS attribute_type, domain, id, name, score, (#proc_counter := #proc_counter + 1) AS rank,
partner_match, person_match, sponsor_match, date_match
FROM (
SELECT m_c.domain, m_c.campaign_id AS id, m_c.name, m_c.client_id, m_c.sent_date,
'VENDOR SPONSOR VALUE' AS invoice_sponsor, bs.sponsor AS campaign_sponsor,
'johnsmith#domain.com' AS invoice_email, aes_encrypt('johnsmith#domain.com', 'secret_key') as campaign_email,
if (m_c.client_id = m_c.client_id COLLATE latin1_general_ci, 'Yes', 'No') AS partner_match,
if (aes_encrypt('johnsmith#domain.com', 'secret_key'), 'Exact Email', 'Email Domain') AS person_match,
if (LOCATE(CONVERT(bs.sponsor USING utf8mb4), 'VENDOR SPONSOR VALUE') > 0, 'Sponsor',
if (CONVERT(bs.vendor USING utf8mb4) = 'VENDOR', 'Vendor', 'No') ) AS sponsor_match,
if (datediff('2016-10-14', m_c.sent_date) BETWEEN 0 AND 92, 'Within Three', 'Within Six') AS date_match,
(
if (m_c.client_id = m_c.client_id COLLATE latin1_general_ci, 45, 10) + 30 +
if (LOCATE(CONVERT(bs.sponsor USING utf8mb4), 'VENDOR SPONSOR VALUE') > 0, 10,
if (CONVERT(bs.vendor USING utf8mb4) = 'VENDOR', 5, 0) ) +
if (datediff('2016-10-14', m_c.sent_date) BETWEEN 0 AND 92, 15, 5)
) AS score
FROM campaign_table m_c
INNER JOIN recipient_table m_r ON m_c.domain = m_r.domain AND m_c.campaign_id = m_r.campaign_id
LEFT JOIN booking_sponsor bs ON m_c.domain = bs.domain AND m_c.campaign_id = bs.campaign_id
WHERE datediff('2016-10-14', m_c.sent_date) BETWEEN 0 AND 185
AND ( aes_encrypt('johnsmith#domain.com', 'secret_key') = m_r.email OR m_r.email_domain = 'domain.com' )
) T ORDER BY score DESC, sent_date DESC LIMIT 5;
Now, magically without doing anything else, the query runs within two seconds. How is that possible?
Figured it out. Some of the declared variable type was different compared to the column being compared, so I guess MySQL could not compare them in the most efficient way possible.

output sql data to html with hyperlink

I have below query that converts sql result to HTML
DECLARE #BODY VARCHAR(MAX)
SET #BODY = CAST((
SELECT td = entity + '</td><td>' + procname + '</td><td>' + exedate + '</td><td>' + rowcnt + '</td><td>' +
cont + '</td><td>' + fpath from (
SELECT entity = ENTITY , procname = PROC_NAME,exedate = EXEC_DATE, rowcnt = ROW_COUNT,
cont = CONTENT, fpath = FILEPATH FROM HTML_OUTPUTFOREMAIL) AS D
for xml path('tr'),type) as varchar(max))
set #BODY = '<tr><h1>SUMMARY TABLE</h1>'
+'<table cellpadding="2" cellspacing="2" border="1">'
+'<tr><th>ENTITY</th><th>PROC_NAME</th><th>EXEC_DATE</th><th>ROW_COUNT</th><th>CONTENT</th><th>FILEPATH</th><tr>'
+ REPLACE(replace(#body,'<','<'),'>','>')
+'<table>'
print #body
In this data I have a column "fpath" that contains link to the HTML file. But I want the result of this column in a hyperlink.
This is not really SQL Server jobs to output HTML.
If you have to create it this way, you can generate the whole table with FOR XML Explicit.
I am just creating a dummy table with 4 line here and dummy data. You still have to change columns name or add more.
declare #t table(id int, entity varchar(10), name varchar(10), fpath varchar(50), link_name varchar(10), exec_date datetime)
insert into #t(id, entity, name, fpath, link_name, exec_date) values
(1, 'ent1', 'A', 'google.com', 'linkA', '20150115')
, (2, 'ent2', 'B', 'google.com', 'linkB', '20150215')
, (3, 'ent3', 'C', 'google.com', 'linkC', '20150315')
, (4, 'ent4', 'D', 'google.com', 'linkD', '20150415')
Select Tag, Parent
, [Table] as 'Table!1!'
, [entity] as 'TR!2!TD!Element'
, [name] as 'TR!2!TD!Element'
, [exec_date] as 'TR!2!TD!Element'
, [TD] as 'TD!3!TD!Element'
, [href] as 'A!4!href'
, [target] as 'A!4!target'
, [link] as 'A!4!'
From(
Select 1 as Tag, NULL as Parent, 0 as sort
, NULL as 'Table'
, NULL as 'entity'
, NULL as 'name'
, NULL as 'exec_date'
, NULL as 'TD'
, NULL as 'href'
, NULL as 'link'
, NULL as 'Target'
Union All
Select 2 as Tag, 1 as Parent, id*10 as sort
, NULL
, entity
, name
, cast(exec_date as varchar(50))
, NULL
, NULL
, NULL
, NULL
From #t
Union All
Select 3 as Tag, 2 as Parent, id*10+1 as sort
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
From #t
Union All
Select 4 as Tag, 3 as Parent, id*10+2
, NULL
, NULL
, NULL
, NULL
, NULL
, fpath
, link_name
, '_blank'
From #t
) X
Order By sort
For XML EXplicit

count not null columns out of specific list of columns and then compare in where clause for each row

Table structure:
Table Structure http://imagebin.org/index.php?mode=image&id=238883
I want to fetch data from both the tables which satisfy some of the conditions like
WHERE batch='2009', sex='male',course='B.Tech', branch='cs', xth_percent>60,
x2percent>60, gradpercent>60 and (if ranktype='other' ) then
no._of_not_null_semester>number elseifranktype='Leet') then
no._of_not_null_semester>number-2
sem 1-8 contains percentage for 8 semesters, and I want to filter results for each student if they have cleared 3 semesters or 4 semester i.e. not null values out of 8 values
no._of_not_null_semester
needs to be calculated, it is not a part of database, need help with that as well.
Required Query
SELECT * FROM student_test
INNER JOIN master_test ON student_test.id=master_test.id
WHERE sex='male' and batch='2009' and course='B.Tech'
and xthpercent>60 and x2percent>60 and
WHEN ranktype='Leet' THEN
SUM(CASE WHEN sem1 IS NOT NULL THEN 1 ELSE 0
WHEN sem2 IS NOT NULL THEN 1 ELSE 0
WHEN sem3 IS NOT NULL THEN 1 ELSE 0
WHEN sem4 IS NOT NULL THEN 1 ELSE 0
WHEN sem5 IS NOT NULL THEN 1 ELSE 0) >2
ELSE
SUM(CASE WHEN sem1 IS NOT NULL THEN 1 ELSE 0
WHEN sem2 S NOT NULL THEN 1 ELSE 0
WHEN sem3 IS NOT NULL THEN 1 ELSE 0
WHEN sem4 IS NOT NULL THEN 1 ELSE 0
WHEN sem5 IS NOT NULL THEN 1 ELSE 0) >4
Without changing the structure you can't use COUNT to achieve this.
One way to solve the problem would be to create a semester table which would contain a row for each finished semester for each student. This would have a foreign key pointing to test_student.id and you could use COUNT(semester.id)
If that is an option for you, it would be the best solution.
EDIT:
Check this out, didn't test the query but should work generally
I decided to do the math in the select itself to prevent calculating the same thing twice.
The HAVING conditions are applied after your result is ready to deliver, just before a LIMIT.
In terms of speed optimization you could try and move the sSum block into the WHERE condition just like you had it before. Probably it doesn't make much of a difference
SUM() does not work because it is an aggregate function which summarizes values in a column
I did some changes to your query in addition:
don't SELECT *, select specific fields and add a table identifier. ( in this case I used the aliases s for student_test AND m for master_test )
you put s.batch = '2009' into single quotes - if the field batch is an integer field, you should use s.batch = 2009, which would prevent MySQL from casting every single row to string to be able to compare it (int = int much faster than cast(int as varchar) = varchar) same about the other numeric values in your table
The Query:
SELECT
s.id,
s.sex,
s.course,
s.branch,
(
IF ( m.sem1 IS NOT NULL, 1, 0 ) +
IF ( m.sem2 IS NOT NULL, 1, 0 ) +
IF ( m.sem3 IS NOT NULL, 1, 0 ) +
IF ( m.sem4 IS NOT NULL, 1, 0 ) +
IF ( m.sem5 IS NOT NULL, 1, 0 ) +
IF ( m.sem6 IS NOT NULL, 1, 0 ) +
IF ( m.sem7 IS NOT NULL, 1, 0 ) +
IF ( m.sem8 IS NOT NULL, 1, 0 )
) AS sSum
FROM
student_test s
INNER JOIN master_test m ON m.id = s.id
WHERE
s.sex = 'male'
AND
s.batch = '2009' # I dont see this field in your database diagram!?
AND
s.course = 'B.Tech'
AND
m.xthpercent > 60
AND
m.x2percent > 60
HAVING
(
m.ranktype = 'OTHER'
AND
sSum > 4
)
OR
(
m.ranktype = 'LEET'
AND
sSum > 2
)
If you're generally interested in learning database design and usage I found an very interesting opportunity for you.
Stanford University offers a free database class "Introduction to databases". This is free and will cost you approx. 2 hours a week for 3 weeks, final exam included.
https://class2go.stanford.edu/db/Winter2013/preview/
SELECT
s.id,
s.sex,
s.course,
s.deptt,
m1.id,
m1.xthpercent,
m1.x2percent,
m1.sem1,
m1.sem2,
m1.sem3,
m1.ranktype,
m1.sem4,
m1.sem5,
m1.sem6,
m1.sem7,
m1.sem8,
m1.sSum
FROM
student_test s
INNER JOIN(SELECT m.id,
m.xthpercent,
m.x2percent,
m.sem1,
m.sem2,
m.sem3,
m.ranktype,
m.sem4,
m.sem5,
m.sem6,
m.sem7,
m.sem8,
( IF ( ceil(m.sem1)>0, 1, 0 ) +
IF ( ceil(m.sem2)>0, 1, 0 ) +
IF ( ceil(m.sem3)>0, 1, 0 ) +
IF ( ceil(m.sem4)>0, 1, 0 ) +
IF ( ceil(m.sem5)>0, 1, 0 ) +
IF ( ceil(m.sem6)>0, 1, 0 ) +
IF ( ceil(m.sem7)>0, 1, 0 ) +
IF ( ceil(m.sem8)>0, 1, 0 )
) AS sSum FROM master_test m
WHERE m.xthpercent>60 and
m.x2percent>60
HAVING (m.ranktype='Leet' AND sSum>2 )
OR
(m.ranktype != 'Leet') AND sSum>4 )
as m1 ON m1.id = s.id
WHERE
s.sex='Male'
and
s.course='B.Tech'
and
s.deptt='ELE'
This is the query finally I'm using, Love that query :)

what wrong with my SQL statement of condition insert in mysql?

There:
When I try to do data insert data in my table without any duplicated record, I use both of following SQL statement. But none of them work for me. My verion of mysql is 5.1.33-community.
SELECT #nextID := IFNULL( MAX( N_KEY ), 0 ) + 1
FROM SYS_DICT
WHERE N_CAT = 1;
IF (SELECT COUNT(*) AS N_COUNT
FROM SYS_DICT
WHERE N_CAT=1 AND C_VALUE='welcome'
) <= 0
THEN
INSERT INTO SYS_DICT
VALUES( 1, #nextID, 1, 100, 'welcome');
END IF
or
SELECT #nextID := IFNULL( MAX( N_KEY ), 0 ) + 1
FROM SYS_DICT
WHERE N_CAT=1;
INSERT INTO SYS_DICT
VALUES ( 1, #nextID, 1, 100, 'welcome')
WHERE NOT EXISTS (
SELECT N_KEY
FROM SYS_DICT
WHERE N_CAT=1 AND C_VALUE='welcome'
);
Any hint?