lets say I have a table containing
|vendor | price| productID|
|--------------------------------|
|abc | 6 | 0001 |
|1 | 7 | 0001 |
|def | 8 | 0001 |
|xyz | 30 | 0002 |
|zxy | 32 | 0002 |
now I want to get the vendor which has the min() price for a product
for product 0001 that would be Vendor abc
for product 0002 that would be Vendor xyz
BUT! IF there is a Vendor named 1 I would like to see his name instead of the actual vendor with the min() price, if there is no Vendor named 1 for a product, I want to see the the one with the min() price again
if that makes any sense for you.. its kinda like a if-else construct but I dont know how to do it in SQL
(sorry for the bad formatted table, I just dont get it formatted the right way)
Thank you
This is a prioritization query. One method is to use row_number() and to put the rules for prioritization into the order by. This resulting query:
select t.*
from (select t.*,
row_number() over (partition by productId
order by (case when vendorid = 1 then 1 else 2 end),
price asc
) as seqnum
from t
) t
where seqnum = 1;
The (currently) accepted answer does not meet the OP's requirement. The requirement was to show the minimum price for each product, even if vendor '1' does not have the lowest price. However, when someone else has the lowest price but vendor '1' also sells the same product, replace the vendor name with '1' (but don't change the lowest price to vendor `1``s price). This looks like "will meet my competitors' lowest price" type of arrangement.
Query and output:
with
price_table ( vendor, price, productid ) as (
select 'abc', 6 , '0001' from dual union all
select '1' , 7 , '0001' from dual union all
select 'def', 8 , '0001' from dual union all
select 'xyz', 30 , '0002' from dual union all
select 'zxy', 32 , '0002' from dual
),
prep ( vendor, price, productid, rn, ct ) as (
select vendor, price, productid,
row_number() over (partition by productid order by price),
count( case when vendor = '1' then 1 end ) over ( partition by productid)
from price_table
)
select case when ct = 0 then vendor else '1' end as vendor,
price,
productid
from prep
where rn = 1
;
VENDOR PRICE PROD
------ ------- ----
1 6 0001
xyz 30 0002
Related
I have two tables: products and products_prices.
products table:
id
name
user_id
1
Headphones
1
2
Phone
1
products_prices table:
id
product_id
price
time
1
1
10
1
2
1
15
2
3
1
20
3
4
2
10
4
5
2
15
5
6
2
20
6
I have a simple query:
SELECT * FROM products WHERE (user_id = 1) LIMIT 1 OFFSET 1
So I need to get limited rows from products table with only two prices values from table product_prices ordered by time for each row in products.
(I need to get product with two latest prices).
This is example of what I want to get:
id
user_id
name
curr_price
prev_price
2
1
Phone
20
15
And example of my query:
select products.*,
(SELECT price FROM products_prices WHERE product_id = products.id ORDER BY time asc LIMIT 1 OFFSET 0) as curr_price,
(SELECT price FROM products_prices WHERE product_id = products.id ORDER BY time asc LIMIT 1 OFFSET 1) as prev_price
from "products"
where (products."user_id" = 1)
limit 1 offset 1
Is it possible to do it without subqueries?
Not sure I find any of these easier to read...
0th approach using window functions and a CTE Demo
With products as (SELECT 1 ID, 'Headphones' name, 1 user_id UNION ALL
SELECT 2 ID, 'Phone' name, 1 user_id ),
products_Prices as (SELECT 1 ID, 1 Product_ID, 10 price, 1 time UNION ALL
SELECT 2 ID, 1 Product_ID, 15 price, 2 time UNION ALL
SELECT 3 ID, 1 Product_ID, 20 price, 3 time UNION ALL
SELECT 4 ID, 2 Product_ID, 33 price, 4 time UNION ALL
SELECT 5 ID, 2 Product_ID, 22 price, 5 time UNION ALL
SELECT 6 ID, 2 Product_ID, 11 price, 6 time),
STEP1 as (
SELECT P.ID, P.Name, P.user_ID,
price as CurrentPrice, lead(price) over (partition by P.ID order by time desc) Prev_Price, time,
row_number() over (Partition by P.ID order by time Desc) RN
FROM Products P
LEFT JOIN Products_Prices Z
on Z.Product_ID = P.ID)
SELECT Id, Name, User_ID, CurrentPRice, PRev_Price
From Step1 where RN = 1
Giving us:
+----+------------+---------+--------------+------------+
| id | name | user_id | currentprice | prev_price |
+----+------------+---------+--------------+------------+
| 1 | Headphones | 1 | 20 | 15 |
| 2 | Phone | 1 | 11 | 22 |
+----+------------+---------+--------------+------------+
1st approach using analytics and a CTE: note I changed price numbers to show variance.
DEMO
With products as (SELECT 1 ID, 'Headphones' name, 1 user_id UNION ALL
SELECT 2 ID, 'Phone' name, 1 user_id ),
products_Prices as (SELECT 1 ID, 1 Product_ID, 10 price, 1 time UNION ALL
SELECT 2 ID, 1 Product_ID, 15 price, 2 time UNION ALL
SELECT 3 ID, 1 Product_ID, 20 price, 3 time UNION ALL
SELECT 4 ID, 2 Product_ID, 33 price, 4 time UNION ALL
SELECT 5 ID, 2 Product_ID, 22 price, 5 time UNION ALL
SELECT 6 ID, 2 Product_ID, 11 price, 6 time),
STEP1 as (SELECT P.ID, P.Name, P.user_ID, PP.price, row_number() over (partition by PP.product_ID order by time desc) RN
FROM Products P
LEFT JOIN products_prices PP
on P.ID = PP.Product_ID)
SELECT ID, Name, User_ID, max(case when RN = 1 then Price end) as Current_price, max(case when RN=2 then price end) as Last_price
FROM STEP1
WHERE RN <=2
GROUP BY ID, name, User_ID
Giving us:
+----+------------+---------+---------------+------------+
| id | name | user_id | current_price | last_price |
+----+------------+---------+---------------+------------+
| 2 | Phone | 1 | 11 | 22 |
| 1 | Headphones | 1 | 20 | 15 |
+----+------------+---------+---------------+------------+
Option 2 using lateral.
demo
With products as (SELECT 1 ID, 'Headphones' name, 1 user_id UNION ALL
SELECT 2 ID, 'Phone' name, 1 user_id ),
products_Prices as (SELECT 1 ID, 1 Product_ID, 10 price, 1 time UNION ALL
SELECT 2 ID, 1 Product_ID, 15 price, 2 time UNION ALL
SELECT 3 ID, 1 Product_ID, 20 price, 3 time UNION ALL
SELECT 4 ID, 2 Product_ID, 33 price, 4 time UNION ALL
SELECT 5 ID, 2 Product_ID, 22 price, 5 time UNION ALL
SELECT 6 ID, 2 Product_ID, 11 price, 6 time)
SELECT P.ID, P.Name, P.user_ID, PP.price, time
FROM Products P
LEFT JOIN lateral (SELECT Product_ID, Price, time
FROM Products_Prices Z
WHERE Z.Product_ID = P.ID
ORDER BY Time Desc LIMIT 2) PP
on TRUE
ORDER BY TIME DESC;
Givng us : (unpivoted) and using the row number logic above we could pivot.
+----+------------+---------+-------+------+
| id | name | user_id | price | time |
+----+------------+---------+-------+------+
| 2 | Phone | 1 | 11 | 6 |
| 2 | Phone | 1 | 22 | 5 |
| 1 | Headphones | 1 | 20 | 3 |
| 1 | Headphones | 1 | 15 | 2 |
+----+------------+---------+-------+------+
Here is a sample code:
SELECT DISTINCT salary, planet,
sum(case when company LIKE '%Google%' then 1 end) `Google`,
sum(case when company LIKE '%IBM%' then 1 end) `IBM`,
sum(case when company LIKE '%Cisco%' then 1 end) `Cisco`
from industries
where planet = 'Earth' ;
Can someone give me advice how to summarize amount of multiple variables defined outside case condition? I tried to use simple math, but it did not work.
SELECT DISTINCT salary, planet,
sum(case when company LIKE '%Google%' then 1 end) `Google`,
sum(case when company LIKE '%IBM%' then 1 end) `IBM`,
sum(case when company LIKE '%Cisco%' then 1 end) `Cisco`,
-- similar math to count multiple columns,
sum(`Google` + `IBM` + `Cisco`) AS Total_amount
from industries
where planet = 'Earth' ;
The result should like this:
------------------------------------------------------------
| salary | Planet| Google | IBM | Cisco | Total_amount |
|----------------------------------------------------------|
| 3000.00 | Earth | 26 | 26 | 25 | 77 |
------------------------------------------------------------
Thanks in advance!
It's just in front of your eyes. It happens to me, too. Just COUNT(*) for the total amount.
WITH industries(salary,planet,company) AS (
SELECT 3000.00,'Earth','Google'
UNION ALL SELECT 3000.00,'Earth','Google'
UNION ALL SELECT 3000.00,'Earth','Google'
UNION ALL SELECT 3000.00,'Earth','Google'
UNION ALL SELECT 3000.00,'Earth','IBM'
UNION ALL SELECT 3000.00,'Earth','IBM'
UNION ALL SELECT 3000.00,'Earth','IBM'
UNION ALL SELECT 3000.00,'Earth','Cisco'
UNION ALL SELECT 3000.00,'Earth','Cisco'
UNION ALL SELECT 3000.00,'Earth','Cisco'
UNION ALL SELECT 3000.00,'Earth','Cisco'
UNION ALL SELECT 3000.00,'Earth','Cisco'
UNION ALL SELECT 3000.00,'Earth','Cisco'
)
SELECT
salary
, planet
, SUM(CASE company WHEN 'Google' THEN 1 END) AS Google
, SUM(CASE company WHEN 'IBM' THEN 1 END) AS IBM
, SUM(CASE company WHEN 'Cisco' THEN 1 END) AS Cisco
, COUNT(*) AS total_amount
FROM industries
WHERE planet = 'Earth'
GROUP BY
salary
, planet
;
-- out salary | planet | Google | IBM | Cisco | total_amount
-- out ---------+--------+--------+-----+-------+--------------
-- out 3000.00 | Earth | 4 | 3 | 6 | 13
Consider below (BigQuery)
SELECT salary, planet,
COUNTIF(company LIKE '%Google%') AS Google,
COUNTIF(company LIKE '%IBM%') AS IBM ,
COUNTIF(company LIKE '%Cisco%') AS Cisco,
COUNTIF(REGEXP_CONTAINS(company, 'Google|IBM|Cisco')) AS Total_amount
FROM industries
WHERE planet = 'Earth'
GROUP BY salary, planet
Yet another approach (BigQuery)
SELECT *, Google + IBM + Cisco AS Total_amount
FROM (
SELECT * EXCEPT(company),
REGEXP_EXTRACT(company, 'Google|IBM|Cisco') as col
FROM industries
WHERE planet = 'Earth'
)
PIVOT (COUNT(*) FOR col IN ('Google','IBM','Cisco'))
I have a simple student table.
name | amount | vdate
Josh | 15 | 01.01.2020
Steve | 25 | 05.04.2008
Josh | 40 | 01.01.2022
What I want to do is subtract Josh value from each other.
I wrote this query but it is not working
select name , sum(b.amount-a.amount) diff from
select name,amount from student a where name = 'Josh' and vdate='01.01.2020'
union all
select name,amount from student b where name = 'Josh' and vdate = '01.01.2022')
group by name
Expected Result is:
name | diff
Josh | 25
Steve| 25
you can try this code,
select
fname,
abs(sum(amount2)) amount
from
(
WITH
student(fname,amount,vdate) AS (
SELECT 'Josh' ,15, to_date('01102017','ddmmyyyy') from dual
UNION ALL SELECT 'Steve',25, to_date('01102017','ddmmyyyy') from dual
UNION ALL SELECT 'Josh' ,40 ,to_date('01102019','ddmmyyyy')from dual
)
select
h.fname,
h.amount,
decode((ROW_NUMBER() OVER(PARTITION BY fname order by vdate desc)),1,amount,amount* -1) amount2
from student h
)
group by
fname
;
I assume that you get the greater amount value of the person and substract other values, you can select the bigger date instead by modifying the order by clause in the partition window i. e.
decode((ROW_NUMBER() OVER(PARTITION BY fname order by vdate desc)),1,amount,amount * -1) amount2
You can try this (I don't know what sense it makes ...):
Count the number of rows found until now per fname ("name" is a reserved word and I don't use it). And if the row number obtained this way is odd, then use the negative amount, else the positive amount.
Finally, run a sum over these positive/negative rows.
WITH
indata(fname,amount) AS (
SELECT 'Josh' ,15
UNION ALL SELECT 'Steve',25
UNION ALL SELECT 'Josh' ,40
)
,
alternate AS (
SELECT
fname
, CASE ROW_NUMBER() OVER(PARTITION BY fname) % 2
WHEN 1 THEN amount * -1 -- when odd then negative
ELSE amount -- else positive
END AS amount
FROM indata
)
SELECT
fname
, ABS(SUM(amount)) AS amount -- absolute value
FROM alternate
GROUP BY fname;
-- out fname | amount
-- out -------+--------
-- out Josh | 25
-- out Steve | 25
I need a bit of help with a SQL query.
Imagine I've got the following table
id | date | price
1 | 1999-01-01 | 10
2 | 1999-01-01 | 10
3 | 2000-02-02 | 15
4 | 2011-03-03 | 15
5 | 2011-04-04 | 16
6 | 2011-04-04 | 20
7 | 2017-08-15 | 20
What I need is all dates where only one price is present.
In this example I need to get rid of row 5 and 6 (because there is two difference prices for the same date) and either 1 or 2(because they're duplicate).
How do I do that?
select date,
count(distinct price) as prices -- included to test
from MyTable
group by date
having count(distinct price) = 1 -- distinct for the duplicate pricing
The following should work with any DBMS
SELECT id, date, price
FROM TheTable o
WHERE NOT EXISTS (
SELECT *
FROM TheTable i
WHERE i.date = o.date
AND (
i.price <> o.price
OR (i.price = o.price AND i.id < o.id)
)
)
;
JohnHC answer is more readable and delivers the information the OP asked for ("[...] I need all the dates [...]").
My answer, though less readable at first, is more general (allows for more complexes tie-breaking criteria) and also is capable of returning the full row (with id and price, not just date).
;WITH CTE_1(ID ,DATE,PRICE)
AS
(
SELECT 1 , '1999-01-01',10 UNION ALL
SELECT 2 , '1999-01-01',10 UNION ALL
SELECT 3 , '2000-02-02',15 UNION ALL
SELECT 4 , '2011-03-03',15 UNION ALL
SELECT 5 , '2011-04-04',16 UNION ALL
SELECT 6 , '2011-04-04',20 UNION ALL
SELECT 7 , '2017-08-15',20
)
,CTE2
AS
(
SELECT A.*
FROM CTE_1 A
INNER JOIN
CTE_1 B
ON A.DATE=B.DATE AND A.PRICE!=B.PRICE
)
SELECT * FROM CTE_1 WHERE ID NOT IN (SELECT ID FROM CTE2)
There is my current query:
SELECT Name, Code, Today
, Account || Currency as Accounts
FROM (
SELECT
b.description AS Name
, b.contragentidentifycode AS Code
, c.systemday AS Today
, b.accountno AS Account
, b.currencysname AS Currency
FROM vAACCOUNT b, currentdaysetting c
WHERE b.contragentid = 412
AND b.accountno LIKE '26%'
)
it gives me such result:
Name | Code | Today | Accounts
---------------------------------------
name1 | code1 | 07.09.2016 | acc1+curr1
name1 | code1 | 07.09.2016 | acc2+curr1
name1 | code1 | 07.09.2016 | acc1+curr2
name1 | code1 | 07.09.2016 | acc2+curr2
name1 | code1 | 07.09.2016 | acc1+curr3
name1 | code1 | 07.09.2016 | acc2+curr3
name1 | code1 | 07.09.2016 | acc1+curr4
name1 | code1 | 07.09.2016 | acc2+curr4
I need convert this view to:
Name | Code | Today | someName1 | someName2 | someName3 | someName4 | someName5 | someName6 | someName7 | someName8
-------------------------------------------------------------------------------------------------------------------------------------------
name1 | code1 | 07.09.2016 | acc1+curr1 | acc2+curr1 | acc1+curr2 | acc2+curr2 | acc1+curr3 | acc2+curr3 | acc1+curr4 | acc2+curr4
I guess that most probably for this I have to use the keyword "Pivot". But all my attempts to do so - have failed. I can not to project what I see in the examples, to my table. Please help.
For number of columns I can add such "id" column:
SELECT id, Name, Code, Today
, Account || Currency as Accounts
FROM (
SELECT
row_number() over (ORDER BY b.id) AS id
, b.description AS Name
...
In my scenario:
numbers of accounts may be different;
name, code and data - one per query;
combination of accaunt+currency are unique;
result should be in one line;
total number of lines in result of query, cannot be more then 10 (in my example 8)
Per my comment above, I don't think PIVOT works for you. The answer from #RoundFour works, but requires that you know, and code for, all possible values for Account || Currency. This suggests there will never be new values for these items - I find that unlikely.
The following will allow you to switch the shape of your data. It makes no assumptions about the values in your data, but it does assume a limit on the number of possible combinations - I have coded for eight.
WITH account_data (name,code,today,account)
AS
(
SELECT 'name1','code1',TO_DATE('07.09.2016','DD.MM.YYYY'),'acc1+curr1' FROM dual UNION ALL
SELECT 'name1','code1',TO_DATE('07.09.2016','DD.MM.YYYY'),'acc2+curr1' FROM dual UNION ALL
SELECT 'name1','code1',TO_DATE('07.09.2016','DD.MM.YYYY'),'acc1+curr2' FROM dual UNION ALL
SELECT 'name1','code1',TO_DATE('07.09.2016','DD.MM.YYYY'),'acc2+curr2' FROM dual UNION ALL
SELECT 'name1','code1',TO_DATE('07.09.2016','DD.MM.YYYY'),'acc1+curr3' FROM dual UNION ALL
SELECT 'name1','code1',TO_DATE('07.09.2016','DD.MM.YYYY'),'acc2+curr3' FROM dual UNION ALL
SELECT 'name1','code1',TO_DATE('07.09.2016','DD.MM.YYYY'),'acc1+curr4' FROM dual UNION ALL
SELECT 'name1','code1',TO_DATE('07.09.2016','DD.MM.YYYY'),'acc2+curr4' FROM dual UNION ALL
SELECT 'name2','code1',TO_DATE('07.09.2016','DD.MM.YYYY'),'acc1+curr1' FROM dual UNION ALL
SELECT 'name2','code1',TO_DATE('07.09.2016','DD.MM.YYYY'),'acc2+curr1' FROM dual UNION ALL
SELECT 'name2','code1',TO_DATE('07.09.2016','DD.MM.YYYY'),'acc1+curr2' FROM dual UNION ALL
SELECT 'name3','code1',TO_DATE('07.09.2016','DD.MM.YYYY'),'acc2+curr2' FROM dual
)
SELECT
name
,code
,today
,MAX(account1)
,MAX(account2)
,MAX(account3)
,MAX(account4)
,MAX(account5)
,MAX(account6)
,MAX(account7)
,MAX(account8)
FROM
(SELECT
name
,code
,today
,CASE
WHEN rn = 1 THEN account
END account1
,CASE
WHEN rn = 2 THEN account
END account2
,CASE
WHEN rn = 3 THEN account
END account3
,CASE
WHEN rn = 4 THEN account
END account4
,CASE
WHEN rn = 5 THEN account
END account5
,CASE
WHEN rn = 6 THEN account
END account6
,CASE
WHEN rn = 7 THEN account
END account7
,CASE
WHEN rn = 8 THEN account
END account8
FROM
(SELECT
name
,code
,today
,account
,ROW_NUMBER() OVER (PARTITION BY name ORDER BY account) rn
FROM
account_data
)
)
GROUP BY
name
,code
,today
;
UPDATE >>>>>>>>>
The WITH... clause above is just because I don't have your tables and data in my system. I've rewritten my answer using your query as a guide - please note I have not been able to test this ...
SELECT
name
,code
,today
,MAX(account1)
,MAX(account2)
,MAX(account3)
,MAX(account4)
,MAX(account5)
,MAX(account6)
,MAX(account7)
,MAX(account8)
FROM
(SELECT
name
,code
,today
,CASE
WHEN rn = 1 THEN account
END account1
,CASE
WHEN rn = 2 THEN account
END account2
,CASE
WHEN rn = 3 THEN account
END account3
,CASE
WHEN rn = 4 THEN account
END account4
,CASE
WHEN rn = 5 THEN account
END account5
,CASE
WHEN rn = 6 THEN account
END account6
,CASE
WHEN rn = 7 THEN account
END account7
,CASE
WHEN rn = 8 THEN account
END account8
FROM
(SELECT
b.description AS Name
,b.contragentidentifycode AS Code
,c.systemday AS Today
,b.accountno AS Account
,b.currencysname AS Currency
,b.accountno || b.currencysname AS Accounts
,ROW_NUMBER() OVER (PARTITION BY b.description ORDER BY b.accountno) rn
FROM vAACCOUNT b, currentdaysetting c
WHERE b.contragentid = 412
AND b.accountno LIKE '26%'
)
)
GROUP BY
name
,code
,today
;
If you know all the account+currency combinations you can use this pivot (I only implemented 3 of them here):
select *
from (
<your-query> )
pivot (
min(accounts) as accounts FOR (accounts) in ('acc1+curr1' as a, 'acc2+curr1' as b, 'acc1+curr2' c)
);
There is my pivot solution:
SELECT *
FROM (
SELECT id, Name, Code, Today, Account || Currency as Accounts
FROM (
SELECT
row_number() over (ORDER BY b.id) AS id
, b.description AS Name
, b.contragentidentifycode AS Code
, c.systemday AS Today
, b.accountno AS Account
, b.currencysname AS Currency
FROM vAACCOUNT b, currentdaysetting c
WHERE b.contragentid = 412
AND b.accountno LIKE '26%'
)
)
pivot (
MIN(Accounts)
FOR ID IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
) pvt