select true/false based on col value in a group by - sql-server

Sorry if the title is not clear. I have a simple table T1
counter int, not null
type nvarchar(250), not null
name nvarchar(50), not null
I'm summing up the counters grouped by type, Like this:
select sum(counter), type
from T1
group by type;
I want to select one more field which is boolean (true/false) which is any of the names contains a specific text i.e. if name like '%Bassem%' then select true. But I can not figure it out since I'm using group by.

Here's a way to do this.
First create a test table and insert some values:
CREATE TABLE dbo.T
(
[counter] int not null,
[type] nvarchar(250) not null,
[name] nvarchar(50) not null
);
INSERT INTO dbo.T ([counter], [type], [name])
VALUES (1, N'Alpha', N'Bassem Akl'),
(2, N'Alpha', N'aaaaa'),
(3, N'Alpha', N'Akl Bassem'),
(4, N'Bravo', N'bbbbb'),
(5, N'Bravo', N'A Bassem'),
(6, N'Charlie', N'ccccc'),
(7, N'Charlie', N'ddddd');
Then use a CTE (common table expression) to determine if the name contains the text you are searching for. You don't have to use a CTE here, but it makes the overall SELECT statement easier to understand.
WITH cte AS
(
SELECT [counter], [type], IIF([name] LIKE N'%Bassem%', 1, 0) AS 'contains'
FROM dbo.T
)
SELECT SUM([counter]) AS 'SumCounter', [type], CAST(MAX([contains]) AS bit) as 'contains'
FROM cte
GROUP BY [type];
Note that Transact-SQL doesn't have a Boolean data type; instead it has a bit type. See Books Online > bit (Transact-SQL) -- https://msdn.microsoft.com/en-gb/library/ms177603.aspx

select sum(counter), type,
max(case when name like '%value%' then true else false end ) as 'Booleanvalue'
from T1
group by type;
you can do this as well,but you may want to do a distinct later using cte or derived table
select sum(counter) over (partition by type order by type),
type,
case when name like '%value%' then true else false end as 'Booleanvalue'
from table

WITH cte as
(
SELECT counter, type, IIF(name like '%Bassem%', 1, 0) as b
FROM #t
)
select sum(counter), type, CAST(max(b) as bit)
from cte
group by type;

A simple CASE statement gets it done.
SELECT SUM(foo.counter)
, type
, CASE WHEN foo.type = 'type a' THEN 1 ELSE 0 END AS true_false
FROM (VALUES (1, 'type a'), (2, 'type b')) AS foo(counter, type)
GROUP BY type;

Related

T-SQL - Customer Linking

Please run the below code, these are all the same Customer because 2 of them have the same TaxNumber while another one matches one based on CompanyName. I need to link them all and set the ParentCompanyID based on who was created first. I am struggling to get them linked.
CREATE TABLE #Temp
(
CustomerID INT,
CustomerName VARCHAR(20),
CustomerTaxNumber INT,
CreatedDate DATE
)
INSERT INTO #Temp
VALUES (8, 'Company PTY',1234, '2019-09-20'),
(2, 'Company PT', 1234, '2019-09-24'),
(3, 'Company PTY',NULL, '2019-09-29')
SELECT * FROM #Temp
Below is the result that I require....
Any help will be appreciated.
Using case expression with first_value can give you the desired results:
SELECT CustomerID, CustomerName, CustomerTaxNumber, CreatedDate,
CASE WHEN CustomerTaxNumber IS NULL THEN
FIRST_VALUE(CustomerID) OVER(PARTITION BY CustomerName ORDER BY CreatedDate)
ELSE
FIRST_VALUE(CustomerID) OVER(PARTITION BY CustomerTaxNumber ORDER BY CreatedDate)
END As ParentCompanyID
FROM #Temp
Try this:
CREATE TABLE #Temp
(
CustomerID INT,
CustomerName VARCHAR(20),
CustomerTaxNumber INT,
CreatedDate DATE
)
INSERT INTO #Temp
VALUES (8, 'Company PTY',1234, '2019-09-20'),
(2, 'Company PT', 1234, '2019-09-24'),
(3, 'Company PTY',NULL, '2019-09-29')
SELECT DS.[CreatedDate] AS [FirstEntry]
,DS.[CustomerID] AS [ParentCompanyID]
,#Temp.*
FROM #Temp
CROSS APPLY
(
SELECT TOP 1 *
FROM #Temp
ORDER BY CreatedDate
) DS
DROP TABLE #Temp
You are condition is pretty simple - get the first record. If you need to group the records in some way, you can add additional filtering in the CROSS APPLY clause.

