I have the following [table a]
id res1 res2
1 a f
1 b f
1 b f
1 c f
2 e g
2 e g
2 e g
2 f g
I'm getting the following after doing a group_concat
select
id,
group_concat(case when cnt = 1 then res1 else concat(cnt, ' ', res1) end) as r1,
group_concat(case when cnt = 1 then res2 else concat(cnt, ' ', res2) end) as r2
from
(
select id, res1,res2, count(*) as cnt
from [table a]
group by id, res1,res2
) t
group by id;
id r1 r2
1 a,2 b,c f,2 f,f
2 3 e,f 3 g,g
The res1 column is coming fine BUT res2 column is duplicating the res1 column.
Basically i want to print the value of how many times a character occurs before the character.
.I want in the following format..
id r1 r2
1 a,2 b,c 4 f
2 3 e,f 4 g
How can I achieve it ?
The way I would approach this is to do two rollups/aggregations, using two separate subqueries for the res1 and res2 columns. The first aggregation is over id and res1 (or res2), and obtains the counts for each letter or word. Then, aggregate again, this time only over the id, to obtain a comma separated string for each id. Finally, join these subqueries together to obtain the final result.
SELECT
t1.id, t1.r1, t2.r2
FROM
(
SELECT t.id, GROUP_CONCAT(res1agg ORDER BY res1) AS r1
FROM
(
SELECT
id,
res1,
CASE WHEN COUNT(*) = 1 THEN res1
ELSE CONCAT(CAST(COUNT(*) AS CHAR(50)), res1) END AS res1agg
FROM yourTable
GROUP BY id, res1
) t
GROUP BY t.id
) t1
INNER JOIN
(
SELECT t.id, GROUP_CONCAT(res2agg ORDER BY res2) AS r2
FROM
(
SELECT
id,
res2,
CASE WHEN COUNT(*) = 1 THEN res2
ELSE CONCAT(CAST(COUNT(*) AS CHAR(50)), res2) END AS res2agg
FROM yourTable
GROUP BY id, res2
) t
GROUP BY t.id
) t2
ON t1.id = t2.id;
Output:
Demo here:
Rextester
Just added 2 more conditions in your query without using more inner queries.
Try this:-
input:-
CREATE TABLE Table1 (id INT, res1 varchar(20), res2 varchar(20));
insert into Table1 values(1, 'a', 'f');
insert into Table1 values(1, 'b', 'f');
insert into Table1 values(1, 'b', 'f');
insert into Table1 values(1, 'c', 'f');
insert into Table1 values(2, 'e', 'g');
insert into Table1 values(2, 'e', 'g');
insert into Table1 values(2, 'e', 'g');
insert into Table1 values(2, 'f', 'g');
Query:-
Select t.id,group_concat(case when cnt = 1 then res1 else concat(cnt, ' ', res1) end) as r1,
case when id=1 then trim(concat(sum(case when id = 1 then cnt end),' ',res2))
else trim(concat(sum(case when id = 2 then cnt end),' ',res2)) end as r2
from
(
select id, res1,res2,count(*) as cnt
from table1 a
group by id, res1,res2
) t
group by t.id
My Output:-
id r1 r2
1 a,c,2 b 4 f
2 f,3 e 4 g
Let me know if you have any questions
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
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 have this table in mysql:
name ID
B1 1
B2 1
B3 1
B4 2
B5 2
B6 2
A1 3
A2 3
A3 3
I'd like to add data into a column, notifying that the id will change at the next line. Something like this:
name beforeChange ID
B1 NO 1
B2 NO 1
B3 YES 1
B4 NO 2
B5 NO 2
B6 YES 2
A1 NO 3
A2 NO 3
A3 NO 3
A4 NO 3
Is there any way to do this in sql?
Thanks!
Ugly as sin & probably far from ideal from an efficiency point of view but seems to work:
create table myTable (id int(10) unsigned not null,name varchar(10));
insert into myTable (id,name) values (1,'B1');
insert into myTable (id,name) values (1,'B2');
insert into myTable (id,name) values (1,'B3');
insert into myTable (id,name) values (2,'B4');
insert into myTable (id,name) values (2,'B5');
insert into myTable (id,name) values (2,'B6');
insert into myTable (id,name) values (3,'A1');
insert into myTable (id,name) values (3,'A2');
insert into myTable (id,name) values (3,'A3');
insert into myTable (id,name) values (3,'A4');
select a.id,
case
when b.id is null then 'NO'
when b.id = (select max(id) from myTable) then 'NO' -- < handle last line of results set
else 'YES' end as beforeChange,a.name
from
-- rank within id
(
select mt.id,mt.name,
case mt.id when #curId then #curRow := #curRow + 1
else #curRow := 1 and #curId := mt.id END as rank
from myTable mt
join (select #curRow := 0, #curId := -1) r
order by id,name asc
) a
left outer join
-- max rank by id
(
select t.id,max(t.rank) as maxRank
from (
select mt.*,
case mt.id
when #curId
then #curRow := #curRow + 1
else #curRow := 1 and #curId := mt.id END
as rank
from myTable mt
join (select #curRow := 0, #curId := -1) r
order by id,name asc
) t
group by t.id
) b on a.id = b.id and b.maxRank = a.rank;
current situation is to add below value of A01, B03, Z11 and X21 in repetitive way in field code for 400 hundreds row of data in table BabyCode.
Above is current table - without value in 'Code" column
Above is to be updated table - repetitive value is added in 'Code' column
You can do this:
INSERT INTO BabyCode
SELECT Codes.Code
FROM
(
SELECT id
FROM
(
SELECT t3.digit * 100 + t2.digit * 10 + t1.digit + 1 AS id
FROM TEMP AS t1
CROSS JOIN TEMP AS t2
CROSS JOIN TEMP AS t3
) t
WHERE id <= 400
) t,
(
SELECT 1 AS ID, 'A01' AS Code
UNION ALL
SELECT 2, 'B03'
UNION ALL
SELECT 3, 'Z11'
UNION ALL
SELECT 4, 'X21'
) codes;
But you will need to define a temp table, to use as an anchor table:
CREATE TABLE TEMP (Digit int);
INSERT INTO Temp VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
SQL Fiddle Demo
This will insert 400 hundred rows of the values A01, B03, Z11, and X21, into the code column in the table BabyCode.
You could put the four values into a virtual table identical to that used in #Mahmoud Gamal's answer, and, if the ID values in your table start at 1 and are sequential (have neither gaps nor duplicates), you could use the following method to join to the virtual table and update the target's Code column:
UPDATE YourTable t
INNER JOIN (
SELECT 1 AS ID, 'A01' AS Code
UNION ALL SELECT 2, 'B03'
UNION ALL SELECT 3, 'Z11'
UNION ALL SELECT 4, 'X21'
) x
ON (t.ID - 1) MOD 4 + 1 = x.ID
SET t.Code = x.Code
;
Otherwise you could use variables to assign 1, 2, 3, 4 sequentially to every row of your table, then you would be able join to the virtual table using those values:
UPDATE YourTable t
INNER JOIN (
SELECT ID, #rnk := CASE WHEN #rnk = 4 THEN 0 ELSE #rnk END + 1 AS rnk
FROM YourTable
CROSS JOIN (SELECT #rnk := 0) x
ORDER BY ID
) r ON t.ID = r.ID
INNER JOIN (
SELECT 1 AS ID, 'A01' AS Code
UNION ALL SELECT 2, 'B03'
UNION ALL SELECT 3, 'Z11'
UNION ALL SELECT 4, 'X21'
) x
ON r.rnk = x.ID
SET t.Code = x.Code
;
Both queries can be played with at SQL Fiddle:
Method 1
Method 2
I inherited a table with the following structure:
rowID rn userID Data1 Data2 Data3
----- -- ------ ----- ---- ----
1 1 1 A null 123
2 2 1 B 111 null
3 1 2 C 222 333
4 2 2 D null null
5 3 2 E 111 null
6 1 3 F 333 222
The first recs (rn=1) need to be inserted, while the rest (rn <>1) need to update the insertions (sequentially). I can insert easily, using where rn = 1 and checking for absence of the userID.
My problem is that I need to now update all recs sequentially using rn <>1 so that the user table reflects the latest state. That is, the user table after UPDATEs should look like this:
rowID userID Data1 Data2 Data3
----- ------ ----- ----- -----
1 1 B 111 123
2 2 E 111 333
3 3 F 333 222
My thought was to write a CTE, where each "pass" would grab all the recs where rn=2, then rn=3, then rn=4.... until I have no more rn to process. This way, I can update in sets.
Is this possible (or should I use do-while)? if so, do I need a recursive one or a "regular" CTE?
Here is what I tried:
;with my_cte (rowID, rn, userID, Data1, Data2, Data3, val) As
(
SELECT rowID, rn, userID, Data1, Data2, Data3, val
from #MyTempTable x
where rn =1
UNION ALL
SELECT rowID, rn, userID, Data1, Data2, Data3, b.val +1
from #MyTempTable y
INNER JOIN
my_cte b
ON y.userID = b.userID
WHERE y.rn = b.val +1
)
UPDATE userTable
SET
[Data1] = COALESCE(c.Data1, [Data1])
,[Data2]= COALESCE(c.Data2, [Data2])
,[Data3]= COALESCE(c.Data3, [Data3])
From #MyTempTable c
JOIN
( SELECT user_id
FROM my_cte
WHERE rn<>1
) b
ON b.user_id = c.user_id
WHERE
EXISTS
( Select userID
from userTable q
Where q.userId = b.userId
)
I could not get this to work, and it looks like only the first row is updating. Any thoughts? I'm a noob with CTEs. More than anything I'd like to know what exactly the CTE is doing... is it even possible for the update to run in "passes"?
Following CTE returns your given output for your given inputs. You can use these results as a starting point for inserting the records into another table.
The gist of it is
Use a recursive CTE, starting with all rows where rn=1.
In the recursive part, pick Data1-3 from the recursive part if available, otherwise retain the exisiting value (COALESCE). The result of the CTE now is your final values + the initial values where rn=1
Add a ROW_NUMBER for each userID but ORDER DESC on the existing rn. This makes sure that the latest values get rownumber 1.
Finally select all with rownumber 1 and add another rownumber as per your final results.
SQL Statement
;WITH q AS (
SELECT rn
, UserID
, Data1
, Data2
, Data3
FROM Inherited
WHERE rn = 1
UNION ALL
SELECT i.rn
, i.UserID
, COALESCE(i.Data1, q.Data1)
, COALESCE(i.Data2, q.Data2)
, COALESCE(i.Data3, q.Data3)
FROM q
INNER JOIN Inherited i ON i.rn = q.rn+1 AND i.userID = q.userID
)
SELECT rn = ROW_NUMBER() OVER (ORDER BY userID)
, *
FROM (
SELECT UserID
, Data1
, Data2
, Data3
, rn = ROW_NUMBER() OVER (PARTITION BY UserID ORDER BY rn DESC)
FROM q
) t
WHERE rn = 1
Test Script
;WITH Inherited (rowID, rn, userID, Data1, Data2, Data3) AS (
SELECT * FROM (VALUES
(1, 1, 1, 'A', null, '123')
, (2, 2, 1, 'B', '111', null)
, (3, 1, 2, 'C', '222', '333')
, (4, 2, 2, 'D', null, null)
, (5, 3, 2, 'E', '111', null)
, (6, 1, 3, 'F', '333', '222')
) a (b, c, d, e, f, g)
)
, q AS (
SELECT rn
, UserID
, Data1
, Data2
, Data3
FROM Inherited
WHERE rn = 1
UNION ALL
SELECT i.rn
, i.UserID
, COALESCE(i.Data1, q.Data1)
, COALESCE(i.Data2, q.Data2)
, COALESCE(i.Data3, q.Data3)
FROM q
INNER JOIN Inherited i ON i.rn = q.rn+1 AND i.userID = q.userID
)
SELECT rn = ROW_NUMBER() OVER (ORDER BY userID)
, *
FROM (
SELECT UserID
, Data1
, Data2
, Data3
, rn = ROW_NUMBER() OVER (PARTITION BY UserID ORDER BY rn DESC)
FROM q
) t
WHERE rn = 1