SQL - Comparing Values - sql

I need your help again. Here's my SQL query :
SELECT sp.pro_ID, description, price, sp.sup_ID
FROM L4_Sup_Pro sp, L4_Products prod
WHERE prod.our_id = sp.pro_id
AND (sp.sup_ID = '23' OR sp.sup_ID = '75')
ORDER BY sp.pro_ID;
And this query gives me this result :
I need to compare the prices of SUP_ID column having values 75 and 23, and display the cheapest price. So output will be:
PRO_ID DESCRIPTION PRICE SUP_ID
101 Laser Printer 630 23
121 Color Jet Printer 223 23
302 Scanner 399 75

You can use ROW_NUMBER() analytic function
SELECT pro_ID, description, price, sup_ID
FROM
(
SELECT sp.pro_ID, description, price, sp.sup_ID,
ROW_NUMBER() OVER (PARTITION BY description ORDER BY price ) as rn
FROM L4_Sup_Pro sp
JOIN L4_Products prod
ON prod.our_id = sp.pro_id
WHERE sp.sup_ID in (23,75) -- without quotes by considering SUP_ID is a numeric column
)
WHERE rn = 1
ORDER BY pro_ID
Demo

Presuming columns description, and price stored on L4_Products, you can try below query -
SELECT sp.pro_ID, prod.description, prod.price, sp.sup_ID
FROM L4_Sup_Pro sp
JOIN (SELECT our_id, description, MIN(price) price
FROM L4_Product
GROUP BY our_id, description) prod ON prod.our_id = sp.pro_id
WHERE sp.sup_ID = '23'
OR sp.sup_ID = '75'
ORDER BY sp.pro_ID;

One method is aggregation:
SELECT sp.pro_ID, description,
MIN(price),
MIN(sp.sup_ID) KEEP (DENSE_RANK FIRST ORDER BY price) as min_sup_ID
FROM L4_Sup_Pro sp JOIN
L4_Products prod
ON prod.our_id = sp.pro_id AND
WHERE sp.sup_ID IN (23, 75) -- looks like a number so I dropped the quotes
GROUP BY p.pro_ID, description

You just need a group by with having
SELECT sp.pro_ID, description,
min(price) , min(sp.sup_ID)
FROM L4_Sup_Pro sp, L4_Products
prod
WHERE prod.our_id = sp.pro_id AND
(sp.sup_ID = '23' OR sp.sup_ID = '75')
Group by s.pro_id,description having
1=
Max(case when price=min(price)
then 1
else 0 end)
ORDER BY sp.pro_ID;

Related

Conditional return values in SQL

