Complicated SELECT statement in Oracle DB - sql

Can you please help me with one complicated select statement?
I have a table like this:
+----+-----------+-----------+-----------------+
| ID | User_name | Situation | Date_time |
+----+-----------+-----------+-----------------+
| 1 | Alex | 1 | 14.3.18 11:30 |
| 4 | Alex | 2 | 14.3.18 11:35 |
| 6 | Alex | 3 | 14.3.18 12:30 |
| 7 | Johnny | 1 | 15.3.18 10:01 |
| 9 | Johnny | 2 | 15.3.18 10:05 |
| 12 | Johnny | 3 | 15.3.18 10:20 |
| 14 | Alex | 1 | 20.3.18 20:00 |
| 15 | Alex | 2 | 20.3.18 20:25 |
| 17 | Alex | 3 | 20.3.18 21:25 |
+----+-----------+-----------+-----------------+
And I need a select statement, which will give me the following result:
User_name, Date_time_1 (Date_time of situation 1), Date_time_3 (Date_time of situation 3).
*In this case the result will have just 3 rows (2 for Alex and 1 for Johnny). Each row will contain 3 columns as described above.
And sorry for the formatting - I posted that from a mobile. I will add the result table when I will get to PC.*
That's how the output should looks like:
+----+-----------+-------------+-----------------+
| ID | User_name |Date_time_1 | Date_time_3 |
+----+-----------+-------------+-----------------+
| 1 | Alex |14.3.18 11:30| 14.3.18 12:30 |
| 2 | Johnny |15.3.18 10:01| 15.3.18 10:20 |
| 3 | Alex |20.3.18 20:00| 20.3.18 21:25 |
+----+-----------+-------------+-----------------+

You could use conditional aggregation:
SELECT User_name,
MAX(CASE WHEN Situation = 1 THEN Date_time END) AS date_time_1,
MAX(CASE WHEN Situation = 3 THEN Date_time END) AS date_time_3
FROM tab
GROUP BY User_name;
EDIT
In this case the result will have just 3 rows (2 for Alex and 1 for Johnny)
WITH cte AS (
SELECT t.*, SUM(CASE WHEN Situation=1 THEN 1 ELSE 0 END)
OVER(PARTITION BY User_name ORDER BY id) AS s
FROM tab t
)
SELECT User_name,
MAX(CASE WHEN Situation = 1 THEN Date_time END) AS date_time_1,
MAX(CASE WHEN Situation = 3 THEN Date_time END) AS date_time_3
FROM cte
GROUP BY s, User_name;
DBFiddle Demo

Related

Increment columns value based on duplicity record

