SQL Server - Compare values from the same table - sql

In SQL Server, I have one table with following data (tblUserSettings):
| CountryID | CityID | UserType | Value1 | Value2 | Value3 |
| 9 | 3 | 1 | 5 | 5 | 5 |
| 9 | 3 | 2 | NULL | NULL | NULL |
| 9 | 3 | 3 | 5 | 5 | 5 |
| 9 | 3 | 4 | 5 | 5 | 5 |
| 9 | 20 | 1 | 5 | 5 | 5 |
| 9 | 20 | 2 | NULL | NULL | NULL |
| 9 | 20 | 3 | 5 | 5 | 5 |
| 9 | 20 | 4 | 0 | 0 | 0 |
I need to compare all the values for all UserTypes from CityID = 20 with all the values for corresponding UserTypes from CityID = 3. The CountryID = 9. The columns to compare are: Value1, Value2, Value3.
I just need to know if all of them are matched to each other or not. I tried to do something as follows:
SELECT CASE WHEN ISNULL(t1.Value1, 0) = ISNULL(t2.Value1, 0) THEN 1 ELSE 0 END AS Match1,
CASE WHEN ISNULL(t1.Value2, 0) = ISNULL(t2.Value2, 0) THEN 1 ELSE 0 END AS Match2,
CASE WHEN ISNULL(t1.Value3, 0) = ISNULL(t2.Value3, 0) THEN 1 ELSE 0 END AS Match3
FROM tblUserSettings t1
INNER JOIN tblUserSettings t2 ON t1.CountryID = t2.CountryID
AND t1.UserType = t2.UserType
AND t1.CityID = 3
AND t2.CityID = 20
WHERE t1.CountryID = 9
And it gives me following result which I have to process further to define if everything matches or not.
| Match1 | Match2 | Match3 |
| 1 | 1 | 1 |
| 1 | 1 | 1 |
| 1 | 1 | 1 |
| 0 | 0 | 0 |
Can I do this in a way to have only one column and row in output - just receive either 1 for all the matches or 0 if at least one doesn't match?

If you are looking to get only one column with 1 when all the values match and 0 if atleast one doesn't, use,
SELECT
CASE WHEN ISNULL(t1.Value1, 0) = ISNULL(t2.Value1, 0)
AND ISNULL(t1.Value2, 0) = ISNULL(t2.Value2, 0)
AND ISNULL(t1.Value3, 0) = ISNULL(t2.Value3, 0)
THEN 1 ELSE 0 END AS Match
FROM tblUserSettings t1
INNER JOIN tblUserSettings t2 ON t1.CountryID = t2.CountryID
AND t1.UserType = t2.UserType
AND t1.CityID = 3
AND t2.CityID = 20
WHERE t1.CountryID = 9

If you are looking to compare all cities rather than just two you should be able to do this by grouping rather than joining.
Something like:
SELECT
CASE WHEN
max(Value1)-min(Value1) = 0
AND max(Value2)-min(Value2) = 0
AND max(Value3)-min(Value3) = 0
THEN 1 ELSE 0 AS Match
FROM tblUserSettings
GROUP BY CountryID,UserType

Related

Generate multiple record from existing records based on interval columns [from and to]

