How to show different dates data (from the same table) as columns in Oracle - sql

I'm sorry if the title wasn't too clear, but the following explanation will be more accurate.
I have the following view:
DATE USER CONDITION
20140101 1 A
20140101 2 B
20140101 3 C
20140108 1 C
20140108 3 B
20140108 2 C
What I need to do is present how many users where in all conditions this week and 7 days before today.
Output should be like this:
Condition Today Last_Week (Today-7)
A 0 1
B 1 1
C 2 1
How can I do this in Oracle? I will need to do this for 4 weeks so itll be Today-7,14-21.
I've tried this with group by but I get the "week2" as rows. Then I've tried something like Select conditions, (select count(users) from MyView where DATE='Today') FROM MyView(looking at something thats actually working) but it doesnt work for me.
Achieved this with a little modification of the accepted answer:
select condition,
count(case when to_date(xdate) = to_date(sysdate) then 1 end) to_day,
count(case when to_date(xdate) = to_date(sysdate-7) then 1 end) last_7_days
from my_table
group by condition

select condition, count(case when to_date(xdate) = to_date(sysdate) then 1 end) to_day,
count(case when to_date(xdate) < to_date(sysdate) then 1 end) last_7_days
from my_table
where to_date(xdate) >= to_date(sysdate) - 7
group by condition

select condition
, sum
( case
when date between trunc(sysdate) - 7 and trunc(sysdate) - 1
then 1
else 0
end
)
last_week
, sum
( case
when date between trunc(sysdate) and trunc(sysdate + 1)
then 1
else 0
end
)
this_week
from table
group
by condition
By using the conditional count (as a sum) and grouping on condition you can filter out all desired dates. Note that using trunc will cause to use the begin of the day.

Related

Oracle SQL Show all month of a year, with or without value ORA-01841

I have a problem with which I despair, I have data distributed over days, and would like to display this for the entire year in months and once in weeks.
My problem with the months that I get in the select my data displayed (for January, September) but I want that all months for a selected year are displayed, even if they are empty. For this I have made myself a "WITH" (copied) and now try to join this, but get an ORA-01841 error.
And how do I implement the whole construct to display only the weeks.
WITH MONAT_ZAEHLER (MZ) AS
(
SELECT
TO_CHAR(ADD_MONTHS(TO_DATE('01.2022','MM.YYYY'),LEVEL -1),'Month', 'NLS_DATE_LANGUAGE = GERMAN') AS GRD_ROW_ID
FROM
DUAL
CONNECT BY LEVEL <= 12
)
SELECT
TO_CHAR(GEN_DATUM,'Month', 'NLS_DATE_LANGUAGE = GERMAN') AS GRD_ROW_ID
, COUNT( DISTINCT CASE
WHEN LP_BELEGUNG.ART = 1 THEN LP_BELEGUNG.LP_BELEGUNG_ID
ELSE NULL
END ) AS "1"
, COUNT( DISTINCT CASE
WHEN LP_BELEGUNG.ART = 2 THEN LP_BELEGUNG.LP_BELEGUNG_ID
ELSE NULL
END ) AS "2"
, COUNT( DISTINCT CASE
WHEN LP_BELEGUNG.ART = 3 THEN LP_BELEGUNG.LP_BELEGUNG_ID
ELSE NULL
END ) AS "3"
, COUNT( DISTINCT CASE
WHEN LP_BELEGUNG.ART = 99 THEN LP_BELEGUNG.LP_BELEGUNG_ID
ELSE NULL
END ) AS "99"
FROM
LP_BELEGUNG
FULL OUTER JOIN MONAT_ZAEHLER ON TRUNC(LP_BELEGUNG.GEN_DATUM, 'Month') = MONAT_ZAEHLER.MZ
WHERE
TO_CHAR(GEN_DATUM, 'YYYY') = '2022'
GROUP BY
TO_CHAR(GEN_DATUM,'Month', 'NLS_DATE_LANGUAGE = GERMAN')
The error is because you're converting the month to a name string in the CTE, then trying to convert it again for the GRD_ROW_ID alias.
The solution is basically the same as your previous question, but now you want the CTE to have one row per month - which you are doing, but you should leave it as a date type in the CTE, not convert it to a string there:
with cte (dt) as (
select add_months(date '2022-01-01', level - 1)
from dual
connect by level <= 12
)
... then convert that actual date value to a string:
SELECT
TO_CHAR(cte.dt, 'Month', 'NLS_DATE_LANGUAGE = GERMAN') AS GRD_ROW_ID
...
... and outer join to your actual table as before, using a date range:
FROM
cte
LEFT JOIN
LP_BELEGUNG
ON
LP_BELEGUNG.GEN_DATUM >= cte.dt AND LP_BELEGUNG.GEN_DATUM < add_months(cte.dt, 1)
GROUP BY
cte.dt
ORDER BY
cte.dt
... this time looking for values where the the GEN_DATUM is greater than or equal to cte.dt value (again, as before), which is midnight on the first day of the first day of the month; and less than add_months(cte.dt, 1), which is midnight on the first day of the first day of the following month. So for January, that will be >= 2022-01-01 00:00:00 and < 2022-02-01 00:00:00, which is all possible dates and times during that month.
GRD_ROW_ID
ANZAHL_ART_1
ANZAHL_ART_2
ANZAHL_ART_3
ANZAHL_ART_4
Januar
0
0
0
0
Februar
0
0
0
0
März
0
0
0
0
April
0
0
0
0
Mai
0
0
0
0
Juni
0
0
0
0
Juli
0
0
0
0
August
0
0
0
0
September
1
1
1
7
Oktober
0
0
0
0
November
0
0
0
0
Dezember
0
0
0
0
fiddle
To get a row for every week of the year you would do something similar again, but in blocks of 7 days:
with cte (dt) as (
select date '2022-01-01' + 7 * (level - 1)
from dual
connect by level <= 53
)
SELECT
TO_CHAR(cte.dt, 'YYYY-WW') AS GRD_ROW_ID
...
FROM
cte
LEFT JOIN
LP_BELEGUNG
ON
LP_BELEGUNG.GEN_DATUM >= cte.dt AND LP_BELEGUNG.GEN_DATUM < cte.dt + 7
AND LP_BELEGUNG.GEN_DATUM < add_months(trunc(cte.dt, 'YYYY'), 12)
GROUP BY
cte.dt
ORDER BY
cte.dt
which has an extra check in the join to stop it including data from week 53 which is actually in the following year - which I'm guessing you woudl want to do.
fiddle