I have a question on whether if I can avoid using a cursor.
I have a table with thousands of record like this:
Date | Name | StateData |
-----------+-------+-----------+
22-10-2019 | Tom | OPENED |
22-10-2019 | David | NULL |
22-10-2019 | Tom | NULL |
22-10-2019 | Brand | CLOSED |
22-10-2019 | Tom | NULL |
23-10-2019 | Brand | NULL |
22-10-2019 | Brand | OPENED |
22-10-2019 | Tom | OPENED |
22-10-2019 | Brand | OPENED |
22-10-2019 | Tom | CLOSED |
22-10-2019 | Brand | CLOSED |
What I'd like to achieve is a result, where I can see each name on just 1 row with counting how many times they had which State "StateData".
Date | Name | OPENED | CLOSED | UNUSED |
-----------+------+--------+--------+--------+
22-10-2019 | Tom | 2 | 1 | 2 |
22-10-2019 | David| 0 | 0 | 1 |
22-10-2019 | Brand| 2 | 2 | 1 |
I've tried a select like this
SELECT DISTINCT d.Name, d.[opened], d.[closed], d.[unused], StateData
FROM [dbo].[StateData] d
INNER JOIN (
SELECT DISTINCT Name, [opened], [closed], [unused]
FROM [dbo].[StateData]
GROUP BY Name, [opened], [closed], [unused]
) dp
ON dp.Name = d.Name
;
I know it can be done with creating stored procedure using CURSOR, but I'm not that experienced with cursor.
Use conditional aggregation:
select
Date,
Name,
SUM(CASE WHEN StateDAte = 'Opened' THEN 1 ELSE 0 END) Opened,
SUM(CASE WHEN StateDAte = 'Closed' THEN 1 ELSE 0 END) Closed,
SUM(CASE WHEN StateDAte IS NULL THEN 1 ELSE 0 END) Unused
from mytable
group by Date, Name
You can use sql pivot as follows
;with cte as (select date,name,isnull(statedate,'unused') statedate from mytable)
select p.* from cte
pivot
(count(statedate) for statedate in ([Opened],[Closed], [unused] )) as p
GMB and Kemal,
thanks,
I have tried this
SELECT [Date],
[Name],
COUNT(CASE StateData WHEN 'OPENED' THEN 1 END) AS OPENED,
COUNT(CASE StateData WHEN 'CLOSED' THEN 1 END) AS CLOSED,
COUNT(CASE WHEN StateData IS NULL THEN 1 END) AS UNUSED
FROM YourTable
GROUP BY [Date],
[Name];
but the result is not correct
Name | OPENED | CLOSED | UNUSED |
Tom | 2 | 0 | 57412 |
Brad | 2 | 0 | 57412 |
David | 2 | 0 | 57412 |
the correct result should be this
Date | Name | OPENED | CLOSED | UNUSED |
-----------+------+--------+--------+--------+
22-10-2019 | Tom | 2 | 1 | 2 |
22-10-2019 | David| 0 | 0 | 1 |
22-10-2019 | Brand| 2 | 2 | 1 |
but it seems like its just copy pasting the results.

PostgreSQL multiple row as columns

I have a table like this:
| id | name | segment | date_created | question | answer |
|----|------|---------|--------------|----------|--------|
| 1 | John | 1 | 2018-01-01 | 10 | 28 |
| 1 | John | 1 | 2018-01-01 | 14 | 37 |
| 1 | John | 1 | 2018-01-01 | 9 | 83 |
| 2 | Jack | 3 | 2018-03-11 | 22 | 13 |
| 2 | Jack | 3 | 2018-03-11 | 23 | 16 |
And I want to show this information in a single row, transpose all the questions and answers as columns:
| id | name | segment | date_created | question_01 | answer_01 | question_02 | answer_02 | question_03 | answer_03 |
|----|------|---------|--------------|-------------|-----------|-------------|-----------|-------------|-----------|
| 1 | John | 1 | 2018-01-01 | 10 | 28 | 14 | 37 | 9 | 83 |
| 2 | Jack | 3 | 2018-03-11 | 22 | 13 | 23 | 16 | | |
The number os questions/answers for the same ID is known. Maximum of 15.
I'm already tried using crosstab, but it only accepts a single value as category and I can have 2 (question/answer). Any help how to solve this?
You can try to use row_number to make a number in subquery then, do Aggregate function condition in the main query.
SELECT ID,
Name,
segment,
date_created,
max(CASE WHEN rn = 1 THEN question END) question_01 ,
max(CASE WHEN rn = 1 THEN answer END) answer_01 ,
max(CASE WHEN rn = 2 THEN question END) question_02,
max(CASE WHEN rn = 2 THEN answer END) answer_02,
max(CASE WHEN rn = 3 THEN question END) question_03,
max(CASE WHEN rn = 3 THEN answer END) answer_03
FROM (
select *,Row_number() over(partition by ID,Name,segment,date_created order by (select 1)) rn
from T
) t1
GROUP BY ID,Name,segment,date_created
sqlfiddle
[Results]:
| id | name | segment | date_created | question_01 | answer_01 | question_02 | answer_02 | question_03 | answer_03 |
|----|------|---------|--------------|-------------|-----------|-------------|-----------|-------------|-----------|
| 1 | John | 1 | 2018-01-01 | 1 | 28 | 14 | 37 | 9 | 83 |
| 2 | Jack | 3 | 2018-03-11 | 22 | 13 | 23 | 16 | (null) | (null) |