I have started a simple auction system where each row contains the following data:
Type:= BID|WIT
ProductName := Str
User := Str
Value := Decimal
CreatedAt := datetime
*Here WIT means withdraw, aka quit from item auction *
A user can naturally do multiple bid requests, always raising the bid value (this is handled at language level)
I would like to list all top bids from all users but under a condition, only if they are not before a WITHDRAW request for the given item.
For example, given the entries
BID,Doll,John,10,2021-11-26 10:10
BID,Ball,John,12,2021-11-26 10:11
BID,Doll,Mary,12,2021-11-26 10:12
BID,Doll,Alice,13,2021-11-26 10:13
BID,Doll,Mary,14,2021-11-26 10:14
BID,Doll,Alice,17,2021-11-26 10:14
BID,Ball,Mary,14,2021-11-26 10:14
WIT,Doll,John,00,2021-11-26 10:16
BID,Doll,Mary,20,2021-11-26 10:18
BID,Ball,John,15,2021-11-26 10:20
If I magic_sql_query(Doll) I would like to get
BID,Doll,Alice,17,2021-11-26 10:14
BID,Doll,Mary,20,2021-11-26 10:18
Also If I magic_sql_query(Ball) I would like to get:
BID,Ball,Mary,14,2021-11-26 10:14
BID,Ball,John,15,2021-11-26 10:20
How can I do it in a SQL Statement?
You can
use the row_number() windowing function to rank within a group (a
group is defined by the username and the product); the latest entry
gets rank 1
get rid of all entries whose rank is > 1 (i.e. the user has a later entry for this product)
get rid of all entries of type 'WIT'
with base as (select
b.type,
b.productname,
b.username,
b.value,
b.createdat,
row_number() over (partition by productname, username
order by createdat desc) as rn
from bids b
order by productname, username, createdat
)
select * from base
where rn = 1
and type = 'BID';
SQL Fiddle
To find all bids that have no withdrawal later (by the same user for the same product) you can use a NOT EXITS condition:
select a1.*
where a1.product_name = 'Ball'
and type = 'BID'
and not exists (select *
from auction a2
where a2.product_name = a1.product_name
and a2.user_name = a1.user_name
and a2.type = 'WIT'
and a2.created_at > a1.created_at)
Now we need to filter out the highest bids per product and user. This can be done using the dense_rank() function.
select type, product_name, user_name, value, created_at
from (
select a1.*, dense_rank() over (partition by product_name, user_name order by value desc) as rnk
from auction a1
where a1.product_name = 'Ball'
and type = 'BID'
and not exists (select *
from auction a2
where a2.product_name = a1.product_name
and a2.user_name = a1.user_name
and a2.type = 'WIT'
and a2.created_at > a1.created_at)
) t
where rnk = 1
order by created_at;
Online example
A left join version
select t.Type, product_name, t.User, Value, CreatedAt
from (
select b.*
, row_number() over(partition by b.product_name, b.user order by b.createdAt desc) rn
from auction b
left join auction w
on w.type = 'WIT'
and b.product_name = w.product_name
and b.user = w.user
and w.createdAt > b.createdAt
where b.type = 'BID'
and w.product_name is null
-- and b.product_name = 'Ball'
) t
where rn=1
-- and product_name = 'Ball'
order by product_name, CreatedAt;
uncomment one of .. product_name = 'Ball' to get only this product.
db<>fidlle
You can use MAX() window function to get for each user the last entry with type = 'BID' and the last entry with type = 'WIT'.
Then filter the results:
SELECT type, productname, "user", value, createdat
FROM (
SELECT *,
MAX(CASE WHEN type = 'BID' THEN createdat END) OVER (PARTITION BY "user") last_bid,
MAX(CASE WHEN type = 'WIT' THEN createdat END) OVER (PARTITION BY "user") last_wit
FROM tablename
WHERE productname = ?
) t
WHERE (last_wit IS NULL OR last_wit < last_bid) AND createdat = last_bid;
Replace ? with the name of the product that you want.
See the demo.

Where should i put the AS Clause for tax rate (VAT_RATE)

I want to put a AS VAT_RATE in this SELECT statement but i don't know where.
SELECT ROW_NUMBER() OVER(ORDER BY QD.DETAIL_ID) AS No,
QD.PRODUCT_ID AS PROD_ID,PM.'+#ProdCode+' AS PROD_CODE,pm.DESCRIPTION AS SHORT_DESC,
QD.CORPORATE_PRICE AS Corpo_Price,CONVERT(DECIMAL(18,2),QD.RETAIL_PRICE) AS UNIT_SP,QD.COST_PRICE AS COST_SP,
QD.GM,QD.DETAIL_ID,QD.DISC AS Discount,QD.NOTE,
VAT_RATE=(SELECT VAT_RATE/100 FROM dbo.vat
WHERE VAT_ID=(SELECT TOP 1 VAT_ID FROM dbo.product_detail(NOLOCK) WHERE PRODUCT_ID=PM.PROD_ID))
,
Img=(SELECT TOP 1 IMAGE_DATA FROM dbo.PRODUCT_IMAGE WHERE PRODUCT_ID=PM.PROD_ID), QD.CostPrice_Percentage
FROM dbo.CUSTOMER_QUOTATION_DETAIL(NOLOCK) QD
JOIN dbo.product_master(NOLOCK) PM ON PM.PROD_ID=QD.PRODUCT_ID
In TSQL you there is 3 way to name your columns
1) With the AS (optional in tsql)
SELECT QD.PRODUCT_ID AS PROD_ID
FROM dbo.CUSTOMER_QUOTATION_DETAIL(NOLOCK) QD
2) Without the AS (since it is optional)
SELECT QD.PRODUCT_ID PROD_ID
FROM dbo.CUSTOMER_QUOTATION_DETAIL(NOLOCK) QD
3) with an equal sign as if it is a formula
SELECT PROD_ID = QD.PRODUCT_ID
FROM dbo.CUSTOMER_QUOTATION_DETAIL(NOLOCK) QD
Specifically for your query this is where the AS should go.
You would have to remove the equal and put the AS at the end of the sub-query.
Please do note that you have various other issues with the queries that is beyond the scope your original question. If you run into preformance issue, do investigated on the subject of CROSS APPLY / CROSS OUTER JOIN and/or CTE : Common Table Expression.
SELECT ROW_NUMBER() OVER (
ORDER BY QD.DETAIL_ID
) AS No
, QD.PRODUCT_ID AS PROD_ID
--, PM.'+#ProdCode+' AS PROD_CODE
, #ProdCode AS PROD_CODE
, pm.DESCRIPTION AS SHORT_DESC
, QD.CORPORATE_PRICE AS Corpo_Price
, CONVERT(DECIMAL(18, 2), QD.RETAIL_PRICE) AS UNIT_SP
, QD.COST_PRICE AS COST_SP
, QD.GM
, QD.DETAIL_ID
, QD.DISC AS Discount
, QD.NOTE
, (
SELECT TOP 1 (VAT_RATE / 100)
FROM dbo.vat
WHERE VAT_ID = (
SELECT TOP 1 VAT_ID
FROM dbo.product_detail(NOLOCK)
WHERE PRODUCT_ID = PM.PROD_ID
)
) AS VAT_RATE
, (
SELECT TOP 1 IMAGE_DATA
FROM dbo.PRODUCT_IMAGE
WHERE PRODUCT_ID = PM.PROD_ID
) AS Img
, QD.CostPrice_Percentage
FROM dbo.CUSTOMER_QUOTATION_DETAIL(NOLOCK) QD
JOIN dbo.product_master(NOLOCK) PM
ON PM.PROD_ID = QD.PRODUCT_ID

