SQL Count In Range - sql

How could I count data in range which could be configured
Something like this,
CAR_AVBL
+--------+-----------+
| CAR_ID | DATE_AVBL |
+--------------------|
| JJ01 | 1 |
| JJ02 | 1 |
| JJ03 | 3 |
| JJ04 | 10 |
| JJ05 | 13 |
| JJ06 | 4 |
| JJ07 | 10 |
| JJ08 | 1 |
| JJ09 | 23 |
| JJ10 | 11 |
| JJ11 | 20 |
| JJ12 | 3 |
| JJ13 | 19 |
| JJ14 | 22 |
| JJ15 | 7 |
+--------------------+
ZONE_CFG
+--------+------------+
| DATE | ZONE_DESCR |
+--------+------------+
| 15 | GREEN_ZONE |
| 25 | YELLOW_ZONE|
| 30 | RED_ZONE |
+--------+------------+
Table ZONE_CFG is configurable, so I could not use static value for this
The DATE column mean maximum date for each ZONE
And the result what I expected :
+------------+----------+
| ZONE_DESCR | AVBL_CAR |
+------------+----------+
| GREEN_ZONE | 11 |
| YELLOW_ZONE| 4 |
| RED_ZONE | 0 |
+------------+----------+
Please could someone help me with this

You can use LAG and group by as following:
SELECT
ZC.ZONE_DESCR,
COUNT(1) AS AVBL_CAR
FROM
CAR_AVBL CA
JOIN ( SELECT
ZONE_DECR,
COALESCE(LAG(DATE) OVER(ORDER BY DATE) + 1, 0) AS START_DATE,
DATE AS END_DATE
FROM ZONE_CFG ) ZC
ON ( CA.DATE_AVBL BETWEEN ZC.START_DATE AND ZC.END_DATE )
GROUP BY
ZC.ZONE_DESCR;
Note: Don't use oracle preserved keywords (DATE, in your case) as the name of the columns. Try to change it to something like DATE_ or DATE_START or etc..
Cheers!!

If you want the zero 0, I might suggest a correlated subquery instead:
select z.*,
(select count(*)
from car_avbl c
where c.date_avbl >= start_date and
c.date_avbl <= date
) as avbl_car
from (select z.*,
lag(date, 1, 0) as start_date
from zone_cfg z
) z;
In Oracle 12C, can phrase this using a lateral join:
select z.*,
(c.cnt - lag(c.cnt, 1, 0) over (order by z.date)) as cnt
from zone_cfg z left join lateral
(select count(*) as cnt
from avbl_car c
where c.date_avbl <= z.date
) c
on 1=1

Related

Combining multiple rows into a single row SQL

I have a table like this.
|InvID| Client | Group | PricedDate | TotalFee | RepricedFee | CompanyFee|
|1 | A | A.1 | 02-24-2020 | 100 | 80 | 8 |
|1 | A | A.1 | 01-05-2020 | 100 | 75 | 1 |
|2 | A | A.1 | 01-09-2020 | 100 | 60 | 1 |
|3 | B | B.1 | 01-11-2020 | 150 | 95 | 10 |
|4 | B | B.1 | 01-01-2020 | 100 | 55 | 11 |
|4 | B | B.1 | 02-01-2020 | 100 | 90 | 10 |
I need to display a single row based on the latest PricedDate and Sum of Company Fee
|InvID| Client | Group | PricedDate | TotalFee | RepricedFee | CompanyFee|
|1 | A | A.1 | 02-24-2020 | 100 | 80 | 9 |
|2 | A | A.1 | 01-09-2020 | 100 | 60 | 1 |
|3 | B | B.1 | 01-11-2020 | 150 | 95 | 10 |
|4 | B | B.1 | 02-01-2020 | 100 | 90 | 21 |
Is it the latest row per InvID you want? I would probably just get the maximum date and the sum in an aggregation query and then join that row:
select
t.invid,
t.client,
t.group,
t.priceddate,
t.totalfee,
t.repricedfee,
agg.sum_fee as companyfee
from
(
select invid, max(priceddate) as max_date, sum(companyfee) as sum_fee
from mytable
group by invid
) agg
join mytable t on t.invid = agg.invid and t.priceddate = agg.max_date
order by t.invid;
just do aggregation
select invId,client,[group],max(priceddate),max(Totalfee),min(repricedFee),sum(companyfee)
from table
group by invId,client,[group]
Try it like this:
select *
, (select sum(CompanyFee) from my_table mt3 group by InvID) CompanyFee
from my_table mt1
where mt1.PricedDate = (select max(mt2.PricedDate)
from my_table mt2
where mt2.InvID = mt1.InvID);
This part will make sure your data is from the row that has the largest PricedDate :
mt1.PricedDate = (select max(mt2.PricedDate)
from my_table mt2
where mt2.InvID = mt1.InvID)
Also, if it is not enough to group by InvID only you can add other columns there.
Here is a demo
Try this,
declare #CompanyFee= select sum(CompanyFee) from table1
select InvID,Client,Group,PricedDate,TotalFee,RepricedFee,#CompanyFee from table1
where priceddate=max(priceddate)
Try this.
select *
from my_table mt1
cross apply (
select CompanyFee=sum(CompanyFee) from my_table mt3 where mt3.invid=mt1.invid
) as CompanyFeeTbl
where mt1.PricedDate = (select max(mt2.PricedDate)
from my_table mt2
where mt2.InvID = mt1.InvID)
You can use window function :
select t.InvID, t.Client, t.Group, t.PricedDate,
t.TotalFee, t.RepricedFee, t.SumCompanyFee as CompanyFee
from(select t.*, sum(t.companyfee) over (partition by t.client, t.invId) as SumCompanyFee,
row_number() over (partition by t.client, t.invId order by t.PricedDate desc) as seq
from table t
) t
where seq = 1;