Show only one record, if value same in another column SQL

I have a table with 5 columns like this:
| ID | NAME | PO_NUMBER | DATE | STATS |
| 1 | Jhon | 160101-001 | 2016-01-01 | 7 |
| 2 | Jhon | 160101-002 | 2016-01-01 | 7 |
| 3 | Jhon | 160102-001 | 2016-01-02 | 7 |
| 4 | Jane | 160101-001 | 2016-01-01 | 7 |
| 5 | Jane | 160102-001 | 2016-01-02 | 7 |
| 6 | Jane | 160102-002 | 2016-01-02 | 7 |
| 7 | Jane | 160102-003 | 2016-01-02 | 7 |
I need to display all values, but stats fields without duplicate according from date field.
Like this
| ID | NAME | PO_NUMBER | DATE | STATS |
| 1 | Jhon | 160101-001 | 2016-01-01 | 7 |
| 2 | Jhon | 160101-002 | 2016-01-01 | null |
| 3 | Jhon | 160102-001 | 2016-01-02 | 7 |
| 4 | Jane | 160101-001 | 2016-01-01 | 7 |
| 5 | Jane | 160102-001 | 2016-01-02 | 7 |
| 6 | Jane | 160102-002 | 2016-01-02 | null |
| 7 | Jane | 160102-003 | 2016-01-02 | null |
I've had trouble getting the hoped. Thanks
From your sample data, it appears you only want to show the stats for po_number ending with 001. If so, this should be the easiest approach:
select id, name, po_number, date,
case when right(po_number, 3) = '001' then stats else null end as stats
from yourtable
If instead you want to order by the po_number, then here's one option using row_number:
select id, name, po_number, date,
case when rn = 1 then stats else null end as stats
from (
select *, row_number() over (partition by name, date order by po_number) as rn
from yourtable
) t
SQL Fiddle Demo
since you are using SQL 2012, you can use the LEAD() or LAG() window function to compare the DATE value
select *,
STATS = case when t.DATE = LAG(DATE) OVER(ORDER BY ID)
then NULL
else STATS
end
from yourtable t
Use below code
;with temp as (
select id,name ,PO_NUMBER ,DATE, STATS,
LAG (STATS, 1, 0)
OVER (PARTITION BY name ,PO_NUMBER ,DATE ORDER BY id) AS PrevSTATS
from tableName
)
select id,name ,PO_NUMBER ,DATE,
case when STATS = PrevSTATS then null
else STATS end as STATS
from temp

Calculation going wrong due to JOIN issue