Select columns maximum and minimum value for all records

I have a table as below; I want to get the column names having maximum and minimum value except population column (ofcourse it will have maximum value) for all records.
State Population age_below_18 age_18_to_50 age_50_above
1 1000 250 600 150
2 4200 400 300 3500
Result :
State Population Maximum_group Minimum_group Max_value Min_value
1 1000 age_18_to_50 age_50_above 600 150
2 4200 age_50_above age_18_to_50 3500 300
Assuming none of the values are NULL, you can use greatest() and least():
select state, population,
(case when age_below_18 = greatest(age_below_18, age_18_to_50, age_50_above)
then 'age_below_18'
when age_below_18 = greatest(age_below_18, age_18_to_50, age_50_above)
then 'age_18_to_50'
when age_below_18 = greatest(age_below_18, age_18_to_50, age_50_above)
then 'age_50_above'
end) as maximum_group,
(case when age_below_18 = least(age_below_18, age_18_to_50, age_50_above)
then 'age_below_18'
when age_below_18 = least(age_below_18, age_18_to_50, age_50_above)
then 'age_18_to_50'
when age_below_18 = least(age_below_18, age_18_to_50, age_50_above)
then 'age_50_above'
end) as minimum_group,
greatest(age_below_18, age_18_to_50, age_50_above) as maximum_value,
least(age_below_18, age_18_to_50, age_50_above) as minimum_value
from t;
If your result set is actually being generated by a query, there is likely a better approach.
An alternative method "unpivots" the data and then reaggregates:
select state, population,
max(which) over (dense_rank first_value order by val desc) as maximum_group,
max(which) over (dense_rank first_value order by val asc) as minimum_group,
max(val) as maximum_value,
min(val) as minimum_value
from ((select state, population, 'age_below_18' as which, age_below_18 as val
from t
) union all
(select state, population, 'age_18_to_50' as which, age_18_to_50 as val
from t
) union all
(select state, population, 'age_50_above' as which, age_50_above as val
from t
)
) t
group by state, population;
This approach would have less performance than the first, although it is perhaps easier to implement as the number of values increases. However, Oracle 12C supports lateral joins, where a similar approach would have competitive performance.
with CTE as (
select T.*
--step2: rank value
,RANK() OVER (PARTITION BY "State", "Population" order by "value") "rk"
from (
--step1: union merge three column to on column
select
"State", "Population",
'age_below_18' as GroupName,
"age_below_18" as "value"
from TestTable
union all
select
"State", "Population",
'age_18_to_50' as GroupName,
"age_18_to_50" as "value"
from TestTable
union all
select
"State", "Population",
'age_50_above' as GroupName,
"age_50_above" as "value"
from TestTable
) T
)
select T1."State", T1."Population"
,T3.GroupName Maximum_group
,T4.GroupName Minimum_group
,T3."value" Max_value
,T4."value" Min_value
--step3: max rank get maxvalue,min rank get minvalue
from (select "State", "Population",max( "rk") as Max_rank from CTE group by "State", "Population") T1
left join (select "State", "Population",min( "rk") as Min_rank from CTE group by "State", "Population") T2
on T1."State" = T2."State" and T1."Population" = T2."Population"
left join CTE T3 on T3."State" = T1."State" and T3."Population" = T1."Population" and T1.Max_rank = T3."rk"
left join CTE T4 on T4."State" = T2."State" and T4."Population" = T2."Population" and T2.Min_rank = T4."rk"
SQL Fiddle DEMO LINK
Hope it help you :)
Another option: use a combination of UNPIVOT(), which "rotates columns into rows" (see: documentation) and analytic functions, which "compute an aggregate value based on a group of rows" (documentation here) eg
Test data
select * from T ;
STATE POPULATION YOUNGERTHAN18 BETWEEN18AND50 OVER50
1 1000 250 600 150
2 4200 400 300 3500
UNPIVOT
select *
from T
unpivot (
quantity for agegroup in (
youngerthan18 as 'youngest'
, between18and50 as 'middleaged'
, over50 as 'oldest'
)
);
-- result
STATE POPULATION AGEGROUP QUANTITY
1 1000 youngest 250
1 1000 middleaged 600
1 1000 oldest 150
2 4200 youngest 400
2 4200 middleaged 300
2 4200 oldest 3500
Include Analytic Functions
select distinct
state
, population
, max( quantity ) over ( partition by state ) maxq
, min( quantity ) over ( partition by state ) minq
, first_value ( agegroup ) over ( partition by state order by quantity desc ) biggest_group
, first_value ( agegroup ) over ( partition by state order by quantity ) smallest_group
from T
unpivot (
quantity for agegroup in (
youngerthan18 as 'youngest'
, between18and50 as 'middleaged'
, over50 as 'oldest'
)
)
;
-- result
STATE POPULATION MAXQ MINQ BIGGEST_GROUP SMALLEST_GROUP
1 1000 600 150 middleaged oldest
2 4200 3500 300 oldest middleaged
Example tested w/ Oracle 11g (see dbfiddle) and Oracle 12c.
Caution: {1} column (headings) need adjusting (according to your requirements). {2} If there are NULLs in your original table, you should adjust the query eg by using NVL().
An advantage of the described approach is: the code will remain rather clear, even if more 'categories' are used. Eg when working with 11 age groups, the query may look something like ...
select distinct
state
, population
, max( quantity ) over ( partition by state ) maxq
, min( quantity ) over ( partition by state ) minq
, first_value ( agegroup ) over ( partition by state order by quantity desc ) biggest_group
, first_value ( agegroup ) over ( partition by state order by quantity ) smallest_group
from T
unpivot (
quantity for agegroup in (
y10 as 'youngerthan10'
, b10_20 as 'between10and20'
, b20_30 as 'between20and30'
, b30_40 as 'between30and40'
, b40_50 as 'between40and50'
, b50_60 as 'between50and60'
, b60_70 as 'between60and70'
, b70_80 as 'between70and80'
, b80_90 as 'between80and90'
, b90_100 as 'between90and100'
, o100 as 'over100'
)
)
order by state
;
See dbfiddle.

