How to group by with NULL values - sql-server

What would be the simplest way to group by when NULL values?
declare #MyTable Table (ID int, Name varchar(50),Coverage varchar(50), Premium money)
insert into #MyTable values (1,'Robert', 'AutoBI', 100),
(1,'Robert', NULL, 300),
(2,'Neill','AutoBIPD',150),
(2,'Neill','AutoBI',200),
(3,'Kim', 'Collision',50),
(3,'Kim',NULL,100),
(4,'Rick','AutoBI',70),
(5,'Lukasz','Comprehensive',50),
(5,'Lukasz','NULL',25)
select ID,
Name,
Coverage,
sum(Premium) as Premium
from #MyTable
group by ID
,Name
,Premium
,Coverage
The outcome looks like this:
As you can see there is NULL value for name 'Robert'.
How can I have summed premium ($400) and only one line without NULL Coverage?
But I need to make it look like this:
I cannot use MAX() function in this case.

This solution assumes that NULL will be grouped to one "random" NOT NULL value within ID/Name. If more than single value is poissible then this query won't return stable result sets between executions:
select ID,
Name,
ISNULL(m1.Coverage, sub.Coverage) AS Coverage,
sum(Premium) as Premium
FROM #MyTable m1
cross apply (SELECT TOP 1 m2.Coverage FROM #MyTable m2 WHERE Coverage IS NOT NULL
AND m1.ID = m2.ID AND m1.Name = m2.Name) sub
group by ID
,Name
,ISNULL(m1.Coverage, sub.Coverage);
Rextester Demo

Related

SELECT THAT ALSO SET TO VARIABLE