Comparing Results From Two Different Queries Based on Common Field

Ok, so I just started working with SQL so please be easy on me if there's a super easy solution that I just don't know. I'm using Microsoft SQL Server. I have two queries that give me results, one being
EXEC ('SELECT ID ,
Type,
Date
FROM DB2T.BBT') AT DB2
and the other being
select
ca.value('(/CA[#name=''ID'']/#value)[1]','VARCHAR(MAX)') as ID,
ca.value('(/CA[#name=''Type'']/#value)[1]', 'VARCHAR(MAX)') as Type,
ca.value('(/CA[#name=''Date'']/#value)[1]', 'VARCHAR(MAX)') as Date,
from log
This is just how I extract the relevant data from the places I need. I end up with two different queries with two different table outputs, each row containing an ID, Type, and Date.
I need to combine these two queries so I can compare the two tables. I need to see, depending on matching IDs from the two queries, if the values for Type and Date are equal. I only want to output IDs that have differing values and then output the differing values with it.
So I have two problems I guess, one being combining the two queries and then doing the comparing. Thanks in advance.
This query will combine the resultsets into one, then group by the ID, Type, and Date columns and pull back which ones don't have two rows (should have one from DB2 and one from SQL). It doesn't tell you exactly what is different but should be pretty easy to tell with that few columns.
SELECT
MIN(System) AS System,
ID,
Type,
Date
FROM (
select
'SQL',
ca.value('(/CA[#name=''ID'']/#value)[1]','VARCHAR(MAX)') as ID,
ca.value('(/CA[#name=''Type'']/#value)[1]', 'VARCHAR(MAX)') as Type,
ca.value('(/CA[#name=''Date'']/#value)[1]', 'VARCHAR(MAX)') as Date,
from log
UNION ALL
SELECT 'DB2', * FROM OPENQUERY([DB2], ''SELECT ID, Type, Date FROM DB2T.BBT'')
) compare
GROUP BY ID, Type, Date
HAVING COUNT(*) <> 2
ORDER BY ID, Type, Date
Basically, break it down into 2 queries. The first one is qry1, the second is qry2. You want to use an inner join, because you only want values where Type and Date are equal. Then, you use a WHERE clause to give you only the records where ID doesn't match.
You may have to put ID in brackets, I can't remember off the top of my head if that's a reserved word or not. I know both Date and Type are reserved words, and that's why I put them in brackets.
SELECT
qry1.ID as ID1,
qry1.[Type] as Type1,
qry1.[Date] as Date1,
qry2.ID as ID2,
qry2.[Type] as Type2,
qry2.[Date] as Date2
FROM
(SELECT ID, Type, Date
FROM DB2T.BBT) as qry1,
INNER JOIN
(select
ca.value('(/CA[#name=''ID'']/#value)[1]','VARCHAR(MAX)') as ID,
ca.value('(/CA[#name=''Type'']/#value)[1]', 'VARCHAR(MAX)') as Type,
ca.value('(/CA[#name=''Date'']/#value)[1]', 'VARCHAR(MAX)') as Date,
from log) as qry2
ON qry1.[Type] = qry2.[Type]
AND qry1.[Date] = qry2.[Date]
WHERE qry1.ID <> qry2.ID
What about comparing computed columns that are hashes of the concatenated column values? Something like this:
declare #t1 table (id int, type varchar(max), dt date, hash_bytes as HASHBYTES('SHA1', CAST(id AS NVARCHAR(MAX)) + CAST(type AS NVARCHAR(MAX)) + CAST(dt AS NVARCHAR(MAX))))
declare #t2 table (id int, type varchar(max), dt date, hash_bytes as HASHBYTES('SHA1', CAST(id AS NVARCHAR(MAX)) + CAST(type AS NVARCHAR(MAX)) + CAST(dt AS NVARCHAR(MAX))))
insert into #t1 values
(1, 'val1', getdate()), -- no match in #t2
(2, 'val2', getdate() + 1),
(3, 'val3', getdate() + 2),
(4, 'val4', getdate() + 3),
(5, 'val5', getdate() + 4)
insert into #t2 values
(2, 'val2', getdate() + 1), -- same
(3, 'val300', getdate() + 2), -- different type
(4, 'val4', getdate() + 300), -- different date
(5, 'val500', getdate() + 400),-- different type & date
(6, 'val6', getdate() + 5) -- no match in #t1
select *
from #t1 t1
full join #t2 t2 on t1.hash_bytes = t2.hash_bytes
id type dt hash_bytes id type dt hash_bytes
1 val1 2018-07-27 0xF53D672F572DC49D15AE2ECD2F3225624073FEB8 NULL NULL NULL NULL
2 val2 2018-07-28 0x8840035CC198447CB1F9D85E97A57F2B08ADB39E 2 val2 2018-07-28 0x8840035CC198447CB1F9D85E97A57F2B08ADB39E
3 val3 2018-07-29 0x372E6A3B48C3C96C2456A514CD9D35CAC4EEEACE NULL NULL NULL NULL
4 val4 2018-07-30 0xE91A2E58D2964BB3BE6BDD1C1ECA3628E956484D NULL NULL NULL NULL
5 val5 2018-07-31 0xB289831856A15334BE60EC4F78502052B15EE4CD NULL NULL NULL NULL
NULL NULL NULL NULL 3 val300 2018-07-29 0x2007D7205352EE65013DC21E527780E1FED763D8
NULL NULL NULL NULL 4 val4 2019-05-23 0x60CC2C7B3902204E82F137401446EB974EC83C3B
NULL NULL NULL NULL 5 val500 2019-08-31 0xFFF8FD045B306B3F1663FC4903CE859A6C9577FB
NULL NULL NULL NULL 6 val6 2018-08-01 0x72407548472D00C87E6DDF42A05E0B1B687AACBA

Select Duplicate Records

I want to retrieve only Duplicated records not unique records.
Suppose I have data which consists of as below
Ids Names
1 A
2 B
1 A
I want like output like the following:
Sno Id Name
1 1 A
2 1 A
Try this:
DECLARE #DataSource TABLE
(
[ID] INT
,[name] CHAR(1)
,[value] CHAR(2)
);
INSERT INTO #DataSource ([ID], [name], [value])
VALUES (1, 'A', 'x1')
,(2, 'B', 'x2')
,(1, 'A', 'x3');
WITH DataSource AS
(
SELECT *
,COUNT(*) OVER (PARTITION BY [ID], [name]) AS [Count]
FROM #DataSource
)
SELECT *
FROM Datasource
WHERE [Count] > 1;
The grouping part is done in the PARTITION BY part of the window function. So, basically, we are counting records for each unique ID - name pairs. Of couse, you are able to add more columns columns here.
SELECT Id, Names
FROM T
GROUP BY Id,Name
HAVING COUNT(*) >1
like your request, you need to create a new column [SNo] that is partitioned on the orignal columns (Names, Id). Those with [SNo] >1 are duplicates. To Filter, just get RCount>1.
See a mockup below:
DECLARE #Records TABLE (Id int, Names VARCHAR(10))
INSERT INTO #Records
SELECT 1, 'A' UNION ALL
SELECT 2, 'B' UNION ALL
SELECT 1, 'A'
----To Get Duplicates -----
SELECT *
FROM
(
SELECT
SNo=ROW_NUMBER()over(PARTITION BY Names,Id order by Id),
RCount=COUNT(*) OVER (PARTITION BY [ID], Names),
*
FROM
#Records
)M
WHERE
RCount>1

SQLServer : Grouping and Replacing a COLUMN value with the DATA from other table, without UDF

I would like to replace the numbers in #CommentsTable column "Comments" with the equivalent text from #ModTable table, without using UDF in a single SELECT. May with a CTE. Tried STUFF with REPLACE, but no luck.
Any suggestions would be a great help!
Sample:
DECLARE #ModTable TABLE
(
ID INT,
ModName VARCHAR(10),
ModPos VARCHAR(10)
)
DECLARE #CommentsTable TABLE
(
ID INT,
Comments VARCHAR(100)
)
INSERT INTO #CommentsTable
VALUES (1, 'MyFirst 5 Comments with 6'),
(2, 'MySecond comments'),
(3, 'MyThird comments 5')
INSERT INTO #ModTABLE
VALUES (1, '[FIVE]', '5'),
(1, '[SIX]', '6'),
(1, '[ONE]', '1'),
(1, '[TWO]', '2')
SELECT T1.ID, <<REPLACED COMMENTS>>
FROM #CommentsTable T1
GROUP BY T1.ID, T1.Comments
**Expected Result:**
ID Comments
1 MyFirst [FIVE] Comments with [SIX]
2 MySecond comments
3 MyThird comments [FIVE]
Create a cursor, span over the #ModTable and do each replacement a time
DECLARE replcursor FOR SELECT ModPos, ModName FROM #ModTable;
OPEN replcursor;
DECLARE modpos varchar(100) DEFAULT "";
DECLARE modname varchar(100) DEFAULT "";
get_loop: LOOP
FETCH replcursor INTO #modpos, #modname
SELECT T1.ID, REPLACE(T1.Comments, #modpos, #modname)
FROM #CommentsTable T1
GROUP BY T1.ID, T1.Comments
END LOOP get_loop;
Of course, you can store the results in a temp table and get the results altogether in the end of loop.
You can use a while loop to iterate over the records and the mods. I slightly modified your #ModTable to have unique values for ID. If this is not your data structure, then you can use a window function like ROW_NUMBER() to get a unique value over which you can iterate.
Revised script example:
DECLARE #ModTable TABLE
(
ID INT,
ModName VARCHAR(10),
ModPos VARCHAR(10)
)
DECLARE #CommentsTable TABLE
(
ID INT,
Comments VARCHAR(100)
)
INSERT INTO #CommentsTable
VALUES (1, 'MyFirst 5 Comments with 6'),
(2, 'MySecond comments'),
(3, 'MyThird comments 5')
INSERT INTO #ModTABLE
VALUES (1, '[FIVE]', '5'),
(2, '[SIX]', '6'),
(3, '[ONE]', '1'),
(4, '[TWO]', '2')
declare #revisedTable table (id int, comments varchar(100))
declare #modcount int = (select count(*) from #ModTable)
declare #commentcount int = (select count(*) from #CommentsTable)
declare #currentcomment varchar(100) = ''
while #commentcount > 0
begin
set #modcount = (select count(*) from #ModTable)
set #currentcomment = (select Comments from #CommentsTable where ID = #commentcount)
while #modcount > 0
begin
set #currentcomment = REPLACE( #currentcomment,
(SELECT TOP 1 ModPos FROM #ModTable WHERE ID = #modcount),
(SELECT TOP 1 ModName FROM #ModTable WHERE ID = #modcount))
set #modcount = #modcount - 1
end
INSERT INTO #revisedTable (id, comments)
SELECT #commentcount, #currentcomment
set #commentcount = #commentcount - 1
end
SELECT *
FROM #revisedTable
order by id
I think the will work even though I generally avoid recursive queries. It assumes that you have consecutive ids though:
with Comments as
(
select ID, Comments, 0 as ConnectID
from #CommentsTable
union all
select ID, replace(c.Comments, m.ModPos, m.ModName), m.ConnectID
from Comments c inner join #ModTable m on m.ConnectID = c.ConnectID + 1
)
select * from Comments
where ConnectID = (select max(ID) from #ModTable)
=> CLR Function()
As I have lot of records in "CommentsTable" and the "ModTable" would have multiple ModName for each comments, finally decided to go with CLR Function. Thanks all of you for the suggestions and pointers.

Creating contra values in the same column SQL

Aim to write a statement to produce a table with a numeric column that contains contra values, e.g.
Ref Value
a 100
b 75
c 50
c -50
b -75
a -100
I am new to SQL but aware it works row by row, so the only way I could think of doing this is write an initial SELECT statement into a temporary table and INSERT into my temporary table with the contra values, i.e.
SELECT
[Ref],
[Value]
INTO #Temp
FROM
mytable
INSERT INTO #Temp ([Ref], [Value])
SELECT
[Ref],
0 - [Value]
FROM
mytable
While this 'does the job' I fear it is 'messy' (could possibly cause problems when used for its intended purpose) and wondered if anyone would be able to provide a better solution.
Use Union ALL to combine the original and negative values. Then insert into temp table. Try this.
SELECT [Ref],
[Value]
INTO #Temp
FROM (SELECT [Ref],
[Value]
FROM mytable
UNION ALL
SELECT [Ref],
[Value] * -1
FROM mytable) a
If you just want to view the result remove the into #temp table
SELECT [Ref],
[Value]
FROM (SELECT [Ref],
[Value]
FROM mytable
UNION ALL
SELECT [Ref],
[Value] * -1
FROM mytable) a

Resources