How to get alternate row using SQL query?

I have a requirement which I need to produce the result that returns alternately 1 and 0.
SELECT *
FROM
(SELECT
id
,itemNo
,convert(int,tStationsType_id) as tStationsType_id
,tSpecSetpoint_descriptions_id
,SetpointValue
,rowEvenOdd
FROM
TEST S
INNER JOIN
(SELECT
itemNo AS IM, tStationsType_id as ST,
ROW_NUMBER() OVER(PARTITION BY itemNo ORDER BY itemNo) % 2 AS rowEvenOdd
FROM TEST
GROUP BY itemNo, tStationsType_id) A ON S.itemNo = A.IM
AND S.tStationsType_id = A.ST) t
WHERE
itemno = '1000911752202'
ORDER BY
tStationsType_id
The result I get is something like below.
I would like to produce alternate 1 and 0 in rowEvenOdd. However I notice it I can't get it alternate if I order by tStationsType_id.
So basically, what I want is when the
StationsType_id = 2, then rowEvenOdd = 0
StationsType_id = 3, then rowEvenOdd = 1
StationsType_id = 6, then rowEvenOdd = 0
StationsType_id = 8, then rowEvenOdd = 1
StationsType_id = 10, then rowEvenOdd = 0
Can someone help me with this query?
Thanks.
If you just need alternating 0 and 1 in the result set, use SEQUENCE like this:
CREATE SEQUENCE EvenOdd
START WITH 0
INCREMENT BY 1
MAXVALUE 1
MINVALUE 0
CYCLE;
GO
SELECT SalesId, NEXT VALUE FOR EvenOdd as EvenOddColumn FROM Sales
DROP SEQUENCE EvenOdd
To learn more, go to the MSDN page on sequences here: https://msdn.microsoft.com/en-us/library/ff878091.aspx
you can Use case when then
SELECT
case
when exists (SELECT 1 FROM StationsTypeMaster
M WHERE M.StationsType_id=A.StationsType_id)
then 1
else 0 end as rowEvenOdd
,*
FROM
(
select id
,itemNo
,convert(int,tStationsType_id) as tStationsType_id
,tSpecSetpoint_descriptions_id
,SetpointValue
,rowEvenOdd from TEST S
inner join
(select itemNo AS IM,tStationsType_id as ST,
ROW_NUMBER() OVER(PARTITION BY itemNo ORDER BY itemNo)%2 AS rowEvenOdd
from TEST
group by itemNo,tStationsType_id
)A
on S.itemNo = A.IM
and S.tStationsType_id = A.ST) t
where itemno = '1000911752202'
order by tStationsType_id
Use DENSE_RANK instead of ROW_NUMBER:
SELECT * FROM
(
SELECT
id,
itemNo
convert(int,tStationsType_id) as tStationsType_id,
tSpecSetpoint_descriptions_id,
SetpointValue,
(DENSE_RANK() OVER(ORDER BY CAST(A.ST as INT)) - 1)%2 AS rowEvenOdd
FROM
TEST S
JOIN
(
SELECT
itemNo AS IM,
tStationsType_id as ST
FROM
TEST
GROUP BY
itemNo,tStationsType_id
)A
ON
S.itemNo = A.IM
and S.tStationsType_id = A.ST
) t
WHERE
itemno = '1000911752202'
ORDER BY
tStationsType_id

