Ranking counts using an SQL query - sql

I am using DB Browser for SQLite. I have a table in the following format:
+-----------+-------------------------------------+
| search_id | search_town |
+-----------+-------------------------------------+
| 1 | town1,town3 |
| 2 | town2,town4,town5 |
| 3 | town3,town5 |
| 4 | town2,town5 |
| 5 | town2,town3,town4 |
+-----------+-------------------------------------+
I would like to do a COUNT on the number of times town1 through town5 has appeared under search_town, and then rank in descending order the towns based on their respective counts. So far I have the following query:
SELECT SUM(CASE WHEN search_location LIKE '%town01%' THEN 1 ELSE 0 END) AS town01,
SUM(CASE WHEN search_location LIKE '%town02%' THEN 1 ELSE 0 END) AS town02,
SUM(CASE WHEN search_location LIKE '%town03%' THEN 1 ELSE 0 END) AS town03,
SUM(CASE WHEN search_location LIKE '%town04%' THEN 1 ELSE 0 END) AS town04,
SUM(CASE WHEN search_location LIKE '%town05%' THEN 1 ELSE 0 END) AS town05
FROM searches
...but am unable to do an ORDER BY as the towns and their counts are output as columns instead of rows in this format
+-------+-------+-------+-------+-------+
| town1 | town2 | town3 | town4 | town5 |
+-------+-------+-------+-------+-------+
| 12 | 31 | 12 | 24 | 12 |
+-------+-------+-------+-------+-------+
Is there another approach to this? Appreciate any comments.

You are turning your output in a single row using CASE WHEN, to convert it into multiple rows, you can try like following.
;WITH cte
AS (SELECT *
FROM (VALUES ('Town1'),
('Town2'),
('Town3'),
('Town4'),
('Town5')) T(town))
SELECT Count(*) [Count],
C.town
FROM [TABLE_NAME] T
INNER JOIN cte C
ON T.search_location LIKE '%' + C.town + '%'
GROUP BY C.town
ORDER BY Count(*) DESC
Online DEMO
Another approach can be using UNION ALL.
SELECT *
FROM (SELECT Count(*) s,
'Town1' AS Col
FROM tablename
WHERE search_location LIKE '%town1%'
UNION ALL
SELECT Count(*) s,
'Town2' AS Col
FROM tablename
WHERE search_location LIKE '%town2%'
UNION ALL
SELECT Count(*) s,
'Town3' AS Col
FROM tablename
WHERE search_location LIKE '%town3%'
UNION ALL
SELECT Count(*) s,
'Town4' AS Col
FROM tablename
WHERE search_location LIKE '%town4%'
UNION ALL
SELECT Count(*) s,
'Town5' AS Col
FROM tablename
WHERE search_location LIKE '%town5%') t
ORDER BY s DESC

You can use a recursive common-table expression (CTE) to turn the comma-separated list into a set of rows. When the table is normalized, you can group by town and sort by descending count:
WITH rec(town, remain)
AS (
SELECT SUBSTR(search_town, 0, INSTR(search_town, ',')) -- Before ,
, SUBSTR(search_town, INSTR(search_town, ',')+1) || ',' -- After ,
FROM t1
UNION ALL
SELECT SUBSTR(remain, 0, INSTR(remain, ',')) -- Before ,
, SUBSTR(remain, INSTR(remain, ',')+1) -- After ,
FROM rec
WHERE LENGTH(remain) > 0
)
SELECT town
, COUNT(*)
FROM rec
GROUP BY
town
ORDER BY
COUNT(*) DESC
Idea from this blog post. Working example at sqliteonline.

Related

How put grouping variable to columns in SQL/

