SQL Group by one column, count entries in another - sql

I'm using a sqlite3 database, with a table like this.
|name |action |
-------------------------
|john |run |
|jim |run |
|john |run |
|john |jump |
|jim |jump |
|jim |jump |
|jim |dive |
I want to get an output like this
|name |run |jump |dive |
---------------------------------
|john |2 |1 |0 |
|jim |1 |2 |1 |
The closest I've come is with this, but I would like to have a single row like above.
SELECT name, action, COUNT(name)
FROM table
GROUP BY name, action
|name |action |COUNT(name) |
|john |run |2 |
|john |jump |1 |
|jim |run |1 |
|jim |jump |2 |
|jim |dive |1 |
Also, I will need to have some WHERE statements in the query as well.
Am I up in the night thinking this will work?

You can also accomplish what you want by using a sum aggregate and CASE conditions like this:
SELECT name,
sum(CASE WHEN action = 'run' THEN 1 END) as run,
sum(CASE WHEN action = 'jump' THEN 1 END) as jump,
sum(CASE WHEN action = 'dive' THEN 1 END) as dive
FROM table
GROUP BY name
You will still have to change the query every time additional actions are added.

What you are trying to do is called cross tabulation. Normally this is available as a feature called pivot table in Excel and other spreadsheet softwares.
I have found a blog article which will help you with this using SQL. Check out pivot-table-hack-in-sqlite3-and-mysql

I don't know SQLLite that well, but I image that you could use subqueries or temp tables.
With mssql you could write something like this:
select Name,
(select count(*) from table as t1 where t1.Name = table.Name and t1.Action = 'run') as Run,
(select count(*) from table as t1 where t1.Name = table.Name and t1.Action = 'dive') as dive,
(select count(*) from table as t1 where t1.Name = table.Name and t1.Action = 'jump') as run
from table
But this would need to be rewritten every time you ad another action type. You should probably add an index to get the speed up on the table. But check the query plan with "real" data first.

in oracle database you can write like below query to show required solution :-
select * from table_name
pivot (count(*) for action in ('run','jump','drive'))
this will give the desired output..

Related

Update a column value within a SELECT query

I have a complicated SQL question.
Can we update a column within a SELECT query? Example:
Consider this table:
|ID |SeenAt |
----------------
|1 |20 |
|1 |21 |
|1 |22 |
|2 |70 |
|2 |80 |
I want a SELECT Query that gives for each ID when was it seen for the first time. And when did it seen 'again':
|ID |Start |End |
---------------------
|1 |20 |21 |
|1 |20 |22 |
|1 |20 |22 |
|2 |70 |80 |
|2 |70 |80 |
First, both columns Start and End would have the same value, but when a second row with the same ID is seen we need to update its predecessor to give End the new SeenAt value.
I succeeded to create the Start column, I give the minimum SeenAt value per ID to all IDs. But I can't find a way to update the End column everytime.
Don't mind the doubles, I have other columns that change in every new row
Also, I am working in Impala but I can use Oracle.
I hope that I have been clear enough. Thank you
You could use lead() and nvl():
select id, min(seenat) over (partition by id) seen_start,
nvl(lead(seenat) over (partition by id order by seenat), seenat) seen_end
from t
demo
Start is easy just the MIN of the GROUP
End you need to find the MIN after the SeenAt and in case you don't find it then the current SeenAt
SQL DEMO
SELECT "ID",
(SELECT MIN("SeenAt")
FROM Table1 t2
WHERE t1."ID" = t2."ID") as "Start",
COALESCE(
(SELECT MIN("SeenAt")
FROM Table1 t2
WHERE t1."ID" = t2."ID"
AND t1."SeenAt" < t2."SeenAt")
, t1."SeenAt"
) as End
FROM Table1 t1
OUTPUT
| ID | START | END |
|----|-------|-----|
| 1 | 20 | 21 |
| 1 | 20 | 22 |
| 1 | 20 | 22 |
| 2 | 70 | 80 |
| 2 | 70 | 80 |
you seem to need min() analytic function with a self-join:
select distinct t1.ID,
min(t1.SeenAt) over (partition by t1.ID order by t1.ID) as "Start",
t2.SeenAt as "End"
from tab t1
join tab t2 on t1.ID=t2.ID and t1.SeenAt<=t2.SeenAt
order by t2.SeenAt;
Demo

Select all data with same ID in a table

