T-SQL count number of different values across multiple columns - sql

I have an SQL database which contains a table with four different currency columns, I need to determine how many different currencies each record has across these four columns, for example..
Record ID
Curr1
Curr2
Curr3
Curr4
CurrencyCount
1
GBP
USD
GBP
GBP
2
2
GBP
EUR
GBP
USD
3
3
GBP
GBP
GBP
GBP
1
4
GBP
GBP
GBP
EUR
2
How can I determine the "CurrencyCount" / count of currencies against each record? I can't think how to approach this
Thank you for any help

You can use a Table Value Constructor referencing columns from the outer row and then COUNT (DISTINCT - no need for any expanding out and collapsing with GROUP BY
SELECT *,
CurrrencyCount = (SELECT COUNT (DISTINCT Currency) FROM (VALUES (CURR1), (CURR2), (CURR3), (CURR4)) AS x (Currency))
FROM YourTable

This would do the work. But not sure how will be the performance if there is a large amount of data.
WITH CTE as (
SELECT '1' as RecID,'GBP' as CURR1,'USD' as CURR2,'GBP' as CURR3, 'GBP' as CURR4
UNION ALL
SELECT '2' ,'GBP' ,'EUR' ,'GBP','USD'
UNION ALL
SELECT '3' ,'GBP' ,'GBP' ,'GBP','GBP'
UNION ALL
SELECT '4' ,'GBP' ,'GBP' ,'GBP','EUR'
)
SELECT
RecID
,CURR1,CURR2,CURR3,CURR4
,COUNT(DISTINCT Currency) as CurrrencyCount
FROM CTE
CROSS APPLY (VALUES (CURR1), (CURR2), (CURR3), (CURR4)) as x (Currency)
GROUP BY RecID,CURR1,CURR2,CURR3,CURR4

Only run this its also work
SELECT
RecordID
,Curr1,Curr2,Curr3,Curr4
,COUNT(DISTINCT Currency) as Currency
FROM [TABLE_NAME]
CROSS APPLY (VALUES (Curr1), (Curr2), (Curr3),(Curr4)) as x (Currency)
GROUP BY RecordID,Curr1,Curr2,Curr3,Curr4

Related

Populate a column via multi-dimensional lookup in SQL?

I have the following two tables:
TABLE_A:
Date
USD
EUR
2020-01-31
1.11
0.89
2020-02-28
1.15
0.93
TABLE_B:
Date
Currency
Amount
2020-01-31
USD
NULL
2020-02-29
EUR
NULL
I want to populate the Amount field in TABLE_B with the corresponding amount in TABLE_A dependent on the matching date and currency. Is there a way to perform this multi-dimensional lookup in SQL?
The only join I can see that is common to both tables is the date value, you can join on that and then use a CASE expression to choose the appropriate value from tableA based on the value in tableB
SELECT CASE WHEN b.Currency = 'USD' THEN a.USD ELSE a.EUR END as [Amount]
FROM tableA AS a
INNER JOIN tableB AS b
ON b.Date = a.Date

Link on two tables if not all values between fields match in PostgreSQL?

I have two tables
exchange_rates:
curr1 curr2 rate
USD GBP 0.81
EUR GBP 0.98
transactions
TIMESTAMP user curr amt
2017-01-01 u1 EUR 89
2017-01-01 u2 GBP 3
2017-01-03 u2 USD 10
I want to link exchange_rates and transactions and multiply amt by the corresponding exchange rate in GBP in exchange_rates. e.g. in line 3 of transactions we would multiply 10 by 0.81 to get 8.1. BUT if the amount is in GBP in the transactions table I want to leave that unchanged.
I have tried to use CASE and link the two table like this
select
trans.TIMESTAMP, trans.user
case
when trans.currency != "GBP" then trans.amt*er.rate
else trans.amt
end as "Converted Amount"
from exchange_rates er, transactions trans
where trans.curr = er.curr1
But this doesn't work when curr in transactions is GBP (line 2) since there is not curr1=GBP in exchange_rates... can anyone advise what the logic would be here to solve this?
DESIRED RESULT:
TIMESTAMP user converted amt
2017-01-01 u1 87.22
2017-01-01 u2 3
2017-01-03 u2 8.1
Simply add a record in exchange_rates that defines a 1 to 1 exchange rate for GBP:
curr1 curr2 rate
GBP GBP 1
Aside from that you can also use a left outer join to include all the matching records in the source table, regardless if the joined table matched: http://www.postgresqltutorial.com/postgresql-left-join/
select
trans.TIMESTAMP, trans.user,
trans.amt * coalesce(er.rate, 1) as "Converted Amount"
from transactions trans
left join exchange_rates er on er.curr1 = trans.curr
I think you want something like this:
select t.TIMESTAMP, t.user,
t.amt * coalesce(er.rate, 1) as converted_amount
from transactions t left join
exchange_rates er
on t.curr = er.curr1 and er.curr2 = 'GBP';
Why does this look a bit different from your query?
First, it uses a left join. Second, it compares both currencies. It also simplifies the logic for the lookup.

sql Query to find different record in a column

I have table with 5 OR 6 COLUMNS and I need to use the below 2 columns to get the result
col1 col2
Acc1 USD
ACC1 GBP
ACC1 EUR
ACC2 USD
Result:
I need to find out if a acc has more than 2 currency, but the base currency is USD. I need to find out those records which has USD plus other currency if I have only USD accounts then it should not come in my result.
With the information provided this could be an answer:
SELECT col1
FROM tab
GROUP BY col1
HAVING count(*) > 1
Not the best solution but should do the job.
with cte as
(
SELECT t1.[col1],t1.[col2],(select count(t2.col1) from accounts t2 where t2.col1=t1.col1) as AllCurrency
from accounts t1
)
SELECT distinct cte.col1 from cte where cte.AllCurrency>1

Selecting Account Numbers with latest date

I have been trying to solve this problem now for days.
I have table called Stat with the following simplified structure and sample data:
Customer BankID AccNumb Type Date Amount AccType
Customer 1 Boa 5 Account Statement 2015-01-01 10000,00 Eur
Customer 1 CS 10 Account Statement 2015-04-04 22000,00 Eur
Customer 2 Sa 15 Account Statement 2015-03-13 3000,00 Eur
Customer 2 Sa 40 Account Statement 2015-04-24 1000,00 Eur
Customer 2 Sa 15 Sale Advice 2015-04-16 400,00 Eur
Customer 2 Sa 15 Account Statement 2015-12-24 50,00 Usd
Customer 2 Boa 20 Sale Advice 2015-05-15 6000,00 Eur
Customer 3 Cu 25 Account Statement 2015-11-27 81000,00 Eur
Customer 3 Cu 30 Sale Advice 2015-11-27 3000,00 Usd
Customer 3 Pop 30 Account Statement 2015-11-27 12000,00 Eur
What I'm trying to do is to Select the AccountNumber with the latest date specified. A Customer can also have different Account Numbers on various Banks, so it should also be grouped by BankID and Customer.
I have come this far:
SELECT AccNumb, Customer, BankID,
(SELECT TOP 1 Amount FROM Stat
WHERE AccNumb = y.AccNumb AND Customer = y.Customer AND
BankID = y.BankID AND Type = 'Account Statement' AND
Date = MAX(y.Date) GROUP BY Amount) Amount
FROM Stat y
GROUP BY AccNumb, Customer, BankID
ORDER BY Customer, AccNumb
And it works fine, the problem is i should also add the column AccType and Date
I managed to do this with 2 more subselects (the query takes long but it works).
But now i have the problem that there are also NULL values in Customer (or Date) Column. Now, the account number of these 'NULL' Customers still should be displayed if it's the latest date. I also tried to do the same by joining the table by itself, and it didnt work out.
SELECT x.AccNumber, x.Customer, x.BankID, x.Date, y.Amount, y.AccType
FROM Stat y RIGHT JOIN
(SELECT AccNumber, Customer, BankID, MAX(Date) Date FROM Stat
GROUP BY AccNumber, Customer, BankID) x
ON x.AccNumber = y.AccNumber AND
x.Customer = y.Customer AND
x.BankID = y.BankID AND
x.Date = y.Date
ORDER BY y.Customer, y.AccNumber
But now the 'NULL' Customers only have NULL values in the Amount, Date and AccType Columns, which is not correct.
The output should be something like this
AccNumb Customer BankID Amount Date AccType
111111111 a Boa 1234.40 31.06.2014 Eur
222222222 NULL Boa 5678.40 31.04.2014 Eur
333333333 b Boa 0.00 25.02.2014 Eur
444444444 NULL Boa 9101.40 23.04.2015 Eur
555555555 NULL Boa 1213.40 31.02.2014 Usd
A66666666 c Sa NULL 31.02.2014 Eur
777777777 c Sa 1415.00 31.12.2014 Eur
888888888 c Boa 1617.40 31.12.2014 Usd
999999999 f Pop 5678.64 31.10.2014 Eur
Thanks in advance.
Just use row_number(), if I understand correctly:
select s.*
from (select s.*,
row_number() over (partition by customer, bankId order by date desc) as seqnum
from stat s
) s
where seqnum = 1;
Find the rows with latest dates, i.e. return a row if no other row with same AccountNumber, BankID and Customer but a later Date exists:
select *
from stat s1
where not exists (select 1 from stat s2
where s1.AccountNumber = s2.AccountNumber
and s1.BankID = s2.BankID
and s1.Customer = s2.Customer
and s1.Date < s2.Date)
You're first query works closely to what, I believe, you are looking for. Using it as a base, we can alter to work for you:
SELECT
AccNumb,
Customer,
BankID,
Amount,
Date,
AccType
FROM Stat y
WHERE Date = (SELECT MAX(z.DATE)
FROM Stat z
WHERE z.AccNumb = y.AccNumb
AND z.Customer = y.Customer
AND z.BankID = y.BankID AND Type = 'Account Statement')
ORDER BY Customer, AccNumb

Select entire row with more than one distinct column

I have a table based on invoice items where I am trying to use SQL to detect at what dates the price or currency for the combination of material/customer has changed. The table contains invoices for several customers although the materials can be common.
My SQL skills are quite basic and I have tried several different approaches using GROUP BY and DISTINCT that I have found in other threads but I always seem to get stuck somewhere along the way.
This is basically what the data looks like:
Invoice Inv. Date Material Price Currency Per/Qty Customer
SE100 20140901 111111 1 EUR 1 840006
SE100 20140901 222222 2 EUR 1000 840006
SE100 20140901 333333 3 USD 1 840006
SE101 20140902 111111 1 EUR 1 840006
SE101 20140902 222222 2 EUR 1000 840006
SE101 20140902 333333 3 USD 1 840006
SE102 20140903 111111 2 EUR 1 840006
SE102 20140903 222222 2 USD 1000 840006
SE102 20140903 333333 3 USD 1 840006
SE103 20140904 111111 1 EUR 1 840006
SE103 20140904 222222 2 USD 1000 840006
SE103 20140904 333333 3 USD 1 840006
What I want to accomplish is basically to select the first row datewise for all distinct combinations of Customer/Material/Currency/Price and then subselect the entire rows (sorted by material) for those materials that occur more than once in the selection, thus indicating the price or currency has changed from the initial value.
The expected output from the query using the data in the table above would then look something like this:
Invoice Inv. Date Material Price Currency Per/Qty Customer
SE100 20140901 111111 1 EUR 1 840006
SE102 20140903 111111 2 EUR 1 840006
SE103 20140904 111111 1 EUR 1 840006
SE100 20140901 222222 2 EUR 1000 840006
SE102 20140903 222222 2 USD 1000 840006
I hope I managed to explain the problem in an understandable way. The database engine is SQL Server 2005 Express.
Any help would be appreciated...
The key word DISTINCT in SQL has the meaning of "unique value". When applied to a column in a query it will return as many rows from the result set as there are unique, different values for that column. As a consequence it creates a grouped result set, and values of other columns are random unless defined by other functions (such as max, min, average, etc.)
If you meant to say you want to return all rows for which Col 06 has a specific value, then use the "where Col 06 = value" clause
SELECT mt.*
FROM (
SELECT DISTINCT col6
FROM mytable
) mto
JOIN mytable mt
ON mt.id =
(
SELECT TOP 1 id
FROM mytable mti
WHERE mti.col6 = mto.col6
-- ORDER BY
-- id
-- Uncomment the lines above if the order matters
)
I think this is a direct translation of what you want:
select t.*
from mydata t join
(select Customer, Material, count(distinct price) as numprices
from mydata
group by Customer, Material
having count(distinct price) > 1
) cmcp
on t.customer = cmcp.customer and t.material = cmcp.material;
This leaves out the currency. Unfortunately, SQL Server doesn't support multiple arguments to distinct. You can include it this way:
select t.*
from mydata t join
(select Customer, Material,
count(distinct cast(price as varchar(255)) + ':' + currency) as numprices
from mydata
group by Customer, Material
having count(distinct cast(price as varchar(255)) + ':' + currency) > 1
) cmcp
on t.customer = cmcp.customer and t.material = cmcp.material;
Most databases support window/analytic functions, so you can also phrase this as:
select t.*
from (select t.*,
min(cast(price as varchar(255)) + ':' + currency)) over (partition by Customer, Material) as minprice,
max(cast(price as varchar(255)) + ':' + currency)) over (partition by Customer, Material) as maxprice
from mydata t
) t
where minprice <> maxprice
order by Material, Inv_Date;