Calculating Percentages in Postgres

I'm completely new to PostgreSQL. I have the following table called my_table:
a b c date
1 0 good 2019-05-02
0 1 good 2019-05-02
1 1 bad 2019-05-02
1 1 good 2019-05-02
1 0 bad 2019-05-01
0 1 good 2019-05-01
1 1 bad 2019-05-01
0 0 bad 2019-05-01
I want to calculate the percentage of 'good' from column c for each date. I know how to get the number of 'good':
SELECT COUNT(c), date FROM my_table WHERE c != 'bad' GROUP BY date;
That returns:
count date
3 2019-05-02
1 2019-05-01
My goal is to get this:
date perc_good
2019-05-02 25
2019-05-01 75
So I tried the following:
SELECT date,
(SELECT COUNT(c)
FROM my_table
WHERE c != 'bad'
GROUP BY date) / COUNT(c) * 100 as perc_good
FROM my_table
GROUP BY date;
And I get an error saying
more than one row returned by a subquery used as an expression.
I found this answer but not sure how to or if it applies to my case:
Calculating percentage in PostgreSql
How do I go about calculating the percentage for multiple rows?
avg() is convenient for this purpose:
select date,
avg( (c = 'good')::int ) * 100 as percent_good
from t
group by date
order by date;
How does this work? c = 'good' is a boolean expression. The ::int converts it to a number, with 1 for true and 0 for false. The average is then the average of a bunch of 1s and 0s -- and is the ratio of the true values.
For this case you need to use conditional AVG():
SELECT
date,
100 * avg(case when c = 'good' then 1 else 0 end) perc_good
FROM my_table
GROUP BY date;
See the demo.
You could use a conditional sum for get the good value and count for total
below an exaustive code sample
select date
, count(c) total
, sum(case when c='good' then 1 else 0 end) total_good
, sum(case when c='bad' then 1 else 0 end) total_bad
, (sum(case when c='good' then 1 else 0 end) / count(c))* 100 perc_good
, (sum(case when c='bad' then 1 else 0 end) / count(c))* 100 perc_bad
from my_table
group by date
and for your result
select date
, (sum(case when c='good' then 1 else 0 end) / count(c))* 100 perc_good
from my_table
group by date
or as suggested by a_horse_with_no_name using count(*) filter()
select date
, ((count(*) filter(where c='good'))/count(*))* 100 perc_good
from my_table
group by date

How deal with this issue in SQL with Groupby