I have following dataset
and want to get this
How can I do it?
Using SQL Server, you can use a PIVOT, such as :
SELECT Time, [a],[b],[c]
FROM
(
SELECT time, [group],value
FROM dataset) d
PIVOT
(
SUM(value)
FOR [group] IN ([a],[b],[c])
) AS pvt
You can try it on the following fiddle.
Changed the column names to not conflict with reserved words. You would have to put them into single quotes otherwise.
WITH
-- the input
indata(grp,tm,val) AS (
SELECT 'a',1,44
UNION ALL SELECT 'a',2,22
UNION ALL SELECT 'a',3, 1
UNION ALL SELECT 'b',1, 1
UNION ALL SELECT 'b',2, 5
UNION ALL SELECT 'b',3, 6
UNION ALL SELECT 'c',1, 7
UNION ALL SELECT 'c',2, 8
UNION ALL SELECT 'c',3, 9
)
SELECT tm
, SUM(CASE grp WHEN 'a' THEN val END) AS a
, SUM(CASE grp WHEN 'b' THEN val END) AS b
, SUM(CASE grp WHEN 'c' THEN val END) AS c
FROM indata
GROUP BY tm
;
tm | a | b | c
----+----+---+---
1 | 44 | 1 | 7
2 | 22 | 5 | 8
3 | 1 | 6 | 9
select * from
(
select
time,[group],value
from yourTable
group by time,[group],value
)
as table
pivot
(
sum([value])
for [group] in ([a],[b],[c])
) as p
order by time
This is the result
for Vertica,
SELECT time
, SUM(value) FILTER (WHERE group = a) a
, SUM(value) FILTER (WHERE group = b) b
, SUM(value) FILTER (WHERE group = c) c
FROM yourTable
GROUP BY time

Split Columns into two equal number of Rows

I have the table structure below,
I need to merge the CouponNumber to two equal as CouponNumber1 and CouponNumber2 as shown in the figure
SELECT Name, MobileNumber, CouponNumber, IsDispatched, Status
FROM CouponInvoicePrescription
This is my query.
Try this:
WITH
input(ord,name,mobno,couponno,isdispatched,status) AS (
SELECT 0,'amar',8888888888,'CPever901',FALSE,1
UNION ALL SELECT 1,'amar',8888888888,'CP00005' ,FALSE,1
UNION ALL SELECT 2,'pt3' ,7777777777,'cp9090' ,FALSE,1
UNION ALL SELECT 3,'pt3' ,7777777777,'ev2' ,FALSE,1
UNION ALL SELECT 4,'pt3' ,7777777777,'cp9909' ,FALSE,1
UNION ALL SELECT 5,'pt3' ,7777777777,'cp10' ,FALSE,1
)
SELECT
name
, MAX(CASE ord % 2 WHEN 1 THEN couponno END) AS couponno1
, MAX(CASE ord % 2 WHEN 0 THEN couponno END) AS couponno2
, isdispatched
, status
FROM input
GROUP BY
ord / 2
, name
, isdispatched
, status
ORDER BY 1
-- out name | couponno1 | couponno2 | isdispatched | status
-- out ------+-----------+-----------+--------------+--------
-- out amar | CP00005 | CPever901 | f | 1
-- out pt3 | cp10 | cp9909 | f | 1
-- out pt3 | ev2 | cp9090 | f | 1
Try this:
SELECT * FROM
(
SELECT
sub.rn,
sub.Name,
sub.MobileNumber,
sub.CouponNumber as CouponNumber1,
LEAD(sub.CouponNumber,1) OVER (PARTITION BY sub.MobileNumber ORDER BY sub.rn) as CouponNumber2,
sub.IsDispatched,
sub.Status
FROM
(
SELECT
ROW_NUMBER() OVER (PARTITION by MobileNumber ORDER BY Name) as rn,
*
FROM
input
) sub
)
WHERE rn % 2 <> 0

SQLite: Use subquery result in another subquery