I just got a job to a company to maintain an existing program in the company.
Now I need to get a data from table.
So I need to do a select from a table, the table column dateBegin maybe null maybe not, so if datebegin is null i get the datebegin2. and dateto is added 1 year from the datebegin if is null or <= datebegin.
All I can do now is using case when else to get the right value. But its too long for the code and hard to read. How can I achieve the code right below? thanks for your response...
declare #val smalldatetime
select id
, #val = COALESCE(dateBegin, dateBegin2) as DateBegin
, COALESCE(dateTo, dateadd(Y, 1, #val)) As DateTo
from TblL
You cannot mix this, but you can use APPLY to row-wise compute additional columns. These columns can be used within the statement similar to a variable:
Cannot test this, but this should be equivalent to your attempt:
select id
, A.DateBegin
, COALESCE(dateTo, dateadd(Y, 1, A.DateBegin)) As DateTo
from TblL
CROSS APPLY(SELECT COALESCE(dateBegin, dateBegin2)) AS A(DateBegin)
A working example to illustrate the idea
DECLARE #tbl TABLE(ID INT IDENTITY, SomeString VARCHAR(500));
INSERT INTO #tbl VALUES('Find the #value# between the "#"')
,('One more #example#');
SELECT t.ID
,t.SomeString
,A.FirstHash
,B.SecondHash
,SUBSTRING(t.SomeString,A.FirstHash+1,C.FragmentLength) AS Fragment
FROM #tbl AS t
CROSS APPLY(SELECT CHARINDEX('#',t.SomeString)) AS A(FirstHash)
CROSS APPLY(SELECT CHARINDEX('#',t.SomeString,A.FirstHash+1)) AS B(SecondHash)
CROSS APPLY(SELECT B.SecondHash-A.FirstHash-1) AS C(FragmentLength);
Returns
ID SomeString 1.# 2.# Fragment
1 Find the #value# between the "#" 10 16 value
2 One more #example# 10 18 example
The same query without this trick would be something like this
SELECT t.ID
,t.SomeString
,CHARINDEX('#',t.SomeString)
,CHARINDEX('#',t.SomeString,CHARINDEX('#',t.SomeString)+1)
,SUBSTRING(t.SomeString,CHARINDEX('#',t.SomeString)+1,CHARINDEX('#',t.SomeString,CHARINDEX('#',t.SomeString)+1)-CHARINDEX('#',t.SomeString)-1) AS Fragment
FROM #tbl AS t

SQL combine multiple records into one row

I have a table pulling userid's and their personal and work e-mails. I'd like to have one line per id showing both types of e-mails, but I can't figure out how to do that.
declare #t table(NPI int, email varchar(50), emailtype varchar(50))
insert into #t
values(1, 'john#home', 'personal'), (1, 'john#work', 'work');
This is the query I've written so far, which puts this on 2 separate rows:
select npi, case when emailtype = 'personal' then email end as personalemail,
case when emailtype = 'work' then email end as workemail
from #t;
Current Output:
npi personalemail workemail
1 john#home NULL
1 NULL john#work
What I'd like to see is:
npi personalemail workemail
1 john#home john#work
How can I do this?
This has been asked and answered around here about a million times. It is called conditional aggregation or crosstab. It is faster to write an answer than find one. As an alternative you could use PIVOT but I find the syntax a bit obtuse for me.
select NPI
, max(case when emailtype = 'personal' then email end) as PersonalEmail
, max(case when emailtype = 'work' then email end) as WorkEmail
from #t
group by NPI
Use pivot
SELECT
*
FROM #T
PIVOT
(
MAX(email)
FOR EmailType IN
(
personal,work
)
)Q

rSQL While Loop insert

*Updated - Please see below(Past the picture)
I am really stuck with this particular problem, I have two tables, Projects and Project Allocations, they are joined by the Project ID.
My goal is to populate a modified projects table's columns using the rows of the project allocations table. I've included an image below to illustrate what I'm trying to achieve.
A project can have up to 6 Project Allocations. Each Project Allocation has an Auto increment ID (Allocation ID) but I can't use this ID in a sub-selects because it isn't in a range of 1-6 so I can distinguish between who is the first PA2 and who is PA3.
Example:
(SELECT pa1.name FROM table where project.projectid = project_allocations.projectid and JVID = '1') as [PA1 Name],
(SELECT pa2.name FROM table where project.projectid = project_allocations.projectid and JVID = '1') as [PA2 Name],
The modified Projects table has columns for PA1, PA2, PA3. I need to populate these columns based on the project allocations table. So the first record in the database FOR EACH project will be PA1.
I've put together an SQL Agent job that drops and re-creates this table with the added columns so this is more about writing the project allocation row's into the modified projects table by row_num?
Any advice?
--Update
What I need to do now is to get the row_number added as a column for EACH project in order of DESC.
So the first row for each project ID will be 1 and for each row after that will be 2,3,4,5,6.
I've found the following code on this website:
use db_name
with cte as
(
select *
, new_row_id=ROW_NUMBER() OVER (ORDER BY eraprojectid desc)
from era_project_allocations_m
where era_project_allocations_m.eraprojectid = era_project_allocations_m.eraprojectid
)
update cte
set row_id = new_row_id
update cte
set row_id = new_row_id
I've added row_id as a column in the previous SQL Agent step and this code and it runs but it doesn't produce me a row_number FOR EACH projectid.
As you can see from the above image; I need to have 1-2 FOR Each project ID - effectively giving me thousands of 1s, 2s, 3s, 4s.
That way I can sort them into columns :)
From what I can tell a query using row number is what you are after. (Also, it might be a pivot table..)
Example:
create table Something (
someId int,
someValue varchar(255)
);
insert into Something values (1, 'one'), (1, 'two'), (1, 'three'), (1, 'four'), (2, 'ein'), (2, 'swei'), (3, 'un')
with cte as (
select someId,
someValue,
row_number() over(partition by someId order by someId) as rn
from Something
)
select distinct someId,
(select someValue from cte where ct.someId = someId and rn = 1) as value1,
(select someValue from cte where ct.someId = someId and rn = 2) as value2,
(select someValue from cte where ct.someId = someId and rn = 3) as value3,
(select someValue from cte where ct.someId = someId and rn = 4) as value4
into somethingElse
from cte ct;
select * from somethingElse;
Result:
someId value1 value2 value3 value4
1 one two three four
2 ein swei NULL NULL
3 un NULL NULL NULL

How to check for a specific condition by looping through every record in SQL Server?

I do have following table
ID Name
1 Jagan Mohan Reddy868
2 Jagan Mohan Reddy869
3 Jagan Mohan Reddy
Name column size is VARCHAR(55).
Now for some other task we need to take only 10 varchar length i.e. VARCHAR(10).
My requirement is to check that after taking the only 10 bits length of Name column value for eg if i take Name value of ID 1 i.e. Jagan Mohan Reddy868 by SUBSTRING(Name, 0,11) if it equals with another row value. here in this case the final value of SUBSTRING(Jagan Mohan Reddy868, 0,11) is equal to Name value of ID 3 row whose Name is 'Jagan Mohan Reddy'. I need to make a list of those kind rows. Can somebody help me out on how can i achieve in SQL Server.
My main check is that the truncated values of my Name column should not match with any non truncated values of Name column. If so i need to get those records.
Assuming I understand the question, I think you are looking for something like this:
Create and populate sample data (Please save us this step in your future questions)
DECLARE #T as TABLE
(
Id int identity(1,1),
Name varchar(15)
)
INSERT INTO #T VALUES
('Hi, I am Zohar.'),
('Hi, I am Peled.'),
('Hi, I am Z'),
('I''m Zohar peled')
Use a cte with a self inner join to get the list of ids that match the first 10 chars:
;WITH cte as
(
SELECT T2.Id As Id1, T1.Id As Id2
FROM #T T1
INNER JOIN #T T2 ON LEFT(T1.Name, 10) = t2.Name AND T1.Id <> T2.Id
)
Select the records from the original table, inner joined with a union of the Id1 and Id2 from the cte:
SELECT T.Id, Name
FROM #T T
INNER JOIN
(
SELECT Id1 As Id
FROM CTE
UNION
SELECT Id2
FROM CTE
) U ON T.Id = U.Id
Results:
Id Name
----------- ---------------
1 Hi, I am Zohar.
3 Hi, I am Z
Try this
SELECT Id,Name
FROM(
SELECT *,ROW_NUMBER() OVER(PARTITION BY Name, LEFT(Name,11) ORDER BY ID) RN
FROM Tbale1 T
) Tmp
WHERE Tmp.RN = 1
loop over your column for all the values and put your substring() function inside this loop and I think in Sql index of string starts from 1 instead of 0. If you pass your string to charindex() like this
CHARINDEX('Y', 'Your String')
thus you will come to know whether it is starting from 0 or 1
and you can save your substring value as value of other column with length 10
I hope it will help you..
I think this should cover all the cases you are looking for.
-- Create Table
DECLARE #T as TABLE
(
Id int identity(1,1),
Name varchar(55)
)
-- Create Data
INSERT INTO #T VALUES
('Jagan Mohan Reddy868'),
('Jagan Mohan Reddy869'),
('Jagan Mohan Reddy'),
('Mohan Reddy'),
('Mohan Reddy123551'),
('Mohan R')
-- Get Matching Items
select *, SUBSTRING(name, 0, 11) as ShorterName
from #T
where SUBSTRING(name, 0, 11) in
(
-- get all shortnames with a count > 1
select SUBSTRING(name, 0, 11) as ShortName
from #T
group by SUBSTRING(name, 0, 11)
having COUNT(*) > 1
)
order by Name, LEN(Name)

Speeding up my query using sql server 2008.Alternative to cross apply

I have a function that is performing very slow.I am working a database that I need to migrate data and I have NO control over!.
Ideally I would like to use a view directly since this function is called by a view ,but I could only seem to be able to do it by calling a function.
===
A view should return whatever is in the dummytable by orderNo.If an orderNo has a paymenttype of "Interest" than the balance should be interest,if "tax" should be tax
In real life I will have 200000 rows and more,by using cross apply it seems to slow down quite a lot.
Is there a better way to get the data rather than using CrossApply?
Noddy Sample here(data and datatypes are just fictious for semplicity of the example)
CREATE DATABASE DummyDB
GO
use DummyDB
IF object_id(N'DummyTable', 'U') IS NOT NULL
DROP TABLE DummyTable
GO
CREATE TABLE DummyTable
(
Id int,
OrderNo varchar(255),
PaymentType varchar(255),
Credit varchar(255),
Debit varchar(255),
Balance varchar(255)
)
GO
INSERT INTO [dbo].[DummyTable]([Id], [OrderNo], [PaymentType], [Credit], [Debit], [Balance])
SELECT 1, N'200', N'Interest', N'10', N'5', N'5' UNION ALL
SELECT 2, N'201', N'Deposit', N'400', N'30', N'370' UNION ALL
SELECT 3, N'202', N'Tax', N'20', N'10', N'10' UNION ALL
SELECT 4, N'202', N'Tax', N'50', N'10', N'10'
--my sample attempt not performing
use DummyDB
select * from DummyTable
Declare #OrderNo int
set #OrderNo=202
SELECT
Tax=tx.Tax,
Interest=tx1.Interest,
Deposit=tx2.Deposit
FROM DummyTable T1
CROSS APPLY(SELECT
Tax=sum(cast(T2.Balance as money))
FROM DummyTable T2
WHERE T2.OrderNo=#OrderNo
AND PaymentType='Tax')as tx
CROSS APPLY(select
Interest=sum(cast(T2.Balance as money))
FROM DummyTable T2
WHERE T2.OrderNo=#OrderNo
AND PaymentType='Interest')as tx1
CROSS APPLY(select
Deposit=sum(cast(T2.Balance as money))
FROM DummyTable T2
WHERE T2.OrderNo=#OrderNo
AND PaymentType='Deposit')as tx2
WHERE T1.OrderNo=#OrderNo
Any Suggestion of using something more efficient than cross apply?
Many thanks
This will do almost the same as your sample query. It will give you one row with the result where your query will repeat the values for all rows that match #OrderNo.
select sum(case when T1.PaymentType = 'Tax' then cast(T1.Balance as money) else 0 end) as Tax,
sum(case when T1.PaymentType = 'Interest' then cast(T1.Balance as money) else 0 end) as Interest,
sum(case when T1.PaymentType = 'Deposit' then cast(T1.Balance as money) else 0 end) as Deposit
from DummyTable as T1
where T1.OrderNo = #OrderNo
BTW, you should make sure that the data type for OrderNo in the table is the same as the variable #OrderNo. It looks like you are dealing with integers so you should change the table. If that is not possible for you then you need to change #OrderNo to varchar(255) if you want to use an index on OrderNo.

Resources