Related
I have a csv file containing three columns, class,malecount and femalecount as an input table.
My output should contain two columns named Class and Gender.
The malecount and femalecount values indicates how many times a row should be repeated. i.e. for Class = A and malecount=2, the row (A,M) should appear twice, and for Class = C and femalecount=3, the row (C,F) should appear three times. Check the following image to see the full output.
enter image description here
DDL & DML for the table:
create table mytable (class text, malecount int, femalecount int);
insert into mytable (class, malecount, femalecount) values
( 'A',2,1),
('B',3,1),
('C',0,3),
('D',2,4);
WITH RECURSIVE
-- define maximal amount of rows per class per gender to be generated
cte1 AS ( SELECT MAX(GREATEST(malecount, femalecount)) max_count
FROM test),
-- generate natural numbers till max. amount found above
cte2 AS ( SELECT 1 num
UNION ALL
SELECT num+1
FROM cte1
CROSS JOIN cte2
WHERE cte2.num <= cte1.max_count)
-- generate rows for male
SELECT test.class, 'm' gender
FROM test
JOIN cte2 ON cte2.num <= test.malecount
UNION ALL
-- generate rows for female
SELECT test.class, 'f'
FROM test
JOIN cte2 ON cte2.num <= test.femalecount
-- final sorting
ORDER BY gender DESC, class
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=694dbb214e4c0cd5524800c56a02dc65
I used the LPAD function and then trimmed off the last comma
from there I had a comma delimited string like M,M,M and F,F
then I used json_table to extract the M,M,M into three rows and the F,F into two rows etc.
here is the fiddle https://www.db-fiddle.com/f/jEXes6AttKvc9GKx1mKY2/1
Schema (MySQL v8.0)
create table mytable (class text, malecount int, femalecount int);
insert into mytable (class, malecount, femalecount) values
( 'A',2,1),
('B',3,1),
('C',0,3),
('D',2,4);
Query #1
with t as (select class,
LPAD(' ', malecount * 2 + 1, 'M,') malecount,
LPAD(' ', femalecount * 2 + 1, 'F,') femalecount
from mytable),
t2 as(
select class,
LEFT(malecount,length(malecount)-1) malecount,
LEFT(femalecount,length(malecount)-1) femalecount
from t)
select t2.class, j.name
from t2
join json_table(
replace(json_array(t2.malecount), ',', '","'),
'$[*]' columns (name varchar(50) path '$')
) j where j.name = 'M'
union all
select t2.class, k.name
from t2
join json_table(
replace(json_array(t2.femalecount), ',', '","'),
'$[*]' columns (name varchar(50) path '$')
) k where k.name = 'F';
class
name
A
M
A
M
B
M
B
M
B
M
D
M
D
M
A
F
B
F
D
F
D
F
View on DB Fiddle
You can use Recursive CTE as the following:
with recursive cte as
(
select *,0 as repeats from
(select class, malecount as cnt, 'M' as Gender from Tbl
union
select class, femalecount as cnt, 'F' as Gender from Tbl
) D
union all
select class,cnt,Gender, repeats+1 from cte
where repeats<cnt-1
)
select class, gender from cte
where cnt>0
order by gender desc,class;
See a demo from db-fiddle.
You can use the function,the expression:
CASE input_expression
WHEN when_expression THEN
result_expression [...n ] [
ELSE
else_result_expression
Is it possible to use only sql query no procedural language to get below format output
ID Name
-------------
1 a,b,c
2 x,y,z
3 m,n,l
I want this to print like below
1:a
1:b
1:c
2:x
2:y
2:z
Is this is possible with only my sql query or I have to use UDF ?
Yes you can-
The solution below is for Mysql.
You can use SUBSTRING_INDEX to reverse the process of 'group_concat'.
Here is your sql-
SELECT
id,
SUBSTRING_INDEX(SUBSTRING_INDEX(names, ',', n.d+1), ',', -1) name
FROM
users
INNER JOIN
(SELECT 0 d UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3) n
ON LENGTH(REPLACE(names, ',' , '')) <= LENGTH(names)-n.d
ORDER BY
id,
n.d
;WITH CTE(ID,Name)
AS
(
SELECT 1,'a,b,c' union all
SELECT 2,'x,y,z' union all
SELECT 3,'m,n,l'
)
SELECT ID, Split.a.value('.', 'VARCHAR(100)') AS Data
FROM
(
SELECT ID,
CAST ('<M>' + REPLACE(Name, ',', '</M><M>') + '</M>' AS XML) AS Data
from CTE
) AS A CROSS APPLY Data.nodes ('/M') AS Split(a);
If you want ouput with ':' separter ,below is the code
;WITH CTE(ID,Name)
AS
(
SELECT 1,'a,b,c' union all
SELECT 2,'x,y,z' union all
SELECT 3,'m,n,l'
)
SELECT CONCAT(CAST(ID AS VARCHAR(5)),' : ', CAST(Data AS VARCHAR(5))) As [OutPut] From
(
SELECT ID, Split.a.value('.', 'VARCHAR(100)') AS Data
FROM
(
SELECT ID,
CAST ('<M>' + REPLACE(Name, ',', '</M><M>') + '</M>' AS XML) AS Data
from CTE
) AS A CROSS APPLY Data.nodes ('/M') AS Split(a)
)Final
In SQL server, you could do it by a query with CROSS APPLY and xml query
DECLARE #SampleData AS TABLE (ID int IDENTITY (1,1), Name varchar(100))
INSERT INTO #SampleData (Name)
VALUES ('a,b,c'), ('x,y,z'), ('m,n,l')
;WITH temp AS
(
SELECT *,
CAST('<x>' + replace(sd.Name, ',', '</x><x>') + '</x>' AS xml) AS xmlText
FROM #SampleData sd
)
SELECT CONCAT(t.ID, ':',v.x.value('.','varchar(50)')) AS Result
FROM temp t
CROSS APPLY
t.xmlText.nodes('/x') AS v(x)
Demo link: http://rextester.com/IHQF16100
For example:
pk_ref fk
====== ===
1 a
1 b
1 c
2 a
2 b
2 d
How do I do a query like the "pseudo" query:
select distinc pk_ref
where fk in all('a', 'c');
The return query result must match all given values for the foreign key in the list.
The result should be:
1
While the following select must not return any records.
select distinc pk_ref
where fk in all('a', 'c', 'd');
How do I do that?
Try this
select pk_ref
from yourtable
group by pk_ref
having count(case when fk = 'a', then 1 end) >= 1
and count(case when fk = 'c' then 1 end) >= 1
To do it dynamically. (considering you are using SQL SERVER)
Create a split string function and pass the input as comma separated values
Declare #input varchar(8000)= 'a,c',#cnt int
set #cnt = len(#input)-len(replace(#input,',','')) + 1
select pk_ref
from yourtable
Where fk in (select split_values from udf_splitstring(#input , ','))
group by pk_ref
having count(Distinct fk) >= #cnt
You can create a split string function from the below link
https://sqlperformance.com/2012/07/t-sql-queries/split-strings
:list is the input list (bind variable). The difference of length() return values is the number of commas in the bind variable. This query, or something very close to it, should work in pretty much any DB product. Tested in Oracle.
select pk_ref
from tbl -- enter your table name here
where ',' || :list || ',' like '%,' || fk || ',%'
group by pk_ref
having count(distinct fk) = 1 + length(:list) - length(replace(:list, ',', ''))
If you can pass the IN operator values as Set, then you can do this as below
Schema:
SELECT * INTO #TAB FROM (
SELECT 1 ID, 'a' FK
UNION ALL
SELECT 1, 'b'
UNION ALL
SELECT 1, 'c'
UNION ALL
SELECT 2, 'a'
UNION ALL
SELECT 2, 'b'
UNION ALL
SELECT 2, 'd'
UNION ALL
SELECT 1, 'a'
)AS A
Used CTE to make 'a','c' as Set
;WITH CTE AS (
SELECT 'a' FK --Here 'a','c' passed as a Set through CTE
UNION
SELECT 'c'
)
,FINAL AS(
SELECT DENSE_RANK() OVER (PARTITION BY ID ORDER BY (FK))AS COUNT_ID, ID, FK
FROM #TAB where FK IN (select FK FROM CTE)
)
SELECT ID FROM FINAL WHERE COUNT_ID>=(SELECT COUNT( FK) FROM CTE)
Select pk_ref where fk='a' and pk_ref in (select pk_ref where fk='c' from yourtable) from yourtable;
or
select pk_ref where fk='a' from yourtable intersect select pk_ref where fk='c' from yourtable;
DECLARE #inputVariable VARCHAR(200) = 'a,b,c,d'
DECLARE #inputValue INT
DECLARE #tblInput TABLE
(
FK VARCHAR(100)
)
INSERT INTO #tblInput
SELECT SUBSTRING( #inputVariable+',',RN,1)
FROM (SELECT TOP 100 ROW_NUMBER() OVER(ORDER BY s.object_id) RN
FROM sys.objects s) s
where LEN(#inputVariable) >= RN
AND SUBSTRING(','+ #inputVariable,RN,1) = ','
SELECT #inputValue = COUNT(1) FROm #tblInput
--#inputVariable
DECLARE #tbl TABLE
(
ID INT,
FK VARCHAR(100)
)
INSERT INTO #tbl
SELECT 1 ID, 'a' FK
UNION ALL
SELECT 1, 'b'
UNION ALL
SELECT 1, 'c'
UNION ALL
SELECT 2, 'a'
UNION ALL
SELECT 2, 'b'
UNION ALL
SELECT 2, 'd'
UNION ALL
SELECT 1, 'a'
SELECT t.ID ,COUNT(DISTINCT t.FK)
FROM #tbl t
INNER JOIn #tblInput ti
ON t.FK = ti.FK
GROUP BY ID
HAVING COUNT(DISTINCT t.FK) = #inputValue
I want to find out the longest sequence of letter in a string
e.g. in the word Honorificabcdwert , the output will be abcd.
How can I do it?
My idea is to get the Ascii and then count the sequence until it breaks at some point. But I was able to proceed with only
DECLARE #t TABLE(ID INT IDENTITY,String VARCHAR(100))
INSERT INTO #t SELECT 'Honorificabcdwert'
;with Get_Individual_Chars_Cte AS
(
SELECT
ID
,Row_ID =ROW_NUMBER() Over(PARTITION by ID Order by ID)
,SUBSTRING(String,Number,1) AS [Char]
,ASCII(SUBSTRING(String,Number,1)) AS [Ascii Value]
FROM #t
INNER JOIN master.dbo.spt_values ON
Number BETWEEN 1 AND LEN(String)
AND type='P'
)
Select * from Get_Individual_Chars_Cte
After this I don't know what to do. Help needed for this or any other way of doing so.
Will this help
DECLARE #t TABLE(ID INT IDENTITY,String VARCHAR(100))
INSERT INTO #t
SELECT 'Honorificabcdwert' UNION ALL
SELECT 'AbCdEfxy' UNION ALL
SELECT 'abc1234defg' UNION ALL
SELECT 'XYZABCPPCKLMIDBABC' UNION ALL
SELECT 'MNOP$%^&~()MNOPQRS;:'
SELECT ID, OriginalString,Sequence
FROM (SELECT ID, REPLACE(string,'%','') AS Sequence,OriginalString,
ROW_NUMBER() OVER(PARTITION BY ID ORDER BY LEN(string) DESC, string) AS rn
FROM (SELECT OriginalString = b.String, CASE WHEN b.String LIKE a.strings THEN a.strings ELSE NULL END AS string,
b.ID, ROW_NUMBER() OVER(PARTITION BY ID ORDER BY LEN(strings) DESC, strings) AS rn
FROM (SELECT COALESCE('%' + b.strings+a.strings + '%','%' + a.strings + '%') AS strings
FROM (SELECT SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ',t1.N,t2.N-t1.N+1) AS strings, t1.N
FROM (VALUES(1),(2),(3),(4),(5),(6),(7),(8),
(9),(10),(11),(12),(13),(14),(15),
(16),(17),(18),(19),(20),(21),(22),
(23),(24),(25),(26)) t1(N)
CROSS JOIN (VALUES(1),(2),(3),(4),(5),(6),(7),(8),
(9),(10),(11),(12),(13),(14),(15),
(16),(17),(18),(19),(20),(21),(22),
(23),(24),(25),(26)) t2(N)
WHERE t1.N <= t2.N) a
LEFT OUTER JOIN (SELECT REVERSE(SUBSTRING('ZYXWVUTSRQPONMLKJIHGFEDCBA',1,N)) AS strings, 1 AS ID
FROM (VALUES(1),(2),(3),(4),(5),(6),(7),(8),
(9),(10),(11),(12),(13),(14),(15),
(16),(17),(18),(19),(20),(21),(22),
(23),(24),(25),(26)) t1(N)
UNION ALL SELECT '', 1) b ON a.N = b.ID) a
CROSS JOIN #t b) a ) a
WHERE a.rn = 1
ORDER BY a.ID
Result
ID OriginalString Sequence
1 Honorificabcdwert ABCD
2 AbCdEfxy ABCDEF
3 abc1234defg DEFG
4 XYZABCPPCKLMIDBABC XYZABC
5 MNOP$%^&~()MNOPQRS;: MNOPQRS
Based on your inputs provided in the course of discussion with #Martin Smith, the program is being developed. Please test it and let me know if it satisfies your requirement.
For consecutive rows with characters rising in alphabetical order (equating alphabetical order with ASCII order here) ROW_NUMBER() OVER (ORDER BY Row_ID) - [Ascii Value] will be the same.
This is not sufficient on its own however as for the string ABCZE that would put E in the same group as ABC so then you need a second operation to find gaps in that grouping sequence.
Something like the following should do it.
DECLARE #t TABLE(ID INT IDENTITY,String VARCHAR(100))
INSERT INTO #t SELECT 'Honorificabcdwfrt'
;with Get_Individual_Chars_Cte AS
(
SELECT
ID
,Row_ID =ROW_NUMBER() Over(PARTITION by ID Order by ID)
,SUBSTRING(String,number,1) AS [Char]
,ASCII(SUBSTRING(String,number,1)) AS [Ascii Value]
FROM #t
INNER JOIN master.dbo.spt_values ON
number BETWEEN 1 AND LEN(String)
AND type='P'
)
, T1 AS
(
Select *,
ROW_NUMBER() OVER (ORDER BY Row_ID) - [Ascii Value] AS RN
from Get_Individual_Chars_Cte
), T2 AS
(
SELECT *,
ROW_NUMBER() OVER (ORDER BY Row_ID) -
ROW_NUMBER() OVER (PARTITION BY RN ORDER BY Row_ID) AS Grp
FROM T1
)
SELECT TOP 1 WITH TIES *
FROM T2
ORDER BY COUNT(*) OVER (PARTITION BY RN, Grp) DESC
I have the following table #t:
ParentId SkuName ChildId
P1 X1 C1
C1 X2 C2
C2 X3 C2
If I pass the ParentId = P1, the desired output is x3
i.e. the stopping condition is the last row matching record and get the sku name for that
row. If no row matched, then return null
My attempt (no working though)
DECLARE #t TABLE (ParentId Varchar(100), Name VARCHAR(20), ChildId Varchar(100))
INSERT INTO #t(ParentId, Name, ChildId)
SELECT 'P1', 'X1', 'C1' UNION ALL
SELECT 'C1', 'X2', 'C2' UNION ALL
SELECT 'C2', 'X3', 'C2'
Declare #ParentId Varchar(10) = 'P1'
;With CTE As
(
Select
Rn = ROW_NUMBER() Over(Order By (Select 1))
,ParentId
, Name
, ChildId
From #t Where ParentId = #ParentId
Union All
Select
Rn + 1
,pc.ParentId as Parents
,pc.Name
,pc.ChildId
FROM #t pc
JOIN CTE gp on pc.Childid = gp.Parentid
)
Select *
From CTE
Please help
The problem is with the JOIN in your second UNION query. You are joining on pc.Childid = gp.Parentid. You should be joining on pc.ParentId = gp.Childid.
Also, since your data has both the child and the parent as the same value, you will end up with infinite recursion unless you specify that the recursion should stop when the ParentId equals the Childid. (WHERE gp.ParentId <> gp.Childid)
Is this the result you are looking for?
DECLARE #t TABLE (ParentId VARCHAR(100), Name VARCHAR(20), ChildId VARCHAR(100))
INSERT INTO #t(ParentId, Name, ChildId)
SELECT 'P1', 'X1', 'C1' UNION ALL
SELECT 'C1', 'X2', 'C2' UNION ALL
SELECT 'C2', 'X3', 'C2'
DECLARE #ParentId VARCHAR(10) = 'P1'
;With CTE As
(
SELECT
Rn = 1
,ParentId
,Name
,ChildId
FROM #t WHERE ParentId = #ParentId
UNION All
SELECT
Rn + 1
,pc.ParentId as Parents
,pc.Name
,pc.ChildId
FROM #t pc
JOIN CTE gp
on pc.ParentId = gp.Childid
WHERE gp.ParentId <> gp.Childid
)
SELECT TOP 1 *
FROM CTE
ORDER BY Rn DESC
OPTION (MAXRECURSION 0);