I have a transaction table with item details for each company. I want to write a query to retrieve the companies only having item numbers 1,2 and 3 (according to my sample code in below). Selected companies should have all 1,2,3 items. If some company has only item 1, then it shouldn't come. How can I write this?
CREATE TABLE #TmpTran
(
ID BIGINT IDENTITY,
COMPANY_ID BIGINT,
ITEM_NAME VARCHAR(50),
ITEM_NUMBER INT
)
INSERT INTO #TmpTran (COMPANY_ID, ITEM_NAME, ITEM_NUMBER)
VALUES (1, 'ABC', 1), (1, 'DEF', 2), (1, 'HIJ', 3),
(2, 'KLM', 4), (2, 'KLM', 5), (2, 'ABC', 1)
How can I get only Company 1 data using WHERE or JOIN query?
You can do this with group by and having:
select company_id
from #tmptran tt
where item_number in (1, 2, 3)
group by company_id
having count(distinct item_number) = 3;
Another way (more flexible approach)
select company_id
from #tmptran tt
group by company_id
having count(case when item_number = 1 then 1 end) > 0;
and count(case when item_number = 2 then 1 end) > 0;
and count(case when item_number = 3 then 1 end) > 0;
select tt.company_id
from #tmptran tt
where tt.item_number in (1, 2, 3)
group by tt.company_id
having sum(max(case tt.item_number when 1 then 1 end)) +
and sum(max(case tt.item_number when 2 then 1 end)) +
and sum(max(case tt.item_number when 3 then 1 end)) = 3
You said you have a lot of fields. Probably the easiest for the reader to follow would be something like:
select distinct tt.company_id
from #tmptran tt
where tt.item_number in (1, 2, 3)
and exists(select 1
from #tmptran ttSub
where ttSub.company_id = tt.company_id and ttSub.item_number = 1)
and exists(select 1
from #tmptran ttSub
where ttSub.company_id = tt.company_id and ttSub.item_number = 2)
and exists(select 1
from #tmptran ttSub
where ttSub.company_id = tt.company_id and ttSub.item_number = 3)
Related
I have a (subquery) table that lists meal preferences for my friends. Each meal can only be taken once, and each person can only eat one meal.
row_number person_id meal_id
1 1 3
2 2 1
3 2 2
4 2 3
5 3 1
6 3 2
7 3 3
The picking order is determined by the original order of the table, so I would like the result to be:
person_id meal_id
1 3
2 1
3 2
Because meal 1 is taken by user 2, user 3 gets meal 2. I think this could be solved by selecting distinct values in both columns based on their original order, but I cannot figure out how to write that query. Any help appreciated.
Update Added row_number to original table.
If I understand correctly, this is a rather complicated graph walking problem. I should first note that there is no guarantee of an optimal solution -- without lots and lots of work. But you can implement a greedy algorithm using recursive CTEs:
with recursive t as (
select v.*
from (values (1, 1, 3), (2, 2, 1), (3, 2, 2), (4, 2, 3), (5, 3, 1), (6, 3, 2), (7, 3, 3)
) v(row_number, person_id, meal_id)
),
cte (row_number, person_id, meal_id, rows, persons, meals, lev) as (
select row_number, person_id, meal_id, array[row_number], array[person_id], array[meal_id], 1 as lev
from t
where row_number = 1
union all
select t.row_number, t.person_id, t.meal_id,
(case when t.person_id = any(cte.persons) or t.meal_id = any(cte.meals)
then cte.rows
else array_append(cte.rows, t.row_number)
end),
(case when t.person_id = any(cte.persons) or t.meal_id = any(cte.meals)
then cte.persons
else array_append(cte.persons, t.person_id)
end),
(case when t.person_id = any(cte.persons) or t.meal_id = any(cte.meals)
then cte.meals
else array_append(cte.meals, t.meal_id)
end),
cte.lev + 1
from cte join
t
on t.row_number = cte.row_number + 1
)
select t.*
from t cross join
(select rows from cte order by lev desc fetch first 1 row only) as last1
where t.row_number = any (last1.rows);
Here is a db<>fiddle.
I have some sample data like:
INSERT INTO mytable
([FK_ID], [TYPE_ID])
VALUES
(10, 1),
(11, 1), (11, 2),
(12, 1), (12, 2), (12, 3),
(14, 1), (14, 2), (14, 3), (14, 4),
(15, 1), (15, 2), (15, 4)
Now, here I am trying to check if in each group by FK_ID we have exact match of TYPE_ID values for 1, 2 & 3.
So, the expected output is like:
(10, 1) this should fail
As in group FK_ID = 10 we only have one record
(11, 1), (11, 2) this should also fail
As in group FK_ID = 11 we have two records.
(12, 1), (12, 2), (12, 3) this should pass
As in group FK_ID = 12 we have two records.
And all the TYPE_ID are exactly matching 1, 2 & 3 values.
(14, 1), (14, 2), (14, 3), (14, 4) this should also fail
As we have 4 records here.
(15, 1), (15, 2), (15, 4) this should also fail
Even though we have three records, it should fail as the TYPE_ID here (1, 2, 4) are not matching with required match (1, 2, 3).
Here is my attempt:
select * from mytable t1
where exists (select COUNT(t2.TYPE_ID)
from mytable t2 where t2.FK_ID = t1.FK_ID
and t2.TYPE_ID IN (1, 2, 3)
group by t2.FK_ID having COUNT(t2.TYPE_ID) = 3);
This is not working as expected, because it also pass for FK_ID = 14 which has four records.
Demo: SQL Fiddle
Also, how we can make it generic so that if we need to check for 4 or more TYPE_ID values like (1,2,3,4) or (1,2,3,4,5), we can do that easily by updating few values.
The following query will do what you want:
select fk_id
from t
group by fk_id
having sum(case when type_id in (1, 2, 3) then 1 else 0 end) = 3 and
sum(case when type_id not in (1, 2, 3) then 1 else 0 end) = 0;
This assumes that you have no duplicate pairs (although depending on how you want to handle duplicates, it might be as easy as using, from (select distinct * from t) t).
As for "genericness", you need to update the in lists and the 3.
If you want something more generic:
with vals as (
select id
from (values (1), (2), (3)) v(id)
)
select fk_id
from t
group by fk_id
having sum(case when type_id in (select id from vals) then 1 else 0 end) = (select count(*) from vals) and
sum(case when type_id not in (select id from vals) then 1 else 0 end) = 0;
You can use this code:
SELECT y.fk_id FROM
(SELECT x.fk_id, COUNT(x.type_id) AS count, SUM(x.type_id) AS sum
FROM mytable x GROUP BY (x.fk_id)) AS y
WHERE y.count = 3 AND y.sum = 6
For making it generic, you can equal y.count with N and y.sum with N*(N-1)/2, where N is the number you are looking for (1, 2, ..., N).
You can try this query. COUNT and DISTINCT used for eliminate duplicate records.
SELECT
[FK_ID]
FROM
#mytable T
GROUP BY
[FK_ID]
HAVING
COUNT(DISTINCT CASE WHEN [TYPE_ID] IN (1,2,3) THEN [TYPE_ID] END) = 3
AND COUNT(CASE WHEN [TYPE_ID] NOT IN (1,2,3) THEN [TYPE_ID] END) = 0
Try this:
select FK_ID,count(distinct TYPE_ID) from mytable
where TYPE_ID<=3
group by FK_ID
having count(distinct TYPE_ID)=3
You should use CTE with Dynamic pass Value which you have mentioned in Q.
WITH CTE
AS (
SELECT FK_ID,
COUNT(*) CNT
FROM #mytable
GROUP BY FK_ID
HAVING COUNT(*) = 3) <----- Pass Value here What you want to Display Result,
CTE1
AS (
SELECT T.[ID],
T.[FK_ID],
T.[TYPE_ID],
ROW_NUMBER() OVER(PARTITION BY T.[FK_ID] ORDER BY
(
SELECT NULL
)) RN
FROM #mytable T
INNER JOIN CTE C ON C.FK_ID = T.FK_ID),
CTE2
AS (
SELECT C1.FK_ID
FROM CTE1 C1
GROUP BY C1.FK_ID
HAVING SUM(C1.TYPE_ID) = SUM(C1.RN))
SELECT TT1.*
FROM CTE2 C2
INNER JOIN #mytable TT1 ON TT1.FK_ID = C2.FK_ID;
From above SQL Command which will produce Result (I have passed 3) :
ID FK_ID TYPE_ID
4 12 1
5 12 2
6 12 3
i have following table "vehicle_data" :
ID ALERT_TYPE VALUE
58 2 1
58 1 1
104 1 1
104 2 1
Here alert_type = 2 is for GPS value and alert_type=1 is for engine_value .
so if alert_type=2 and its value is =1 then it means its value is correct.
when alert_type=2 and its value is =0 then it means its value is wrong.
same for alert_type=1
so now here i want the following output:
ID gps engine_value
58 1 1
104 1 1
how can i perform this query??
You can do it like this.
SELECT ID
,CASE WHEN [ALERT_TYPE]=2 and [value ]=1 THEN 1 ELSE 0 END as gps
,CASE WHEN [ALERT_TYPE]=1 and [value ]=1 THEN 1 ELSE 0 END as engine
FROM vehicle_data
SELECT ID, alert_type=2 AS gps, alert_type=1 AS [engine] FROM vehicle_data WHERE value=1;
EDITED to account for your explanation of VALUE.
Schema
CREATE TABLE table3 (id int, ALERT_TYPE int)
INSERT table3 VALUES (58, 1), (58, 2), (104, 1), (104, 2)
Query
SELECT *
FROM (
SELECT ID
,ROW_NUMBER() OVER (
PARTITION BY id ORDER BY id
) AS row_num
,gps = CASE
WHEN ALERT_TYPE = 2
THEN 1
ELSE 0
END
,engine = CASE
WHEN ALERT_TYPE = 1
THEN 1
ELSE 0
END
FROM table3
) a
WHERE a.row_num = 1
Output
ID gps engine
58 1 0
104 0 1
One possible way using subqueries :
select
Ids.ID
, gps.VALUE 'gps'
, engine_value.VALUE 'engine_value'
from (select distinct ID from vehicle_data) Ids
left join
(select ID, VALUE from vehicle_data where ALERT_TYPE = 2) gps
on gps.ID = Ids.ID
left join
(select ID, VALUE from vehicle_data where ALERT_TYPE = 1) engine_value
on engine_value.ID = Ids.ID
[SQL Fiddle demo]
I hope this should work for you,
Select ID,sum(gps) as gps ,sum(engine) as engine from
(SELECT ID
,CASE WHEN [ALERT_TYPE]=2 and [value ]=1 THEN 1 ELSE 0 END as gps
,CASE WHEN [ALERT_TYPE]=1 and [value ]=1 THEN 1 ELSE 0 END as engine
FROM vehicle_data
)a
group by id
select x.id,x.alert_type as GPS,x.value as engine_value from (
select ID,alert_type,value,ROW_NUMBER() over (partition by id order by alert_type ) as Rnk from mytable
)x
where Rnk=1
Please check this query in SQL :
create table mytable (id int, alert_type int, value int);
insert into mytable (id, alert_type, value)
values (58, 2, 1),
(58, 1, 1),
(104, 1, 1),
(104, 2, 1);
SELECT distinct ID
,(select count (id) from mytable mt where mt.id=mytable.id and mt.[ALERT_TYPE]=2 and mt.[value ]=1) as gps
,(select count (id) from mytable mt where mt.id=mytable.id and mt.[ALERT_TYPE]=1 and mt.[value ]=1) as engine
FROM mytable
BASED ON YOUR QUESTION I BELIEVE YOU WANT THE DATA IN COLUMN AND TO SUIT YOUR REQUIREMENT I HAVE MADE A SQL FIDDLE WORKING - CODE IS ALSO MENTIONED BELOW -
HERE YOU GO WITH THE WORKING FIDDLE -
WORKING DEMO
SQL CODE FOR REFERNECE -
CREATE TABLE ALERTS (ID INT, ALERT_TYPE INT, VALUE INT)
INSERT INTO ALERTS VALUES (58,2,1)
INSERT INTO ALERTS VALUES (58,1,0)
INSERT INTO ALERTS VALUES (104,1,1)
INSERT INTO ALERTS VALUES (104,2,0)
CREATE TABLE ALERTSVALUE (ID INT, gps INT,engine INT)
INSERT INTO ALERTSVALUE VALUES (58,1,0)
INSERT INTO ALERTSVALUE VALUES (104,0,1)
SELECT A.ID,
CASE A.ALERT_TYPE WHEN 2 THEN 1 ELSE 0 END AS GPS,
CASE A.ALERT_TYPE WHEN 1 THEN 1 ELSE 0 END AS ENGINE_VALUE,
A.VALUE FROM ALERTS A WHERE A.VALUE = 1
EDIT BASED ON COMMENT - TO MERGE THE ROWS FOR BOTH GPS AND ENGINE_VALUE:
SELECT X.ID,X.ALERT_TYPE as GPS,X.VALUE as ENGINE_VALUE
FROM (
SELECT ID,ALERT_TYPE ,VALUE ,ROW_NUMBER() OVER (PARTITION BY ID ORDER BY alert_type ) AS [Rank] FROM ALERTS
)X
WHERE [Rank]=1
SQL FIDDLE DEMO
I have a table like this:
TransId. LayerNo. AccountId.
100. 1. 2.
100. 2. 3.
120. 1. 5.
120. 2. 6.
120. 3. 12.
70. 1. 2.
I want to find transId(s) where:
(LayerNo = 1 and (accountId = 2 or 5))
and
(LayerNo = 2 and (accountId = 3 or 6))
And result set would be row no 1,2,3,4.
How could I write query to get the result?
My database is SQL server 2008 r2
Thanks in advance
Nima
SELECT TransId
FROM your_table
WHERE ( layerno = 1
AND accountid IN ( 2, 5 ) )
INTERSECT
SELECT TransId
FROM your_table
WHERE ( layerno = 2
AND accountid IN ( 3, 6 ) )
One approach is to ensure that each transID must have two records that satisfy the conditions you outlined.
SELECT * FROM
TABLE
WHERE TransID IN(
SELECT TransId
FROM table
WHERE ( layerno = 1
AND accountid IN ( 2, 5 ) )
OR ( layerno = 2
AND accountid IN( 3, 6 ) )
GROUP BY
TransId
HAVING Count(*) = 2
)
However this could be a problem if you can have multple records where layerno = 1. So you can use self joins instead to ensure the criteria.
SELECT DISTINCT a.transid
FROM table a
INNER JOIN table b
ON a.transid = b.transid
INNER JOIN table c
ON a.transid = c.transid
WHERE b.layerno = 1
AND accountid IN ( 2, 5 )
AND c.layerno = 2
AND accountid IN ( 3, 6 )
That said Martin's INTERSECT approach is probably the best
Do you mean:
SELECT
TransId,
LayerNo,
AccountId
FROM Table
WHERE (LayerNo = 1 AND AccountId IN (2, 5)) OR
(LayerNo = 2 AND AccountId IN (3, 7))
create table #temp
( rowId Int Identity(1,1), transId int)
INSERT INTO #temp(transId)
select TransId
from TableName
where (layerNo = 1 and accountID IN (2, 5))
OR (layerNo = 2 and accountId IN (3, 6))
select * from #temp
SELECT
base.TransId,
base.LayerNo,
base.AccountId
FROM TableX AS base
JOIN TableX AS a
ON a.TransId = base.TransId
AND a.LayerNo = 1 AND a.AccountId IN (2, 5)
JOIN TableX AS b
ON b.TransId = base.TransId
AND b.LayerNo = 2 AND b.AccountId IN (3, 7)
WHERE (base.LayerNo = 1 AND base.AccountId IN (2, 5))
OR (base.LayerNo = 2 AND base.AccountId IN (3, 7))
This intersection is empty. If you take the values where LayerNo = 1 and LayerNo = 2 and intersect them their intersection is empty because these events are mutually exclusive. I believe this error comes from how the question was originally stated. I might be wrong but the predicate should have been
(LayerNo = 1 and (accountId = 2 or 5)) OR (LayerNo = 2 and (accountId = 3 or 6))
Replace the AND with an OR. If the predicate was stated correctly then the intersect is correct but will always be empty.
SELECT *
FROM table
WHERE (LayerNo = 1 AND (AccountID = 2 OR AccountID = 5))
OR (LayerNo = 2 AND (AccountID = 3 OR AccountID = 6))
2 tables: owners & cars
An owner can have many cars. A car can be marked as usable_offroad, usable_onroad, or both. The cars table has usable offroad and usable_onroad fields which can be set to 0 or 1 (no or yes)
Consider the following query:
SELECT *
FROM owners
LEFT JOIN cars on cars.owner_id = owners.id
GROUP BY owners.id
ORDER BY owners.last_name
My goal is to return a list of owners, and whether or not each owns a an onroad or offroad vehicle, or both:
Last Name First Name Has Offroad Car Has Onroad Car
----------------------------------------------------------------------
Smith Todd Yes No
Smith Tom Yes Yes
Test Sue No Yes
Thumb Joe No No
White Al Yes No
How do I query this? Was thinking of using ROLLUP, but would prefer if the summary wasn't an appended row but an actual field on the already grouped owner row instead.
Use:
SELECT DISTINCT
o.lastname,
o.firstname,
CASE WHEN COALESCE(y.num_offroad, 0) > 0 THEN 'yes' ELSE 'no' END AS "Has Offroad Car"
CASE WHEN COALESCE(x.num_onroad, 0) > 0 THEN 'yes' ELSE 'no' END AS "Has Onroad Car"
FROM OWNERS o
LEFT JOIN (SELECT c.owner_id,
COUNT(*) AS num_onroad
FROM CARS c
WHERE c.usable_onroad = 1
GROUP BY c.owner_id) x ON x.owner_id = o.id
LEFT JOIN (SELECT c.owner_id,
COUNT(*) AS num_offroad
FROM CARS c
WHERE c.usable_offroad = 1
GROUP BY c.owner_id) y ON y.owner_id = o.id
Try this:
SELECT
T1.lastname,
T1.firstname,
T1.id in (SELECT owner_id FROM cars WHERE usable_offroad) AS `Has Offroad Car`,
T1.id in (SELECT owner_id FROM cars WHERE usable_onroad) AS `Has Onroad Car`
FROM owners T1
ORDER BY T1.lastname, T1.firstname
Results:
'Smith', 'Todd', 1, 0
'Smith', 'Tom', 1, 1
'Test', 'Sue', 0, 1
'Thumb', 'Joe', 0, 0
'White', 'Al', 1, 0
Here's my test data:
CREATE TABLE owners (id INT NOT NULL, firstname NVARCHAR(100) NOT NULL, lastname NVARCHAR(100) NOT NULL);
INSERT INTO owners (id, firstname, lastname) VALUES
(1, 'Todd', 'Smith'),
(2, 'Tom', 'Smith'),
(3, 'Sue', 'Test'),
(4, 'Joe', 'Thumb'),
(5, 'Al', 'White');
CREATE TABLE cars (id INT NOT NULL, owner_id INT NOT NULL, usable_onroad INT NOT NULL, usable_offroad INT NOT NULL);
INSERT INTO cars (id, owner_id, usable_offroad, usable_onroad) VALUES
(1, 1, 1, 0),
(2, 2, 1, 0),
(3, 2, 0, 1),
(4, 3, 0, 1),
(5, 3, 0, 1),
(6, 5, 1, 0);
Using a sub-query to sum all the flags, and then checking if one is > 1 should do the job:
SELECT last_name, first_name,
CASE WHEN usable_offroad_count > 0 THEN 'Yes' ELSE 'No' END has_offroad_car,
CASE WHEN usable_onroad_count > 0 THEN 'Yes' ELSE 'No' END has_onroad_car
FROM (
SELECT owners.last_name, owners.first_name,
SUM( cars.usable_offroad ) usable_offroad_count,
SUM( cars.usable_onroad ) usable_onroad_count
FROM owners
LEFT JOIN cars on cars.owner_id = owners.id
GROUP BY owners.id
)
ORDER BY last_name
SELECT * ,
if ( cars.usable offroad = 1 and usable_onroad= 1 , 'Both'
, if( cars.usable offroad = 1 and usable_onroad= 0 , 'Offroad' , 'Onroad')
) as Status
FROM owners
LEFT JOIN cars on cars.owner_id = owners.id
GROUP BY owners.id
ORDER BY owners.last_name