SQL get max value with date smaller date

I have a table like this:
| colA | date | num |
| x | 1.7. | 2 |
| x | 3.7. | 1 |
| x | 4.7. | 3 |
| z | 1.8. | 0 | (edit)
| z | 2.8. | 1 |
| z | 5.8. | 2 |
And I want a result like this:
| colA | date | maxNum |
| x | 1.7. | null |
| x | 3.7. | 2 |
| x | 4.7. | 2 |
| z | 1.8. | null | (edit)
| z | 2.8. | 0 |
| z | 5.8. | 1 |
So I want to have the max(num) for every row where the date is smaller the date grouped by colA.
Is this somehow possible with a simple query? It would be part of a bigger query needed for some calculations on big databases.
Edit: maxNum should be null if there is no value before a date in the group
Thanks in advance.
Use MAX..KEEP syntax.
select cola,
adate,
max(num) keep ( dense_rank first order by adate ) over (partition by cola ) maxnum,
case when adate = min(adate) over ( partition by cola )
then null
else max(num) keep ( dense_rank first order by adate ) over (partition by cola ) end maxnum_op
from input;
+------+-------+--------+-----------+
| COLA | ADATE | MAXNUM | MAXNUM_OP |
+------+-------+--------+-----------+
| x | 1.7 | 2 | |
| x | 3.7 | 2 | 2 |
| x | 4.7 | 2 | 2 |
| z | 2.8 | 1 | |
| z | 5.8 | 1 | 1 |
+------+-------+--------+-----------+
The MAXNUM_OP column shows the results you wanted, but you never explained why some of the values were supposed to be null. The MAXNUM column shows the results that I think you described in the text of your post.
You can use first_value and row_number analytical function as following:
Select cola,
date,
case when row_number() over (partition by cola order by date) > 1 then
first_value(num) over (partition by cola order by date)
end as maxnum
From your_table;
Cheers!!
One way is to use a subquery.
SELECT t1.cola,
t1.date,
(SELECT max(t2.num)
FROM elbat t2
WHERE t2.cola = t1.cola
AND t2.date < t1.date) maxnum
FROM elbat t1;

SQL - Rows that are repetitive with a particular condition