I just want to know the correct syntax because what I've done is not what I wanted. So here is the sample table on database,
|ID |BOOKS |COURSE
+------+--------+------+
|1 |web book| A |
|1 |java | A |
|2 |php | B |
|2 |html | B |
In my UI, I have this table. I have a button in the column of the table that will trigger a modal to show the borrowed books of the student in that row.
|ID |COURSE|ACTION|
+------+------+------+
|1 |A |SHOW |
|2 |B |SHOW |
Whenever I want to show the borrowed book of ID = 1, the output on modals should be like
|BOOKS |
+--------+
|web book|
|java |
because that's what ID=1 borrowed. Thanks in advance.
My current query:
SELECT book
FROM borrowing_tbl
where ID in (SELECT ID
FROM borrowing_tbl
GROUP BY ID
HAVING COUNT(*) > 1)
it keeps showing all data with duplicated ID
Select all books with ID 1
Select ID
From BOOKS
Where BOOKS.ID = 1
I'm sure you could lay your tables out better
Edit:
SELECT BOOKS.name
FROM BOOKS
JOIN students on students.id = BOOKS.id
WHERE students.courses = 'A';

2 column with same ID to 1 row

I have a table with only 2 column which is as follow
|ID | Date |
===================
|1 | 03/04/2017 |
|1 | 09/07/1997 |
|2 | 04/04/2014 |
I want to achieve an end result as follow
|ID | Date 1 |Date 2 |
================================
|1 | 03/04/2017 | 09/07/1997 |
|2 | 04/04/2014 | NULL |
I'm currently reading up on PIVOT function and I'm not sure am I on the right track. Am still new to SQL
A simple pivot query should work here, with a twist. For your ID 2 data, there is only one row, but in this case you want to report a first date and a NULL second date. We can use a CASE expression to handle this case.
SELECT
ID,
MAX(Date) AS date_1,
CASE WHEN COUNT(*) = 2 THEN MIN(Date) ELSE NULL END AS date_2
FROM yourTable
GROUP BY ID
Output:
Demo here:
Rextester
This can be done easily using min/max aggregate function
select Id,min(Date),
case when min(Date)<>max(Date) then max(Date) end
From yourtable
Group by Id
If this will not help you with your original data, then alter sample data and expected result

How to select rows where a combination of 2 columns is the same

The title seems to do a really poor job at describing my problem; let me try and explain it with a simplified example:
lets say I have the following table:
_______________________
|id|variant_id|attr_id|
|__|__________|_______|
|1 |15 |110 |
|2 |15 |110 |
|3 |20 |152 |
|4 |20 |110 |
|5 |21 |110 |
|__|__________|_______|
Now, what I'd like to have is a query that selects all rows where a combination of the variant_id and attr_id columns occurs more than once. Basically in this example it should select row 1 and 2, because their combination of variant_id and attr_id occurs more than once in the table.
Is that possible? My head hurts from trying to think of a possible solution.
SELECT variant_id, attr_id
FROM YouTable
GROUP BY variant_id, attr_id
HAVING COUNT(*) > 1
Try this query
SELECT a.* FROM
tbl a
inner join
tbl b
ON a.variant_id =b.variant_id AND a.attr_id = b.attr_id
WHERE a.id <> b.id;
Hope this helps

complex'ish SQL joins across multiple tables with multiple conditions across all tables

Given the following tables:
labels tags_labels
|id |name | |url |labelid |
|-----|-------| |/a/b |1 |
|1 |punk | |/a/c |2 |
|2 |ska | |/a/b |3 |
|3 |stuff | |/a/z |4 |
artists tags
|id |name | |url |artistid |albumid |
|----|--------| |------|-----------|---------|
|1 |Foobar | |/a/b |1 |2637 |
|2 |Barfoo | |/a/z |2 |23 |
|3 |Spongebob| |/a/c |1 |32 |
I would like to get a list of urls that match a couple of conditions (which can be entered by the user into the script that uses these statements).
For example, the user might want to list all urls that have the labels "(1 OR 2) AND 3", but only if they are by the artists "Spongebob OR Whatever".
Is it possible to do this within a single statement using inner/harry potter/cross/self JOINs?
Or would I have to spread the query across multiple statements and buffer the results inside my script?
Edit:
And if it is possible, what would the statement look like? :p
Yes, you can do this in one query. And maybe an efficient way would be to dynamically generate the SQL statement, based on the conditions the user entered.
This query would allow you to filter by label name or artist name.
Building the sql dynamically to concatenate the user parameters or
passing the desired parameters into a stored procedure would obviously change
the where clauses but that really depends on how dynamic your 'script' must be...
SELECT tl.url
FROM labels l INNER JOIN tags_labels tl ON l.id = tl.labelid
WHERE l.name IN ('ska','stuff')
UNION (
SELECT t.url
FROM artists a INNER JOIN tags t ON a.id = t.artistid
WHERE a.name LIKE '%foo%'
)
Good Luck!