I have 2 types of score [M,B] in column 3, if a type is M, then the score is either an S[scored] or SB[bonus scored] in column 6. Every interval [from_hrs - to_hrs] for a type B must have a corresponding SB for type M, thus, an interval for a type B cannot have a score of S for a type M. I have several records that were unfortunately captured as seen in the table below.
CREATE TABLE SCORE_TBL
(
ID int IDENTITY(1,1) PRIMARY KEY,
PERSONID_FK int NOT NULL,
S_TYPE varchar(50) NULL,
FROM_HRS int NULL,
TO_HRS int NULL,
SCORE varchar(50) NULL,
);
INSERT INTO SCORE_TBL(PERSONID_FK,S_TYPE,FROM_HRS,TO_HRS,SCORE)
VALUES
(1, 'M' , 0,20, 'S'),
(1, 'B',6, 8, 'B'),
(2, 'B',0, 2, 'B'),
(2, 'M',0,20, 'S'),
(2, 'B', 10,13, 'B'),
(2, 'B', 18,20, 'B'),
(2, 'M', 13,18, 'S');
| ID | PERSONID_FK |S_TYPE| FROM_HRS | TO_HRS | SCORE |
|----|-------------|------|----------|--------|-------|
| 1 | 1 | M | 0 | 20 | S |
| 2 | 1 | B | 6 | 8 | B |
| 3 | 2 | B | 0 | 2 | B |
| 4 | 2 | M | 0 | 20 | S |
| 5 | 2 | B | 10 | 13 | B |
| 6 | 2 | B | 18 | 20 | B |
| 7 | 2 | M | 13 | 18 | S |
I want the data to look like this
| ID | PERSONID_FK |S_TYPE| FROM_HRS | TO_HRS | SCORE |
|----|-------------|------|----------|--------|-------|
| 1 | 1 | M | 0 | 6 | S |
| 2 | 1 | M | 6 | 8 | SB |
| 3 | 1 | B | 6 | 8 | B |
| 4 | 1 | M | 8 | 20 | S |
| 5 | 2 | B | 0 | 2 | B |
| 6 | 2 | M | 0 | 2 | SB |
| 7 | 2 | M | 2 | 10 | S |
| 8 | 2 | B | 10 | 13 | B |
| 9 | 2 | M | 10 | 13 | SB |
| 10 | 2 | M | 13 | 18 | S |
| 11 | 2 | B | 18 | 20 | B |
| 12 | 2 | S | 18 | 20 | SB |
Any ideas on how to generate this data in SQL Server select statement? Visually, this what am trying to get.
Tricky part here is that interval might need to be split in several pieces like 0..20 for person 2.
Window functions to the rescue. This query illustrates what you need to do:
WITH
deltas AS (
SELECT personid_fk, hrs, sum(delta_s) as delta_s, sum(delta_b) as delta_b
FROM (SELECT personid_fk, from_hrs as hrs,
case when score = 'S' then 1 else 0 end as delta_s,
case when score = 'B' then 1 else 0 end as delta_b
FROM score_tbl
UNION ALL
SELECT personid_fk, to_hrs as hrs,
case when score = 'S' then -1 else 0 end as delta_s,
case when score = 'B' then -1 else 0 end as delta_b
FROM score_tbl) _
GROUP BY personid_fk, hrs
),
running AS (
SELECT personid_fk, hrs as from_hrs,
lead(hrs) over (partition by personid_fk order by hrs) as to_hrs,
sum(delta_s) over (partition by personid_fk order by hrs) running_s,
sum(delta_b) over (partition by personid_fk order by hrs) running_b
FROM deltas
)
SELECT personid_fk, 'M' as s_type, from_hrs, to_hrs,
case when running_b > 0 then 'SB' else 'S' end as score
FROM running
WHERE running_s > 0
UNION ALL
SELECT personid_fk, s_type, from_hrs, to_hrs, score
FROM score_tbl
WHERE s_type = 'B'
ORDER BY personid_fk, from_hrs;
Step by step:
deltas is union of two passes on score_tbl - one for start and one for end of score/bonus interval, creating a timeline of +1/-1 events
running calculates running total of deltas over time, yielding split intervals where score/bonus are active
final query just converts score codes and unions bonus intervals (which are passed unchanged)
SQL Fiddle here.

Identify the second max value in hive based on condition

