How do I `group by` rows and columns in SQLite3? - sql

SQLite database table table1
user command date location
---------- ---------- ---------- ----------
user1 cmd1 2015-01-01 xxxdeyyy
user2 cmd1 2015-01-01 zzzfrxxx
user3 cmd1 2015-01-01 yyyukzzz
user1 cmd1 2015-01-01 xxxdezzz
...
Expected output
Output for where command='cmd1':
month users_de users_fr users_es
-------- -------- -------- --------
01 1 0 5
02 2 0 0
03 0 2 1
04 5 0 15
05 1 0 4
06 11 1 2
07 9 0 3
08 1 0 5
09 0 0 5
10 0 0 0
11 1 0 0
12 1 4 5
It is grouped by month (from column date) and also grouped by a substring in location (from column location).
Actual output
I can achieve this (per location):
month users_de
-------- --------
01 1
02 2
03 0
...
12 1
using this query:
select strftime('%m',date) as month, count(distinct user) as users_de
from table1
where command='cmd1' and location like '%de%'
group by strftime('%m',date);
I then repeat this query for the other locations (where ... and location='fr'):
month users_fr
-------- --------
01 0
02 0
03 2
...
12 4
and (where ... and location='es');
month users_es
-------- --------
01 5
02 0
03 1
...
12 5
Is there a way to have all the users_xx columns in one table (as output from SQLite and not through any external (downstream) processing)?
Am I thinking about this in the wrong way (grouping instead of subqueries in the top select)?

You can use the case statement to match each location and then if matches count the user.
select strftime('%m',date) as month,
CASE WHEN location='de' THEN count(distinct user) END users-de,
CASE WHEN location='fr' THEN count(distinct user) END users-fr,
CASE WHEN location='es' THEN count(distinct user) END users-es,
from table1
where command='cmd1'
group by strftime('%m',date),location;

I think you want conditional aggregation:
select strftime('%m',date) as month,
count(distinct CASE WHEN location like '%de%' THEN user END) as users_de,
count(distinct CASE WHEN location like '%fr%' THEN user END) as users__fr,
count(distinct CASE WHEN location like '%es%' THEN user END) as users_es
from table1
where command = 'cmd1'
group by strftime('%m',date);
Two notes:
like possibly isn't safe in this context. You have the country code embedded in the string, but the characters "de", "es", or "fr" could appear elsewhere in the string. Your question is not clear on better logic for this.
You should include the year in the date string, but your question specifically includes only the month.

Using query like this:
SELECT strftime('%m',date) AS month,
location,
count(distinct user) AS users-de,
count(distinct user) AS users-fr,
count(distinct user) AS users-es
FROM table1
WHERE command='cmd1' GROUP BY strftime('%m', date), location;

Related

SQL Server, joining all values in column with some values of another

I'm pretty bad at explaining, so I'll try and let my examples do most of the talking. Let's say I have a table like so:
dbo.ExampleTable
===================================
ID Year Data1 Data2
====== ======== ========= =========
12 2016 FOO BAR
13 2016 FOO MAN
14 2016 SAW BAR
20 2017 FOO BAR
21 2017 FOO MAN
27 2017 SAW BAR
29 2017 CHU CAR
44 9999 FOO BAR
48 9999 FOO MAN
51 9999 SAW BAR
52 9999 CHU CAR
Some notes:
ID is unique
(Year, Data1, Data2) is unique
The only values in the Year column will be 2016, 2017 or 9999
I want to create a table from that data that looks like this:
ID_9999 ID_2016 ID_2017
=========== =========== ===========
44 12 20
48 13 21
51 14 27
52 NULL 29
So essentially, for every unique pairing of Data1 and Data2 where Year=9999, I want to create a row which contains the ID of that pairing where Year=9999, as well as the ID for the pairings where Year=2016 and also Year=2017. Additionally, if either 2016 or 2017 do not containing that Data pairing, I want their value as NULL.
This is the query I've got so far:
SELECT tbl9999.ID ID_9999,
tbl2016.ID ID_2016,
tbl2017.ID ID_2017
FROM dbo.ExampleTable tbl9999
LEFT JOIN dbo.ExampleTable tbl2016
ON tbl9999.Data1 = tbl2016.Data1
AND tbl9999.Data2 = tbl2016.Data2
LEFT JOIN dbo.ExampleTable tbl2017
ON tbl9999.Data1 = tbl2017.Data1
AND tbl9999.Data2 = tbl2017.Data2
WHERE tbl9999.Year=9999
AND tbl2016.Year=2016
AND tbl2017.Year=2017
This seems to work mostly fine, however it will generate a table like this:
ID_9999 ID_2016 ID_2017
=========== =========== ===========
44 12 20
48 13 21
51 14 27
*Notice that it's missing the row with the null value in my example above. Is there any way to change my query to include that null value such that I have it in my example?
Please let me know if I'm missing any information or need anything clarified. Thanks in advance!
EDIT:
I was able to find an answer on my own! This is the code I used to achieve my desired result:
SELECT [9999] [ID_9999],
[2016] [ID_2016],
[2017] [ID_2017]
FROM dbo.ExampleTable
PIVOT (MAX([ID]) FOR [Year] IN ([2016],[2017],[9999])) [x]
ORDER BY ID_9999
You can do this in multiple ways. Conditional aggregation seems simple enough:
select max(case when year = 2016 then id end) as id_2016,
max(case when year = 2017 then id end) as id_2017,
max(case when year = 9999 then id end) as id_9999
from (select t.*, row_number() over (partition by year order by id) as seqnum
from dbo.ExampleTable t
) t
group by seqnum
order by seqnum;