Table -
+----+-----------+-----------+---------+---------------------+------------+
| ID | Client_Id | Driver_Id | City_Id | Status | Request_at |
+----+-----------+-----------+---------+---------------------+------------+
| 1 | 1 | 10 | 1 | completed | 2013-10-01 |
| 2 | 2 | 11 | 1 | cancelled_by_driver | 2013-10-01 |
| 3 | 3 | 12 | 6 | completed | 2013-10-01 |
| 4 | 4 | 13 | 6 | cancelled_by_client | 2013-10-01 |
| 5 | 1 | 10 | 1 | completed | 2013-10-02 |
| 6 | 2 | 11 | 6 | completed | 2013-10-02 |
| 7 | 3 | 12 | 6 | completed | 2013-10-02 |
| 8 | 2 | 12 | 12 | completed | 2013-10-03 |
| 9 | 3 | 10 | 12 | completed | 2013-10-03 |
| 10 | 4 | 13 | 12 | cancelled_by_driver | 2013-10-03 |
+----+-----------+-----------+---------+---------------------+------------+
My attempt -
WITH src
AS (SELECT Count(status) AS Denom,
request_at
FROM trips
WHERE status = 'completed'
GROUP BY request_at),
src2
AS (SELECT Count(status) AS Num,
request_at
FROM trips
WHERE status <> 'completed'
GROUP BY request_at)
SELECT Cast(Count(num) AS FLOAT)/Cast(Count(Denom) AS FLOAT) AS cancel_rate,
trips.request_at
FROM src,
src2,
trips
GROUP BY trips.request_at;
I am trying to find the cancellation rate per day but it is clearing wrong (MY OUTPUT)-
+-------------+------------+
| cancel_rate | request_at |
+-------------+------------+
| 24 | 2013-10-01 |
| 18 | 2013-10-02 |
| 18 | 2013-10-03 |
+-------------+------------+
The cancellation rate for 2013-10-01 should be 0.5 and not 24. Similarly for other dates it should be different.
I know the problem lies with this part but I do not know what is the correct way or how to approach it
SELECT Cast(Count(num) AS FLOAT)/Cast(Count(Denom) AS FLOAT) AS cancel_rate,
trips.request_at
FROM src,
src2,
trips
Is there any way to put in more than 1 select statement in With NAME as () clause ? So that I won't use any JOIN or multiple tables.
Use conditional aggregation:
SELECT SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as denom,
SUM(CASE WHEN status <> 'completed' THEN 1 ELSE 0 END) as num,
AVG(CASE WHEN status <> 'completed' THEN 1.0 ELSE 0 END) as cancel_rate
FROM trips
GROUP BY request_at;
Note that calculation for the cancel_rate. This is simpler to do using AVG() rather than dividing the two values. The use of 1.0is because SQL Server does integer arithmetic, so 1 / 2 is 0 rather than 0.5.
OK, a bit late again, but here is another variation (edited):
SELECT SUM(CASE LEFT(status,9) WHEN 'cancelled' THEN 1. ELSE 0 END)
/COUNT(*) cancellation_rate,
request_at
FROM trips GROUP BY request_at ORDER BY request_at

SQL Server pivot with "ties"

Here is my source data:
+-------+-------+-------+------+
| Categ | Nm | Value | Rnk |
+-------+-------+-------+------+
| A | Tom | 37 | 1 |
| A | Joe | 36 | 2 |
| A | Eddie | 35 | 3 |
| B | Seth | 28 | 1 |
| B | Ed | 25 | 2 |
| B | Billy | 22 | 3 |
| C | Julie | 42 | 1 |
| C | Jenny | 41 | 2 |
| C | April | 40 | 3 |
| C | Mary | 40 | 3 |
| C | Laura | 40 | 3 |
+-------+-------+-------+------+
And here is the output I would like to produce:
+------+--------+--------+-------+
| Rnk | A | B | C |
+------+--------+--------+-------+
| 1 | Tom | Seth | Julie |
| 2 | Joe | Ed | Jenny |
| 3 | Eddie | Billy | April |
| 3 | (null) | (null) | Mary |
| 3 | (null) | (null) | Laura |
+------+--------+--------+-------+
I have used the following approach (which I understand through other posts may be superior to actually using PIVOT)...and this gets me to where I see Julie/Jenny/April, but not Mary/Laura (obviously, since it is pulling the MIN in the event of a 'tie').
SELECT Rnk
, min(CASE WHEN Categ = 'A' THEN Nm END) as A
, min(CASE WHEN Categ = 'B' THEN Nm END) as B
, min(CASE WHEN Categ = 'C' THEN Nm END) as C
FROM Tbl
GROUP BY Rnk
How to get to my desired output?
Well, if you want multiple rows for each rank, you can't aggregate by rank, or at least by rank alone. So, calculate the rank-within-the-rank or as the following query calls it, the sub_rnk:
SELECT Rnk,
min(CASE WHEN Categ = 'A' THEN Nm END) as A,
min(CASE WHEN Categ = 'B' THEN Nm END) as B,
min(CASE WHEN Categ = 'C' THEN Nm END) as C
FROM (select t.*, row_number() over (partition by categ, rnk order by newid()) as sub_rnk
from Tbl t
) t
GROUP BY rnk, sub_rnk
ORDER BY rnk;