I have this data called pdays:
id|time|date_time| type_id
1 2 2016-03-05 1
2 5 2016-03-05 1
3 3 2016-03-06 2
4 7 2016-03-07 3
5 2 2016-03-10 1
6 1 2016-03-12 3
I would like to calculate the average number of time SUM(time) for weekdays and weekends grouped by type_id
The output expect like this:
type_id| weekday_time|weekends_time
1 7 2
2 3 0
3 7 1
This is my thoughts:
First I need to extract date number from date_time; Second, identify the date number whether falls into (5,6,12,13,19,20,26,27) which are weekend numbers (note: This data presents a one month case, so I do not need to worry about the changes of weekend date numbers in next month); Finally, do the aggregation and grouping on type_id
CASE WHEN pday.date IN(5,6,12,13,19,20,26,27) THEN 'weekend' ELSE 'weekday' END
This is the case part I think I should use.
First, your output appears to be wrong. Type_id 3 has both a weekend and a weekday entry, but you show one of the output values as 0.
This should get you what you want in SQL Server and it is very close to other RDBMS's. If you update your RBDMS, I'll change:
;with cte AS (
select type_id,
CASE WHEN pday.date IN(5,6,12,13,19,20,26,27) THEN 'weekday' ELSE 'weekend' END AS day_type,
SUM(time) AS time_sum
FROM pdays
GROUP BY
type_id,
CASE WHEN pday.date IN(5,6,12,13,19,20,26,27) THEN 'weekday' ELSE 'weekend' END
)
SELECT type_id,
SUM(CASE WHEN day_type = 'weekday' THEN time_sum ELSE 0 END) AS 'weekday_time',
SUM(CASE WHEN day_type = 'weekend' THEN time_sum ELSE 0 END) AS 'weekend_time'
FROM cte
GROUP BY [type_id]

Select multiple COUNTs for every day

I got a table of Visitors.
Visitor has the following columns:
Id
StartTime (Date)
Purchased (bool)
Shipped (bool)
For each day within the last 7 days, I want to select 3 counts of the Visitors who have that day as StartTime:
The count of total visitors
The count of total visitors where Purchased = true
The count of total visitors where Shipped = true
Ideally the returned result would be:
Day Total TotalPurchased TotalShipped
1 100 67 42
2 82 61 27
etc...
I am used to .NET Linq so this has proved to be quite a challenge for me.
All I have come up with so far is the following:
SELECT COUNT(*) AS Total
FROM [dbo].[Visitors]
WHERE DAY([StartTime]) = DAY(GETDATE())
It selects the total of the current day just fine, however I feel pretty stuck right now so it'd be nice if someone could point me in the right direction.
For the last 7 days use the query proposed by Stanislav but with this WHERE clause
SELECT DAY([StartTime]) theDay,
COUNT(*) AS Tot,
SUM(CASE WHEN Purchased=true THEN 1 ELSE 0 END) as TotPurch,
SUM(CASE WHEN Shipped=true THEN 1 ELSE 0 END) as TotShip
FROM [dbo].[Visitors]
WHERE [StartTime] BETWEEN GETDATE()-7 AND GETDATE()
GROUP BY DAY([StartTime])
SELECT COUNT(*) AS Total,
SUM(CASE WHEN Purchased=true THEN 1 ELSE 0 END) as TotalPurchased,
SUM(CASE WHEN Shipped=true THEN 1 ELSE 0 END) as TotalShipped
FROM [dbo].[Visitors]
WHERE DAY([StartTime]) = DAY(GETDATE())
and add GROUP BY DAY([StartTime]) as jarlh mentioned
Here's a simple select that will give you the dataset you want
SELECT DATEDIFF(day,StartTime, getdate())+1 as [Day], -- Add 1 to display 1 to 7 instead of 0 to 6
COUNT(*) as Total,
SUM(CASE WHEN Purchased = 1 THEN 1 ELSE 0 END) as TotalPurchased,
SUM(CASE WHEN Shipped = 1 THEN 1 ELSE 0 END) AS TotalShipped
FROM Visitors
WHERE DATEDIFF(day,startTime,GETDATE()) < 6
GROUP BY DATEDIFF(day,startTime,GETDATE())
ORDER BY 1
This query will not take into consideration the time component of the date.

Split SQL column values and group by date and return single row

I have SQL Server query , using this , I am splitting event id sum columns to two columns based on some condition. Query executed successfully, but the result is not desired. It's half useful. Please help me to get expected result. I want one row for both split columns instead two rows and empty spaces.
SQL Query:
select convert(date, paymenttime)) , SUM(case when eventid = 33 then 1 ELSE 0 END) AS column1,
SUM(case when eventid = 36 then 1 ELSE 0 END) AS column2
from tbltransMain_backup where
paymentime <= '20160731' and PaymentTime >= '20160701'
group by convert(date,paymenttime),event_id
order by convert(date,paymenttime)
Result view:
Expected Result:
2016-07-01 27 1
2016-07-02 28 2
2016-07-03 30 15
The query you posted (perhaps unknowingly) into your question should already give you the desired results:
SELECT CONVERT(DATE, paymenttime),
SUM(CASE WHEN event_id = 33 THEN 1 ELSE 0 END) AS column1,
SUM(CASE WHEN event_id = 36 THEN 1 ELSE 0 END) AS column2
FROM tbltransMain_backup
WHERE paymentime <= '20160731' AND
paymentime >= '20160701'
GROUP BY CONVERT(DATE, paymenttime)
ORDER BY CONVERT(DATE, paymenttime)
The reason you were getting two rows for every date is that your query had the following grouping:
GROUP BY CONVERT(DATE, paymenttime),
event_id
In other words, each date would have two groups, one for event_id = 33 and one for event_id = 36.