Some Case statement issue

I have two tables that has data like
table1
Id id_nm
1 per
2 per
3 org
table2
Id Lst_id l_nm up_dt
1 22 abc 9/10/2015
1 21 abs 10/12/2016
2 21 xzc 10/12/2013
2 23 xyz 10/21/2013
2 23 xnh 01/12/2013
Need to pick the l_nm where lst_id is 22. If that is not present then we need to pick the l_nm with the most recent updated date.
Id lst_id lnm up_dt
1 22 abc 9/10/2015
2 23 xyz 10/21/2013
can any one please help me in implementing it.
Simple way is to use row_number with a window clause to generate a custom sort order:
select id, lst_id, l_nm as lnm, up_dt
from (
select id
,lst_id
,l_nm
,up_dt
,row_number()
over (partition by id
order by case when lst_id = 22 then 1 else 2 end
,up_dt desc) as rn
from table2
) where rn = 1;

getting rid of redundant rows in sql db2

I have the following data format in sql db2:
ID Test_no Result
-- ------- ------
01 1 A
01 2 B
01 3 B
02 1 A
03 1 B
03 2 C
04 1 A
where person can take a maximum of 3 tests, although some only take a minimum of 1 test (the criteria is irrelevant). I have been asked to produce the table in, and I hate to use this phrase "wide format" i.e.
ID Test1 Test2 Test3
-- ----- ----- -----
01 A B B
02 A NULL NULL
03 B C NULL
04 A NULL NULL
where each person has one record and records the result if they took a certain test (Although I don't like working in this format!) I can do something like
select distinct ID,
case when Test_no = 1 then Result end as Test1,
case when Test_no = 2 then Result end as Test2,
case when Test_no = 3 then Result end as Test3
from my_table
however of course this generates a new line each time a non-null test score exists and I end up with:
ID Test1 Test2 Test3
-- ----- ----- -----
01 A NULL NULL
01 NULL B
01 NULL NULL C
.
.
.
How do I remove the rows that are generated as a result of a non-null test result appearing? i.e. like the previous table.
Thanks very much.
Try this way:
SELECT ID,
MAX(case when Test_no = 1 then Result end) as Test1,
MAX(case when Test_no = 2 then Result end) as Test2,
MAX(case when Test_no = 3 then Result end) as Test3
FROM my_table
GROUP BY ID

SQL Converting Column into Rows in Single Select Statement

I need solution for converting SQL output
I am writing
SELECT Merchant_Master.Merchant_ID,
COUNT(Coupon_Type_ID) AS "Total Coupons",
Coupon_Type_ID,
CASE WHEN Coupon_Type_ID=1
THEN COUNT(Coupon_Type_ID)
END AS "Secret",
CASE WHEN Coupon_Type_ID=2
THEN count(Coupon_Type_ID)
END AS "Hot"
FROM Coupon_Master
INNER JOIN Merchant_Master
ON Coupon_Master.Merchant_ID=Merchant_Master.Merchant_ID
GROUP BY
Coupon_Master.Coupon_Type_ID,
Merchant_Master.Merchant_ID
and getting output as
Merchant_ID Total Coupons Coupon_Type_ID Secret Hot
----------- ------------- -------------- ----------- -----------
20 6 1 6 NULL
22 4 1 4 NULL
22 2 2 NULL 2
23 1 2 NULL 1
24 2 1 2 NULL
25 3 1 3 NULL
25 2 2 NULL 2
But I want output as
Merchant_ID Secret Hot_Coupons
----------- ------ -------------
20 6 0
22 4 2
23 0 1
24 2 0
25 3 2
Please, help me to solve the issue.
Move the CASE expressions inside the aggregates. I've also switched to using SUM rather than COUNT - there is a COUNT variant but it may display a warning about eliminating NULL values that I'd rather avoid.
SELECT Merchant_Master.Merchant_ID,
SUM(CASE WHEN Coupon_Type_ID=1
THEN 1 ELSE 0 END) AS "Secret",
SUM(CASE WHEN Coupon_Type_ID=2
THEN 1 ELSE 0 END) AS "Hot"
FROM Coupon_Master
INNER JOIN Merchant_Master
ON Coupon_Master.Merchant_ID=Merchant_Master.Merchant_ID
GROUP BY
Merchant_Master.Merchant_ID
Place it in a subquery and add group by Merchant_ID, Total, Coupons, Coupon_Type_ID
Aggregate the Secret and hot as SUM
select
...
SUM(secret) as secret,
SUM(Hot_Coupons) as Hot_Coupons
FROM (your original query) raw
group by Merchant_ID, Total, Coupons, Coupon_Type_ID

Get 1 row from multiple columns

I have 2 tables
table#1: Order
orderid unitid active
1 aa 1
2 bb 0
3 cc 1
4 dd 1
table#2:Details
orderid month
1 6
1 7
1 12
2 1
2 6
3 1
3 2
3 3
3 4
3 6
Output desired:
orderid unitid jan feb mar apr may jun ......... dec
1 aa yes yes
3 cc yes yes yes yes
For all orders where ACTIVE is 1 and all unitids.
I tried using case statement, i get multiple rows for a single orderid, which is not how i want.
I see a lot of examples for pivot with one table, how to do this using 2 tables? I am using SQL Server 2012.
Maybe a Select within a SELECT as argument
Something like this;
Select orderid, unitid, (SELECT month
From Table2
WHERE ...)
From table1
Where ...
I am referencing with this answer this Issue:
A select query selecting a select statement