I have a table with rows that looks like that with a column that rank all rows partition by ticket id over timestamp desc.
All rows can only have one flag equal to one.
ticketID | flag 1 | flag 2 | flag 3 | flag 4 | Timestamp | Rank | stringvalue |
----------------------------------------------------------------------------------------|
1 | 0 | 0 | 1 | 0 | xxxxxx | 2 | aaaaaa |
1 | 0 | 0 | 0 | 1 | xxxxxx | 1 | bbbbbb |
1 | 0 | 1 | 0 | 0 | xxxxxx | 3 | aaaaaa |
2 | 1 | 0 | 0 | 0 | xxxxxx | 2 | bbbbbb |
2 | 0 | 0 | 0 | 1 | xxxxxx | 1 | xxxxxx |
3 | 0 | 0 | 1 | 0 | xxxxxx | 4 | aaaaaa |
3 | 0 | 1 | 0 | 0 | xxxxxx | 3 | bbbbbb |
3 | 1 | 0 | 0 | 0 | xxxxxx | 1 | ssssss |
3 | 0 | 0 | 0 | 1 | xxxxxx | 2 | nnnnnn |
4 | 0 | 1 | 0 | 0 | xxxxxx | 2 | gggggg |
4 | 0 | 0 | 0 | 1 | xxxxxx | 1 | iiiiii |
for each ticketID i need to get the first row based on the rank but with a an exception for a specific flag :
when the rank 1 of a ticket is a row with flag 4 = 1 then i need to take the second rank position as the first one.
And if the second rank of the ticket is flag 3 = 1 then i need to concatenate stringvalue from the first rank (flag = 4) with the second rank (flag = 3).
If the second rank is flag = 1 or flag = 2 then just forget about the first rank and return the second one as the first.
I hope that my question is clear.
Thanks
Edit
Sample output :
----------------------------------------------------------------------------------------
ticketID | flag 1 | flag 2 | flag 3 | Timestamp | Rank | stringvalue |
---------------------------------------------------------------------------------------|
1 | 0 | 0 | 1 | xxxxxx | 1 | aaaaaa / bbbbbbb |
2 | 1 | 0 | 0 | xxxxxx | 1 | bbbbbb |
3 | 1 | 0 | 0 | xxxxxx | 1 | ssssss |
4 | 0 | 1 | 0 | xxxxxx | 1 | gggggg |
----------------------------------------------------------------------------------------
I'm going to use some sub-queries with a struct group by. This will allow us to ask questions about multiple rows without using a window. Likely will perform faster as we don't have to maintain window state.
create table theRanks (ticketID int, flag_1 int, flag_2 int, flag_3 int, flag_4 int, Timestamp string, Rank int, stringvalue string)
-- create some dummy data
insert into theRanks values ( 1 , 0, 0, 1, 0, 'xxxxxx', 2, 'aaaaaa')
insert into theRanks values ( 1 , 0, 0, 0, 1, 'xxxxxx', 1, 'bbbbbb')
insert into theRanks values ( 1 , 0, 1, 0, 0, 'xxxxxx', 3, 'aaaaaa')
with stuct_table as -- sub-query syntax
(
select
ticketID,
struct( -- struct will allow us to group rows together.
Rank as rawRank, -- this has to be first in strut as we use it for sorting
flag_1 ,
flag_2,
flag_3,
flag_4 ,
Timestamp ,
stringvalue
) as myRow
from
theRanks
where
rank in (1,2) -- only look at first two ranks
),
constants as -- subquery
(
select 0 as rank1, 1 as rank2 -- strictly not needed just to help make it more readable
),
grouped_rows as --subquery
(
select
ticketID,
array_sort(collect_list(myRow)) as row_list -- will sort on rank all structs into a list
from stuct_table
group by ticketID
) ,
raw_rows as (select --sub-query styntax
ticketId,
case
when
row_list[constants.rank2].flag_1 + row_list[constants.rank2].flag_2 > 0 or (row_list[constants.rank1].flag_4 = 1 and row_list[constants.rank2].flag_3 = 0 )
then
row_list[constants.rank2]
when
row_list[constants.rank1].flag_4 = 1 and row_list[constants.rank2].flag_3 = 1 -- condition to concat string
then
struct( -- this struct must match the original one we created
row_list[constants.rank2].rawRank as rawRank,
row_list[constants.rank2].flag_1 as flag_1,
row_list[constants.rank2].flag_2 as flag_2,
row_list[constants.rank2].flag_3 as flag_3,
row_list[constants.rank2].flag_4 as flag_4,
row_list[constants.rank2].Timestamp as Timestamp,
concat(
row_list[constants.rank1].stringvalue,
' / ',
row_list[constants.rank2].stringvalue) as stringvalue
)
else
row_list[constants.rank1]
end as rankedRow,
1 as Rank
from grouped_rows
cross join constants) -- not strictly needed, just replace all constants.rank1 with 0 and constants.rank2 with 1. I just use it to make it more clear what I'm doing. Could be replaced in production.
select rankedRow.* , 1 as Rank from raw_rows; -- makes struct columns into table columns

How to combine data from 2 tables -- which join, what conditions?