We have a table like this:
+----+-------+-----------------+----------------+-----------------+----------------+-----------------+
| ID | Name | RecievedService | FirstZoneTeeth | SecondZoneTeeth | ThirdZoneTeeth | FourthZoneTeeth |
+----+-------+-----------------+----------------+-----------------+----------------+-----------------+
| 1 | John | SomeService1 | 13 | | 4 | |
+----+-------+-----------------+----------------+-----------------+----------------+-----------------+
| 2 | John | SomeService1 | 34 | | | |
+----+-------+-----------------+----------------+-----------------+----------------+-----------------+
| 3 | Steve | SomeService3 | | | | 2 |
+----+-------+-----------------+----------------+-----------------+----------------+-----------------+
| 4 | Steve | SomeService4 | | | | 12 |
+----+-------+-----------------+----------------+-----------------+----------------+-----------------+
Every digit in zones is a tooth (dental science) and it means "John" has got "SomeService1" twice for tooth #3.
+----+------+-----------------+----------------+-----------------+----------------+-----------------+
| ID | Name | RecievedService | FirstZoneTeeth | SecondZoneTeeth | ThirdZoneTeeth | FourthZoneTeeth |
+----+------+-----------------+----------------+-----------------+----------------+-----------------+
| 1 | John | SomeService1 | 13 | | 4 | |
+----+------+-----------------+----------------+-----------------+----------------+-----------------+
| 2 | John | SomeService1 | 34 | | | |
+----+------+-----------------+----------------+-----------------+----------------+-----------------+
Note that Steve has received services twice for tooth #2 (4th Zone) but services are not one.
I'd write some code that gives me a table with duplicate rows (Checking the only patient and received service)(using "group by" clause") but I need to check zones too.
I've tried this:
select ROW_NUMBER() over(order by vv.ID_sick) as RowNum,
bb.Radif,
bb.VCount as 'Count',
vv.ID_sick 'ID_Sick',
vv.ID_service 'ID_Service',
sick.FNamesick + ' ' + sick.LNamesick as 'Sick',
serv.NameService as 'Service',
vv.Mab_Service as 'MabService',
vv.Mab_daryafti as 'MabDaryafti',
vv.datevisit as 'DateVisit',
vv.Zone1,
vv.Zone2,
vv.Zone3,
vv.Zone4,
vv.ID_dentist as 'ID_Dentist',
dent.FNamedentist + ' ' + dent.LNamedentist as 'Dentist',
vv.id_do as 'ID_Do',
do.FNamedentist + ' ' + do.LNamedentist as 'Do'
from visiting vv inner join (
select ROW_NUMBER() OVER(ORDER BY a.ID_sick ASC) AS Radif,
count(a.ID_sick) as VCount,
a.ID_sick,
a.ID_service
from visiting a
group by a.ID_sick, a.ID_service, a.Zone1, a.Zone2, a.Zone3, a.Zone4
having count(a.ID_sick)>1)bb
on vv.ID_sick = bb.ID_sick and vv.ID_service = bb.ID_service
left join InfoSick sick on vv.ID_sick = sick.IDsick
left join infoService serv on vv.ID_service = serv.IDService
left join Infodentist dent on vv.ID_dentist = dent.IDdentist
left join infodentist do on vv.id_do = do.IDdentist
order by bb.ID_sick, bb.ID_service,vv.datevisit
But this code only returns rows with all tooths repeated. What I want is even one tooth repeats ...
How can I implement it?
I need to check characters in zones.
**Zone's datatype is varchar
This is a bad datamodel for what you are trying to do. By storing the teeth as a varchar, you have kind of decided that you are not interested in single teeth, but only in the group of teeth. Now, however, you are trying to investigate on single teeth.
You'd want a datamodel like this:
service
+------------+--------+-----------------+
| service_id | Name | RecievedService |
+------------+--------+-----------------+
| 1 | John | SomeService1 |
+------------+--------+-----------------+
| 3 | Steve | SomeService3 |
+------------+--------+-----------------+
| 4 | Steve | SomeService4 |
+------------+-------+-----------------+
service_detail
+------------+------+-------+
| service_id | zone | tooth |
+------------+------+-------+
| 1 | 1 | 1 |
| 1 | 1 | 3 |
| 1 | 3 | 4 |
+------------+------+-------+
| 1 | 1 | 3 |
| 1 | 1 | 4 |
+------------+------+-------+
| 3 | 4 | 2 |
+------------+------+-------+
| 4 | 4 | 1 |
| 4 | 4 | 2 |
+------------+------+-------+
What you can do with the given datamodel is to create such table on-the-fly using a recursive query and string manipulation:
with unpivoted(service_id, name, zone, teeth) as
(
select recievedservice, name, 1, firstzoneteeth
from mytable where len(firstzoneteeth) > 0
union all
select recievedservice, name, 2, secondzoneteeth
from mytable where len(secondzoneteeth) > 0
union all
select recievedservice, name, 3, thirdzoneteeth
from mytable where len(thirdzoneteeth) > 0
union all
select recievedservice, name, 4, fourthzoneteeth
from mytable where len(fourthzoneteeth) > 0
)
, service_details(service_id, name, zone, tooth, teeth) as
(
select
service_id, name, zone, substring(teeth, 1, 1), substring(teeth, 2, 10000)
from unpivoted
union all
select
service_id, name, zone, substring(teeth, 1, 1), substring(teeth, 2, 10000)
from service_details
where len(teeth) > 0
)
, duplicates(service_id, name) as
(
select distinct service_id, name
from service_details
group by service_id, name, zone, tooth
having count(*) > 1
)
select m.*
from mytable m
join duplicates d on d.service_id = m.recievedservice and d.name = m.name;
A lot of work and a rather slow query due to a bad datamodel, but still feasable.
Rextester demo: http://rextester.com/JVWK49901

