T-Sql: Select Rows where at least two fields matches condition - sql

I've got a table, let's call it values with a primary key and five integer fields, like this:
id val1 val2 val3 val4 val5
1 4 3 4 5 3
2 2 3 2 2 2
3 5 4 1 3 3
4 1 4 3 4 4
Now I need to select all rows where at least any two of the five value fields got the value 4. So the result set should contain the first row (id=1) and the last row (id=4).
I started with a simple OR condition but there are too many combinations. Then I tried a sub-select with HAVING and COUNT but no success.
Any Ideas how to solve this?

You can use VALUES to construct an inline table containing your fields. Then query this table to get rows having at least two fields equal to 4:
SELECT *
FROM mytable
CROSS APPLY (
SELECT COUNT(*) AS cnt
FROM (VALUES (val1), (val2), (val3), (val4), (val5)) AS t(v)
WHERE t.v = 4) AS x
WHERE x.cnt >= 2
Demo here

Although cross apply is fast, it might be marginally faster to simply use case:
select t.*
from t
where ((case when val1 = 4 then 1 else 0 end) +
(case when val2 = 4 then 1 else 0 end) +
(case when val3 = 4 then 1 else 0 end) +
(case when val4 = 4 then 1 else 0 end) +
(case when val5 = 4 then 1 else 0 end)
) >= 2;
I will also note that case is ANSI standard SQL and available in basically every database.

This is trivial to solve if your data is normalized - so lets use UNPIVOT to normalize the data and then solve it:
declare #t table (id int not null, val1 int not null, val2 int not null,
val3 int not null, val4 int not null, val5 int not null)
insert into #t(id,val1,val2,val3,val4,val5) values
(1,4,3,4,5,3),
(2,2,3,2,2,2),
(3,5,4,1,3,3),
(4,1,4,3,4,4)
select
id
from
#t t
unpivot
(valness for colness in (val1,val2,val3,val4,val5)) r
group by id
having SUM(CASE WHEN valness=4 THEN 1 ELSE 0 END) >= 2
Results:
id
-------
1
4
Of course, you can probably come up with better names than valness and colness that describes what these pieces of data (the numbers being stored and the numbers embedded in the column names) actually are.

Related

split 1 column in 5 columns

I have a relatively simple problem (I think), but I can not get my head around it. I have a single column with 200 records. The values of the rows are a list of numbers 1-200 (No duplicates). I would like to organize the numbers in 5 columns with the data sorted horizontally (Ascending A-Z). Example:
Original table:
Column1
1
2
3
4
5
6
7
8
...and so on to 200
The view result should look like this:
|Col1|Col2|Col3|Col4|Col5|
1 2 3 4 5
6 7 8 9 10
11 12 ...an so on to 200
I looked into PIVOTING but I don't think it will work.
We can try a pivot query where the group is defined as the column minus one, divided by 5. This places the first five records into the first group/row, the next five into the second group/row, and so on. Then, we pivot on each Column1 % 5 value to determine into which of the five columns each value will go.
SELECT
MAX(CASE WHEN Column1 % 5 = 1 THEN Column1 END) AS Col1,
MAX(CASE WHEN Column1 % 5 = 2 THEN Column1 END) AS Col2,
MAX(CASE WHEN Column1 % 5 = 3 THEN Column1 END) AS Col3,
MAX(CASE WHEN Column1 % 5 = 4 THEN Column1 END) AS Col4,
MAX(CASE WHEN Column1 % 5 = 0 THEN Column1 END) AS Col5
FROM yourTable
GROUP BY
(Column1 - 1) / 5;
Demo

Average across rows in SQL Server 2008

I have the following table
Data Data1 Data2 YTD
-------------------------
1 2 3
2 3 4
3 3 6
In the YTD column I have to average the rows data. I can use average in columns but not sure how to average across rows.
Looking for the below results and using SQL Server 2008
Data Data1 Data2 YTD
---------------------------------
1 2 3 2 (Average)
2 3 4 3
3 null 6 4.5
I think cross apply is the simplest method:
select t.*, v.avg_data
from t cross apply
(select avg(v.data) as avg_data
from (values (t.data), (t.data1), (t.data2)) v(data)
) v;
Use case expressions, you can also express this as:
select t.*,
( (coalesce(t.data, 0) + (t.data1, 0) + coalesce(t.data2, 0)) /
nullif( (case when t.data is not null then 1 else 0 end) +
(case when t.dat1 is not null then 1 else 0 end) +
(case when t.dat2 is not null then 1 else 0 end), 0
)
) as avg_data;
However, this formulation is messy and prone to typing errors.

How to improve UNION of distinct values from the same table with different filters