Consider the following 2 tables.
TableDE
ID country key1 key2
------------------------
1 US 1 null
1 US 1 null
1 US 1 null
2 US null null
3 US 1 1
4 DE 1 1
5 DE null null
5 DE null null
TableUS
ID key1 key2
--------------
1 null null
2 null 1
4 1 1
8 null 1
2 null 1
2 null 1
9 1 null
I need a distinct overview of all IDs, combining data from both tables:
ID inTableDe country DEkey1 DEkey2 inTableUS USkey1 USKey2
-----------------------------------------------------------------
1 1 US 1 0 1 0 0
2 1 US 0 0 1 0 1
3 1 US 1 1 0 0 0
4 1 DE 1 1 1 1 1
5 1 DE 0 0 0 0 0
8 0 0 0 1 1 0 1
9 0 0 0 1 1 1 0
I hope it speaks for itself:
ID 8 and ID 9 have 0 in the first column bc they aren't in tableDE
ID 8 and ID 9 have 0 in the country column bc this field doesn't exist in tableUS
ID 3 has 0 in inTableUS bc it only exists in tableDE
the key values are copied from the original tables
an ID is not unique: it can appear many times in both tables. However: the values for key1 and key2 will always be the same for each ID within the same table.
I have been messing for hours now with this; I have this now:
select de.[ID],
de.[country],
case when (de.[ID] in (select distinct [ID] from [tableDE]) then 1 else 0 end as [inTableDE],
case when (de.[ID] in (select distinct [ID] from [tableUS]) then 1 else 0 end as [inTableUS],
de.[key1] as [DEKey1],
de.[key2] as [DEKey2],
us.[key1] as [USKey1],
us.[key2] as [USKey2],
from dbo.[tableDE] de
full outer join dbo.[tableUS] us on de.[ID] = us.[ID]
where de.[country] = 'US'
and (de.[key1] = 1 or de.[key2] = 1 or us.[key1] = 1 or us.[key2] = 1)
group by de.[ID], us.[ID]
But this keeps giving me only values that are in both tables.
What am I doing wrong?
You sem to want aggregation on top of the full join:
select
coalesce(de.id, us.id) as id,
case when de.id is null then 0 else 1 end as intablede,
max(de.country) as country,
coalesce(max(de.key1), 0) as dekey1,
coalesce(max(de.key2), 0) as dekey2,
case when us.id is null then 0 else 1 end as intableus,
coalesce(max(us.key1), 0) as uskey1,
coalesce(max(us.key2), 0) as uskey2
from dbo.tablede de
full join dbo.tableus us on de.id = us.id
group by de.id, us.id
order by id
Demo on DB Fiddle:
id | intablede | country | dekey1 | dekey2 | intableus | uskey1 | uskey2
-: | --------: | :------ | -----: | -----: | --------: | -----: | -----:
1 | 1 | US | 1 | 0 | 1 | 0 | 0
2 | 1 | US | 0 | 0 | 1 | 0 | 1
3 | 1 | US | 1 | 1 | 0 | 0 | 0
4 | 1 | DE | 1 | 1 | 1 | 1 | 1
5 | 1 | DE | 0 | 0 | 0 | 0 | 0
8 | 0 | null | 0 | 0 | 1 | 0 | 1
9 | 0 | null | 0 | 0 | 1 | 1 | 0

SQL SELECT GROUP BY and join the row with certain value

I have two tables:
tbl_car
id_c | name car
1 | VW
2 | Audi
3 | Ferrari
tbl_auto_accessorize
id_a | id_c | fuel | gpl | abs
1 | 1 | 0 | 1 | 0
2 | 1 | 1 | 0 | 1
3 | 2 | 0 | 1 | 1
4 | 2 | 1 | 0 | 1
5 | 3 | 0 | 1 | 0
I have this SQL:
SELECT id_a,id_c,abs
FROM tbl_car LEFT JOIN
tbl_auto_accessorize
ON tbl_accessorize.id_c = tbl_auto.id_c
GROUP BY id_c
the return in column abs is 0 because the first row is 0 but the same id_c have another row with 1.
I need a result equal to 1 because at least one is 1 of the same car.
Result:
id_c | abs
1 | 1
2 | 1
3 | 0
I think you might want max():
SELECT c.id_c, MAX(aa.abs)
FROM tbl_car c LEFT JOIN
tbl_auto_accessorize aa
ON aa.id_c = c.id_c
GROUP BY c.id_c;
As a rule, all the columns in an aggregation query should either be in the GROUP BY or be the arguments to aggregation functions. That is why I removed id_a.

Toggle bit for a series of rows based on single row in SQLite

I have a table containing rows which are either 'headers' or 'normal', non-header entries. This is tracked by an INTEGER affinity column IsHeader.
Likewise, I have a column tracking if the row is 'Active'.
With a table 'Entries', and another column 'MCL_Row' used to find relevant rows, I can toggle the value of 'Active' using
UPDATE Entries SET(Active) =
(SELECT (~(Active&1))&(Active|1) WHERE MCL_Row = <target>)
WHERE MCL_Row = <target>;
This works, but if I want to toggle an entire group on or off based on the header, I can't use
UPDATE Entries SET(Active) =
(SELECT (~(Active&1))&(Active|1) WHERE S_Type = <typenum> AND IsHeader=1)
WHERE S_Type = <typenum>;
because here, the SELECT subquery returns the one value I want, but multiple rows are updated. As a result, the first row gets the correct result, and subsequent rows satisfying the WHERE S_Type = <typenum> clause are updated with a NULL value.
How can I use the value returned by this subclause to set the values (identically) of multiple rows used by the UPDATE statement?
Edit: Perhaps the question was a little unclear originally, so adding some example before/after data.
Before:
MCL_Row S_Type Active IsHeader
1 1 1 1
2 1 1 0
3 1 0 0
4 2 1 1
5 2 1 0
6 2 1 0
After setting S_Type=1 active via header:
MCL_Row S_Type Active IsHeader
1 1 1 1
2 1 1 0
3 1 >1< 0
4 2 1 1
5 2 1 0
6 2 1 0
After setting S_Type=1 inactive via header:
MCL_Row S_Type Active IsHeader
1 1 >0< 1
2 1 >0< 0
3 1 0 0
4 2 1 1
5 2 1 0
6 2 1 0
1st query
UPDATE Entries
SET Active = 1-Active
WHERE MCL_Row = <target>
;
2nd query
UPDATE Entries
SET Active = (select 1-h.Active
from Entries as h
where h.S_Type = Entries.S_Type
and h.IsHeader = 1
)
WHERE S_Type = <typenum>
Demo
create table Entries (MCL_Row int,S_Type int,IsHeader int,active int);
insert into Entries (MCL_Row,S_Type,IsHeader,active) values
(1,123,1,1)
,(2,123,0,0)
,(3,123,0,0)
,(4,123,0,1)
;
select * from Entries;
+---------+--------+----------+--------+
| MCL_Row | S_Type | IsHeader | active |
+---------+--------+----------+--------+
| 1 | 123 | 1 | 1 |
+---------+--------+----------+--------+
| 2 | 123 | 0 | 0 |
+---------+--------+----------+--------+
| 3 | 123 | 0 | 0 |
+---------+--------+----------+--------+
| 4 | 123 | 0 | 1 |
+---------+--------+----------+--------+
UPDATE Entries
SET Active = (select 1-h.Active
from Entries as h
where h.IsHeader = 1
and h.S_Type = Entries.S_Type
)
WHERE S_Type = 123
;
select * from Entries;
+---------+--------+----------+--------+
| MCL_Row | S_Type | IsHeader | active |
+---------+--------+----------+--------+
| 1 | 123 | 1 | 0 |
+---------+--------+----------+--------+
| 2 | 123 | 0 | 1 |
+---------+--------+----------+--------+
| 3 | 123 | 0 | 1 |
+---------+--------+----------+--------+
| 4 | 123 | 0 | 1 |
+---------+--------+----------+--------+
UPDATE Entries
SET Active = (select 1-h.Active
from Entries as h
where h.IsHeader = 1
and h.S_Type = Entries.S_Type
)
WHERE S_Type = 123
;
select * from Entries;
+---------+--------+----------+--------+
| MCL_Row | S_Type | IsHeader | active |
+---------+--------+----------+--------+
| 1 | 123 | 1 | 1 |
+---------+--------+----------+--------+
| 2 | 123 | 0 | 0 |
+---------+--------+----------+--------+
| 3 | 123 | 0 | 0 |
+---------+--------+----------+--------+
| 4 | 123 | 0 | 0 |
+---------+--------+----------+--------+