Filter maximum value from sql count query

I currently have the following query
SELECT organisation.organisationID, COUNT(organisation.organisationID)
FROM position, positionLocation, organisation
WHERE position.positionLocationID = positionLocation.positionLocationID AND
positionLocation.organisationID = organisation.organisationID AND
position.status = 'Open'
GROUP BY organisation.organisationID;
This query outputs
organisationID | countOrganisationID
1 3
3 2
5 3
I would like to display records that have max countOrganisationID. Ideally i would just like output the organisationID with its corresponding organisationName if possible.
Something along the lines of
organisationID | organisatioName
1 name1
5 name2
Any help would be appreciate
Thanks
Barrett is right, RANK() is the way to go, e.g.:
SELECT organisationID, c FROM (
SELECT organisationID
,c
,RANK() OVER (ORDER BY c DESC) r
FROM (
SELECT organisation.organisationID
,COUNT(organisation.organisationID) AS c
FROM position, positionLocation, organisation
WHERE position.positionLocationID = positionLocation.positionLocationID
AND positionLocation.organisationID = organisation.organisationID
AND position.status = 'Open'
GROUP BY organisation.organisationID
)
) WHERE r = 1;
Could just subquery it:
WITH counts AS (
SELECT organisation.organisationID
,organisation.organisationName
,COUNT(organisation.organisationID) the_count
FROM position, positionLocation, organisation
WHERE position.positionLocationID = positionLocation.positionLocationID
AND positionLocation.organisationID = organisation.organisationID
AND position.status = 'Open'
GROUP BY organisation.organisationID, organisation.organisationName
)
SELECT organisationID, organisationName
FROM counts
WHERE the_count = (SELECT MAX(the_count) FROM counts)
This should work.
SELECT organisationID, organisatioName
FROM position, positionLocation, organisation
WHERE position.positionLocationID = positionLocation.positionLocationID AND
positionLocation.organisationID = organisation.organisationID AND
position.status = 'Open'
AND COUNT(organisation.organisationID) =
SELECT MAX(cnt) AS MaxCnt FROM
SELECT organisation.organisationID, COUNT(organisation.organisationID) AS cnt
FROM organisation
WHERE position.status = 'Open'
GROUP BY organisation.organisationID
GROUP BY organisation.organisationID, organisation.organisatioName;