I have following table with data
id | COL1
=========
1 | b
2 | z
3 | b
4 | c
5 | b
6 | a
7 | b
8 | c
9 | a
So i know ID of 'z' (ID = 2) in the table and i will call it Z_ID.
I need to retrieve rows between 'a' and 'c' (including 'a' and 'c').
It must be first 'a' that comes after Z_ID.
'c' must come after Z_ID and after 'a' that i found previously.
Result that i am seeking is:
id | COL1
=========
6 | a
7 | b
8 | c
My SELECT looks like this
SELECT *
FROM table
WHERE id >= (
SELECT MIN(ID)
FROM table
WHERE COL1 = 'a' AND ID > 2
)
AND id <= (
SELECT MIN(ID)
FROM table
WHERE COL1 = 'c'AND ID > 2 and ID > (
SELECT MIN(ID)
FROM table
WHERE COL1 = 'a' AND ID > 2
)
)
I am getting the result that i want. But i am concerned about performance because i am using same subquery two times. Is there a way to reuse a result from first subquery?
Maybe there is cleaner way to get the result that i need?
Use a CTE which will return only once the result of the subquery that you use twice:
WITH cte AS (
SELECT MIN(ID) minid
FROM tablename
WHERE COL1 = 'a' AND ID > 2
)
SELECT t.*
FROM tablename t CROSS JOIN cte c
WHERE t.id >= c.minid
AND t.id <= (
SELECT MIN(ID)
FROM tablename
WHERE COL1 = 'c' and ID > c.minid
)
In your 2nd query's WHERE clause:
WHERE COL1 = 'c'AND ID > 2 and ID > (...
the condition AND ID > 2 is not needed because the next condition and ID > (... makes sure that ID will be greater than 2 so I don't use it either in my code.
See the demo.
Results:
| id | COL1 |
| --- | ---- |
| 6 | a |
| 7 | b |
| 8 | c |
You can use window functions for this:
select t.*
from (select t.*,
min(case when id > min_a_id and col1 = 'c' then id end) over () as min_c_id
from (select t.*,
min(case when col1 = 'a' then id end) over () as min_a_id
from (select t.*,
min(case when col1 = 'z' then id end) over () as z_id
from t
) t
where id > z_id
) t
) t
where id >= min_a_id and id < min_c_id;

SQL Transpose row to columns

I am trying to transpose rows to columns but I didn't find any good answers.
Here is an example of what I want:
Input tables:
TABLE A
ID | NAME
1 | BOB
2 | JIM
3 | ROB
TABLE B
ID | CLUB
1 | 2
1 | 3
1 | 4
2 | 2
2 | 1
3 | 5
OUTPUT will be:
ID | CLUB1 | CLUB2 | CLUB3
1 | 2 | 3 | 4
2 | 2 | 1 |
3 | 5 | |
You need to enumerate the values to pivot them:
select id,
max(case when seqnum = 1 then club end) as club_1,
max(case when seqnum = 2 then club end) as club_2,
max(case when seqnum = 3 then club end) as club_3
from (select b.*,
row_number() over (partition by id order by club) as seqnum
from b
) b
group by id;
use conditional aggregation
select id,
max(case when id=1 then club end) club1,
max(case when id=2 then club end) club2,
max(case when id=3 then club end) club3
from tablename
group by id
use case when
select a.id,max(case when name='BOB' then CLUB end) ,
max(case when name='JIM' then CLUB end),
max(case when name='ROB' then CLUB end)
tablea a join tableb b on a.id=b.id group by a.id
Sample Data
IF OBJECT_ID('tempdb..#TempTab')IS NOT NULL
DROP TABLE #TempTab
;WITH CTE (ID,CLUB)
AS
(
SELECT 1 , 2 UNION ALL
SELECT 1 , 3 UNION ALL
SELECT 1 , 4 UNION ALL
SELECT 2 , 2 UNION ALL
SELECT 2 , 1 UNION ALL
SELECT 3 , 5
)
SELECT ID,
CLUB,
'CLUB'+CAST(ROW_NUMBER()OVER(PARTITION BY ID ORDER BY ID) AS VARCHAR) AS CLUBData
INTO #TempTab
FROM CTE
Dynamic sql
DECLARE #Column nvarchar(1000),#Column2 nvarchar(max),
#Sql nvarchar(max)
SELECT #Column =STUFF((SELECT DISTINCT ', '+QUOTENAME(CLUBData)
FROM #TempTab FOR XML PATH ('')),1,1,'')
SET #Sql = 'SELECT Id,'+#Column +'
FROM
(
SELECT * FROM #TempTab
) AS SRc
PIVOT
(
MAX(CLUB) FOR CLUBData IN ('+#Column+')
) AS pvt
'
PRINT #Sql
EXEC (#Sql)
Result
Id CLUB1 CLUB2 CLUB3
-------------------------
1 3 4 2
2 1 2 NULL
3 5 NULL NULL

Select and merge rows in a table in SQL Stored procedure

Have a temp table with schema: ID | SeqNo | Name
ID - Not unique
SeqNo - Int (can be 1,2 or 3). Sort of ID+SeqNo as Primary key
Name - Any text
And sample data in the table like this
1 | 1 | RecordA
2 | 1 | RecordB
3 | 1 | RecordC
1 | 2 | RecordD
4 | 1 | RecordE
5 | 1 | RecordF
3 | 1 | RecordG
Need to select from this table and output like
1 | RecordA/RecordD
2 | RecordB
3 | RecordC/RecordG
4 | RecordE
5 | RecordF
Need to do this without cursor.
If SeqNo is limited to 1,2,3:
select id, a.name + coalesce('/'+b.name, '') + coalesce('/'+c.name, '')
from myTable a
left outer join myTable b on a.id=b.id and b.seqno = 2
left outer join myTable c on a.id=c.id and c.seqno = 3
where a.seqno = 1;
If SeqNo is open ended you can deploy a recursive cte:
;with anchor as (
select id, name, seqno
from myTable
where seqno=1)
, recursive as (
select id, name, seqno
from anchor
union all
select t.id, r.name + '/' + t.name, t.seqno
from myTable t
join recursive r on t.id = r.id and r.seqno+1 = t.seqno)
select id, name from recursive;
If you know SeqNo will never be more than 3:
select Id, Names = stuff(
max(case when SeqNo = 1 then '/'+Name else '' end)
+ max(case when SeqNo = 2 then '/'+Name else '' end)
+ max(case when SeqNo = 3 then '/'+Name else '' end)
, 1, 1, '')
from table1
group by Id
Otherwise, something like this is the generic solution to an arbitrary number of items:
select Id, Names = stuff((
select '/'+Name from table1 b
where a.Id = b.Id order by SeqNo
for xml path (''))
, 1, 1, '')
from table1 a
group by Id
Or write a CLR UDA.
Edit: had the wrong alias on the correlated table!
Edit2: another version, based on Remus's recursion example. I couldn't think of any way to select only the last recursion per Id, without aggregation or sorting. Anybody know?
;with
myTable as (
select * from (
values
(1, 1, 'RecordA')
, (2, 1, 'RecordB')
, (3, 1, 'RecordC')
, (1, 2, 'RecordD')
, (4, 1, 'RecordE')
, (5, 1, 'RecordF')
, (3, 2, 'RecordG')
) a (Id, SeqNo, Name)
)
, anchor as (
select id, name = convert(varchar(max),name), seqno
from myTable where seqno=1
)
, recursive as (
select id, name, seqno
from anchor
union all
select t.id, r.name + '/' + t.name, t.seqno
from myTable t
join recursive r on t.id = r.id and r.seqno+1 = t.seqno
)
select id, name = max(name)
from recursive
group by id;
---- without aggregation, we get 7 rows:
--select id, name
--from recursive;
The solution is good. I have a similar issue, but here I am using 2 different tables. ex:
table1
1 | 1 |
2 | 3 |
3 | 1 |
4 | 2 |
5 | 1 |
1 | 2 |
1 | 3 |
4 | 1 |
5 | 2 |
2 | 2 |
4 | 3 |
table2
1 | RecordA
2 | RecordB
3 | RecordC
I want to get the data from two tables and display in the below format.
1 | RecordA,RecordB,RecordC|
2 | RecordB,RecordC|
3 | RecordA |
4 | RecordA,RecordB,RecordC|
5 | RecordA,RecordB |