Split the date of same column in multiple rows till the next date value is specified - SQL Server

I have this table
+------+------------+-----+
| Code | date | qty |
+------+------------+-----+
| 1 | 06-07-2017 | 44 |
| 1 | 08-07-2017 | 45 |
| 2 | 07-07-2017 | 32 |
| 2 | 09-07-2017 | 33 |
+------+------------+-----+
and I want to display it this way
+------+------------+-----+
| Code | date | qty |
+------+------------+-----+
| 1 | 06-07-2017 | 44 |
| 1 | 07-07-2017 | 44 |
| 1 | 08-07-2017 | 45 |
| 2 | 07-07-2017 | 32 |
| 2 | 08-07-2017 | 32 |
| 2 | 09-07-2017 | 33 |
+------+------------+-----+
I want to split the date of same 'Code' and keep the same value for 'qty' till the next date of same 'Code'.
You need a calendar table and Outer Apply
;WITH cte
AS (SELECT Min([date]) AS st,
Max([date]) ed,
code
FROM Yourtable
GROUP BY code
UNION ALL
SELECT Dateadd(dd, 1, st) AS st,
ed,
code
FROM cte
WHERE Dateadd(dd, 1, st) <= ed)
SELECT c.code,
[date]=c.st,
qty
FROM cte c
OUTER apply (SELECT TOP 1 qty
FROM Yourtable a
WHERE a.code = c.code
AND c.st >= a.[date]
ORDER BY [date] DESC) oa
ORDER BY c.code,st
Note : For the sake of completeness I have used Recursive CTE to generate the dates you can always create a physical calendar table in your database and use it.
Live Demo

Find the first key by date field using sql and output also have other fields

I want to query the first occurrence of every name according to the earliest date. The output should have the complete row. Please help me to write the query in sql.
Input:
Name | ID | payment_date | Pack
------+-------+-----------------+-------
A | 11 | 31-Jan | P
C | 13 | 31-Jan | Q
B | 2 | 31-Jan | R
C | 3 | 28-Jan | P
D | 23 | 29-Jan | Q
B | 11 | 30-Jan | R
A | 17 | 25-Jan | P
C | 13 | 26-Jan | Q
D | 17 | 2-Feb | R
B | 23 | 3-Feb | P
A | 45 | 4-Feb | Q
B | 3 | 5-Feb | R
Output:
Name | ID | payment_date | Pack
-----+-------+--------------+-----
A | 17 | 25-Jan | P
B | 11 | 30-Jan | R
C | 13 | 26-Jan | Q
D | 23 | 29-Jan | Q
You can use the min function, also assuming payment_date is a date type:
select Name, ID, min(payment_date), Pack from mytable
group by payment_date,Name, ID, Pack
order by Name
The downfall about this method is putting all of the fields in the group by.
If your payment_date is a date data type, you can use not exists() like so:
select *
from t
where not exists (
select 1
from t i
where i.Name = t.Name
and i.payment_date < t.payment_date
)
rextester demo (sql server): http://rextester.com/OKB46268
returns
+------+----+-------------+------+
| Name | Id | PaymentDate | Pack |
+------+----+-------------+------+
| A | 17 | 2017-01-25 | P |
| B | 11 | 2017-01-30 | R |
| C | 13 | 2017-01-26 | Q |
| D | 23 | 2017-01-29 | Q |
+------+----+-------------+------+
You can also use Vertica's enhanced LIMIT clause:
WITH
-- input, don't use in real query
input(Name,ID,payment_date,Pack) AS (
SELECT 'A',11,DATE '31-Jan-2017','P'
UNION ALL SELECT 'C',13,DATE '31-Jan-2017','Q'
UNION ALL SELECT 'B',2, DATE '31-Jan-2017','R'
UNION ALL SELECT 'C',3, DATE '28-Jan-2017','P'
UNION ALL SELECT 'D',23,DATE '29-Jan-2017','Q'
UNION ALL SELECT 'B',11,DATE '30-Jan-2017','R'
UNION ALL SELECT 'A',17,DATE '25-Jan-2017','P'
UNION ALL SELECT 'C',13,DATE '26-Jan-2017','Q'
UNION ALL SELECT 'D',17,DATE '2-Feb-2017','R'
UNION ALL SELECT 'B',23,DATE '3-Feb-2017','P'
UNION ALL SELECT 'A',45,DATE '4-Feb-2017','Q'
UNION ALL SELECT 'B',3, DATE '5-Feb-2017','R'
)
-- end of input , start real query here:
SELECT * FROM input
LIMIT 1 OVER(PARTITION BY Name ORDER BY payment_date)
;
Happy playing ...
Marco the Sane