Background:
The client has the possibility to create his own stock of vehicles. This means that he can display only the vehicles that match his criteria.
He also has the possibility to create frontend filters for users. These filters can synchronize between them. For example, if a user chooses something from filter X, the filter Y shows only the values that match X filter
The performance issue is at populating the frontend filters with values.
What I tried is something like this
;with v as(
select something from vehicles
where [his stock filters])
select 'XFilter',X_id from v where Y_id > [value] and Z_id > [value]
union
select 'YFilter',Y_id from v where X_id > [value] and Z_id > [value]
union
select 'ZFilter',Z_id from v where X_id > [value] and Y_id > [value]
union
...
Multiple unions are going to be added to the query and adding different filters to all of them are hard to index or so I believe.
How should I proceed in order to obtain a better performance? Should I retrieve the data in a different way?
EDIT:
The Vehicles table have columns like the following
ID ColorID FueltypeID Mileage ...
1 1 1 1000
2 1 2 500
3 2 2 2000
4 2 1 1500
5 3 1 9000
6 3 2 8000
Using the above query, I want to extract all the distinct values for filters which have a limited set of values, like colors and fueltypes.
The expected result would be
reference value
1 Color 1
2 Color 2
3 Color 3
4 Fueltype 1
5 Fueltype 2
It is really hard to tell what you want, but it appears to be:
with v as (
select something
from vehicles
where [users stock filters])
)
select f.which, f.id
from v cross apply
(values ('XFilter', v.x_id, (case when Y_id > [value] and Z_id > [value] then 1 else 0 end) ),
('YFilter', v.y_id, (case when X_id > [value] and Z_id > [value] then 1 else 0 end) ),
('ZFilter', v.z_id, (case when X_id > [value] and Y_id > [value] then 1 else 0 end) )
) f(which, id, outcome)
where f.outcome = 1;

How to check Oracle column values are all the same for a specific ID?

I am trying to figure out the best way to determine, for a specific ID within an Oracle 11g table that has 5 columns and say 100 rows against this ID, if all the column values are the same for these five columns.
For example:
Table Name: TABLE_DATA
Columns:
TD_ID ID COL1 COL2 COL3 COL4 COL5
-----------------------------------------------------------------------
1 1 1 0 3 2 0
2 1 1 0 3 2 0
3 1 1 0 3 2 0
4 1 1 0 3 2 0
5 1 1 0 3 2 0
6 1 1 0 3 2 0
So based on the above example which is just showing 6 rows for now against the ID:1, I want to check that for all COL1, COL2, COL3, COL4 and COL5 values where ID = 1, tell me if all the values are the same from the very first row right down to the last – if so, then return ‘Y’ else return ‘N’.
Given the above example, the result would be ‘Y’ but for instance, if TD_ID = 5 and COL3 = 4 then the result would be ‘N’, as all the column values are not the same, i.e.:
TD_ID ID COL1 COL2 COL3 COL4 COL5
-----------------------------------------------------------------------
1 1 1 0 3 2 0
2 1 1 0 3 2 0
3 1 1 0 3 2 0
4 1 1 0 3 2 0
5 1 1 0 4 2 0
6 1 1 0 3 2 0
I’m just not sure what the fastest approach to determine this is, as the table I am looking at may have more than 2000 rows within the table for a specific ID.
You may also try this :
Select ID
, case when count(distinct COL1 || COL2 || COL3 || COL4 || COL5) > 1
then 'N'
else 'Y' end RESULT
From TABLE_DATA
Group by id;
In this way you group by id and counts how many distinct combination are there.
If only 1 , so all the rows have the same set of values, otherwise it don't.
See if the following is fast enough for you:
SELECT ID, CASE WHEN COUNT(*) > 1 THEN 'No' ELSE 'Yes' END As "Result"
FROM (SELECT DISTINCT ID, COL1, COL2, COL3, COL4, COL5
FROM Table_Data) dist
GROUP BY ID
Here's a little query, you might wanna try out (eventually, you just could try figuring out a better MINUS statement for you):
SELECT
CASE
WHEN ( -- select count of records from a subquery
SELECT
COUNT(1)
FROM
( -- select all rows where id = 1
SELECT
td.col1
,td.col2
,td.col3
,td.col4
,td.col5
FROM
table_data td
WHERE
td.id = 1
MINUS -- substract the first row of the table with id = 1
SELECT
td.col1
,td.col2
,td.col3
,td.col4
,td.col5
FROM
table_data td
WHERE
td.id = 1
AND ROWNUM = 1
)
) = 0 -- check if subquery's count equals 0
AND EXISTS ( -- and exists at least 1 row in the table with id = 1
SELECT
1
FROM
table_data td
WHERE
td.id = 1
AND ROWNUM = 1
) THEN 'Y'
ELSE 'N'
END AS equal
FROM
dual

Multiple counts for each ID

I'm trying to get my head around this, but unfortunately neither of my approaches works:
I need a table with 3 columns:
ItemID
Number cases where ItemID has CostcentreID x
Number cases where ItemID has CostcentreID y
SELECT ItemID, Count1, Count2
FROM Table
Output should be like:
--ItemID--Count1--Count2
1 12 5
2 3 2
What i get when using
SELECT ItemdID, SUM(case when costc...),...
FROM Table
is:
--ItemID--Count1--Count2
1 12 0
2 3 0
due to the GROUP BY statement.
Anyway to solve this without a Cursor?
Also, a JOIN of 5 tables is needed.
Thanks in advance!
I'm not sure what you need with the joins, but here is the first part.
DECLARE #table TABLE(ItemID INT, CostCentreID CHAR(1));
INSERT INTO #table
VALUES (1,'X'),
(1,'X'),
(1,'Y'),
(2,'X'),
(2,'Y'),
(2,'Y'),
(2,'Y');
SELECT ItemID,
SUM(
CASE
WHEN CostCentreID = 'X' THEN 1 ELSE 0
END
) AS CostCentreX,
SUM(
CASE
WHEN CostCentreID = 'Y' THEN 1 ELSE 0
END
) AS CostCentreY
FROM #table
GROUP BY ItemID
Results:
ItemID CostCentreX CostCentreY
----------- ----------- -----------
1 2 1
2 1 3