how to select column name that is not in group by clause - sql-server

SELECT [a],[b],[c],COUNT(*) AS Hits
FROM [dbo].[ta] join [dbo].[tb] on [tb].[id] = [ta].[id]
where GROUP BY [a],[b],[c]
I want to select column c , but not in group by cluase?
because column "c" is returned a not suifficient record when as in group by clause
My current query is
SELECT top 20 [headshot],
[athleteId],
[athleteName],
COUNT() AS Hits
FROM [dbo].[tblRep_Usecase2]
join [dbo].[tblRep_Login] on [tblRep_Login].[ID] = [tblRep_Usecase2].[ID]
where athleteName != 'NULL'
and headshot != 'NULL'
and headshot != ''
and convert(date,[tblRep_Usecase2].[insertDate]) >='11/9/2015'
and convert(date,[tblRep_Usecase2].[insertDate]) <='11/9/2015'
and [tblRep_Usecase2].[appsportid]='41'
GROUP BY [headshot],[athleteId],[athleteName]
HAVING COUNT()>1 order by Hits DESC

Short answer : You can't.
Longer answer : You have to apply a formula to define how the "c" data is discriminated.
For instance, if you have a table about kids like
|a |b |c |
|-------|-------|---------|
|Bob |Sarah |Amanda |
|Bob |Sarah |Steve |
|Bob |Amanda |Sarah Jr.|
You ask him the number of kids of each couples (so Bob+Sarah = 2, Bob+Amanda=1), but then you also ask him the name of a kid. Since he cannot tell you which kid you want, he cannot give you your result.
In this case, maybe Bob, Sarah, Amada, Steve and Sarah Jr are in a table elsewhere with their age. so if you want only the oldest child, you would need to return a,b,"subquery","count". Depending on the situation, a simple aggregate function like MAX/MIN also works.

Related

T-SQL Verify each character of each value in a column against values of another table

I have a table in a database with a column which has values like XX-xx-cccc-ff-gg. Let's assume this is table ABC and column is called ABC_FORMAT_STR. In another table, ABC_FORMAT_ELEMENTS I have a column called CHARS with values like, A, B, C, D... X, Y, Z, a, d, f, g, x, y, z etc. (please don't assume I have all ASCII values there, it's mainly some letters and numbers plus some special characters like *, ;, -, & etc.).
I need to add a constraint in [ABC].[ABC_FORMAT_STR] column, in such a way so, each and every character of every value of that column, should exist in [ABC_FORMAT_ELEMENTS].[CHARS]
Is the possible? Can someone help me with this?
Thank you very much in advance.
This is an example with simple names, keeping the names of the object above for clarity:
Example
SELECT [ABC_FORMAT_STR] FROM [ABC]
Nick
George
Adam
SELECT [CHARS] FROM [ABC_FORMAT_ELEMENTS]
A
G
N
a
c
e
g
i
k
o
r
After the coonstraint:
SELECT [ABC_FORMAT_STR] FROM [ABC]
Nick
George
Note on the result:
"Adam" cannot be included because "d" and "m" character are not in [ABC_FORMAT_ELEMENTS] table.
Here is a simple and most natural solution based on the TRANSLATE() function.
It will work starting from SQL Server 2017 onwards.
SQL
-- DDL and sample data population, start
DECLARE #ABC TABLE (ABC_FORMAT_STR VARCHAR(50));
INSERT INTO #ABC VALUES
('Nick'),
('George'),
('Adam');
DECLARE #ABC_FORMAT_ELEMENTS TABLE (CHARS CHAR(1));
INSERT INTO #ABC_FORMAT_ELEMENTS VALUES
('A'), ('G'), ('N'),('a'), ('c'), ('e'), ('g'),
('i'), ('k'), ('o'), ('r');
-- DDL and sample data population, end
SELECT a.*
, t1.legitChars
, t2.badChars
FROM #ABC AS a
CROSS APPLY (SELECT STRING_AGG(CHARS, '') FROM #ABC_FORMAT_ELEMENTS) AS t1(legitChars)
CROSS APPLY (SELECT TRANSLATE(a.ABC_FORMAT_STR, t1.legitChars, SPACE(LEN(t1.legitChars)))) AS t2(badChars)
WHERE TRIM(t2.badChars) = '';
Output
+----------------+-------------+----------+
| ABC_FORMAT_STR | legitChars | badChars |
+----------------+-------------+----------+
| Nick | AGNacegikor | |
| George | AGNacegikor | |
+----------------+-------------+----------+
Output with WHERE clause commented out
Just to see why the row with the 'Adam' value was filtered out.
+----------------+-------------+----------+
| ABC_FORMAT_STR | legitChars | badChars |
+----------------+-------------+----------+
| Nick | AGNacegikor | |
| George | AGNacegikor | |
| Adam | AGNacegikor | d m |
+----------------+-------------+----------+
Based on your sample data, here's one method to identify valid/invalid rows in ABC. You could easily adapt this to be part of a trigger that can check inserted or updated rows in inserted and rollback if any rows violate the criteria.
This uses a tally/numbers table (very often used for splitting strings), This defines one using a CTE but a permanent solution would have a permanent numbers table to reuse.
The logic is to split the strings into rows and then count the rows that exist in the lookup table and reject any with a count of rows that is less than the length of the string.
with
numbers (n) as (select top 100 Row_Number() over (order by (select null)) from sys.messages ),
strings as (
select a.ABC_FORMAT_STR, Count(*) over(partition by a.ABC_FORMAT_STR) n
from abc a cross join numbers n
where n.n<=Len(a.ABC_FORMAT_STR)
and exists (select * from ABC_FORMAT_ELEMENTS e where e.chars=Substring(a.ABC_FORMAT_STR,n,1))
)
select ABC_FORMAT_STR
from strings
where Len(ABC_FORMAT_STR)=n
group by ABC_FORMAT_STR
/* change to where Len(ABC_FORMAT_STR) <> n to find rows that aren't allowed */
See this DB Fiddle

Sql Server Weird CASE Statement

I am attempting to do something, but I am not sure if it is possible. I don't really know how to look up something like this, so I'm asking a question here.
Say this is my table:
Name | Group
-----+--------
John | Alpha
Dave | Alpha
Dave | Bravo
Alex | Bravo
I want to do something like this:
SELECT TOP 1 CASE
WHEN Group = 'Alpha' THEN 1
WHEN Group = 'Bravo' THEN 2
WHEN Group = 'Alpha' AND
Group = 'Bravo' THEN 3
ELSE 0
END AS Rank
FROM table
WHERE Name = 'Dave'
I understand why this won't work, but this was the best way that I could explain what I am trying to do. Basically, I just need to know when one person is a part of both groups. Does anyone have any ideas that I could use?
You should create a column to hold the values you want to sum and sum them, probably easiest to do this via a subquery:
Select Name, SUM(Val) as Rank
FROM (SELECT Name, CASE WHEN Group = 'Alpha' THEN 1
WHEN Group = 'Bravo' THEN 2
ELSE 0 END AS Val
FROM table
WHERE Name = 'Dave') T
GROUP BY Name
You can add TOP 1 and ORDER BY SUM(Val) to get the top ranked row if required.
After reading your comment, it could be simplified further to:
Select Name, COUNT([GROUP]) GroupCount
FROM table
GROUP BY Name
HAVING COUNT([GROUP]) > 1
That will simply return all names where they have more than 1 group.

Best way to concat 1 to n values into single field from two tables

T-SQL
Imagine two tables looking like this:
Table: students
==============================
| TeacherID | SName |
| 1 | Thompson |
| 1 | Nickles |
| 2 | Cree |
==============================
Table: teacher
====================================================
| TeacherID | TName | + many other fields |
| 1 | Pipers | |
| 2 | Slinger | |
====================================================
The field names are completely arbitrary.
I want to create a query with the following output:
================================================================
| TeacherName | many other fields | Students |
| Pipers | | Thompson,Nickles |
================================================================
Currently I have something like this:
SELECT *
FROM teacher
LEFT JOIN (
SELECT DISTINCT
EL2.teacherID,
STUFF(( SELECT ',' + SName
FROM students
WHERE EL2.teacherID = students.teacherID
FOR XML PATH('')
),1,1,'') AS "Students"
FROM students, teacher EL2) t1
ON t1.teacherID = teacher.teacherID
WHERE t1.Students LIKE '%Thompson%'
This works and gives me what I need. The WHERE clause is to illustrate that I
also absolutely need to be able to filter if a teacher has that student, but then put all students that teacher has into the concated field.
My question now is if there is a better way to do this.
I already looked at this:
Concatenate many rows into a single text string?
But it didn't help me much because one I couldn't get it to work with two seperate tables and two I couldn't filter the way I needed.
The SQL Management Studio execution plan indicates that the SELECT DISTINCT is
very expensive and others have said that the reliance on XML PATH is not optimal because it's behaviour can change.
Be carefull with a DISTINCT on names, as you might have two students with the same name! And btw: GROUP BY is in most cases a better performing approach to get a distinct list...
You might try something like this:
SELECT t.*
,STUFF(( SELECT ',' + s.SName
FROM students AS s
WHERE t.teacherID = s.teacherID
FOR XML PATH('')
),1,1,'') AS Students
FROM teacher AS t
WHERE EXISTS(SELECT 1 FROM students AS x WHERE x.teacherID=t.teacherID /*AND [PUT YOUR FILTER HERE]*/)
If I understand this correctly you want to find only teachers where one given student is connected to the teacher. And in this case you want to find all students bound to all teachers connected to the given student, correct?
At the end you find a /*AND [PUT YOUR FILTER HERE]*/ At this place you should put something like AND x.StudentId=123. This will filter the teachers to the rows connected with this student only. For these teachers all students are concatenated...
Use XML Path,..How for XML path works:
select
TeacherID,
Tname,
stuff((select ','+s.sname from students s where s.teacherid=t.teacherid
for xml path('')),1,1,'')as students
from
teachers t

How to use group by in SQL Server query?

I have problem with group by in SQL Server
I have this simple SQL statement:
select *
from Factors
group by moshtari_ID
and I get this error :
Msg 8120, Level 16, State 1, Line 1
Column 'Factors.ID' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
This is my result without group by :
and this is error with group by command :
Where is my problem ?
In general, once you start GROUPing, every column listed in your SELECT must be either a column in your GROUP or some aggregate thereof. Let's say you have a table like this:
| ID | Name | City |
| 1 | Foo bar | San Jose |
| 2 | Bar foo | San Jose |
| 3 | Baz Foo | Santa Clara |
If you wanted to get a list of all the cities in your database, and tried:
SELECT * FROM table GROUP BY City
...that would fail, because you're asking for columns (ID and Name) that aren't in the GROUP BY clause. You could instead:
SELECT City, count(City) as Cnt FROM table GROUP BY City
...and that would get you:
| City | Cnt |
| San Jose | 2 |
| Santa Clara | 1 |
...but would NOT get you ID or Name. You can do more complicated things with e.g. subselects or self-joins, but basically what you're trying to do isn't possible as-stated. Break down your problem further (what do you want the data to look like?), and go from there.
Good luck!
When you group then you can select only the columns you group by. Other columns need to be aggrgated. This can be done with functions like min(), avg(), count(), ...
Why is this? Because with group by you make multiple records unique. But what about the column not being unique? The DB needs a rule for those on how to display then - aggregation.
You need to apply aggregate function such as max(), avg() , count() in group by.
For example this query will sum totalAmount for all moshtari_ID
select moshtari_ID,sum (totalAmount) from Factors group by moshtari_ID;
output will be
moshtari_ID SUM
2 120000
1 200000
Try it,
select *
from Factorys
Group by ID, date, time, factorNo, trackingNo, totalAmount, createAt, updateAt, bark_ID, moshtari_ID
If you are applying group clause then you can only use group columns and aggregate function in select
syntax:
SELECT expression1, expression2, ... expression_n,
aggregate_function (aggregate_expression)
FROM tables
[WHERE conditions]
GROUP BY expression1, expression2, ... expression_n
[ORDER BY expression [ ASC | DESC ]];

Count each unique content

How do I count how many times the content of a field nameappears in my table?
Name | Other
Brad | smth
Brad | smth
Daniel | smth
Matt | smth
Matt | smth
Matt | smth
For example,for the above table I would like to know how many times I have 'Brad',how many times 'Daniel' and how many times 'Matt'.How do I do this with just one select?
I'm interested in this because I want do display only the Names that appear more times than a given value.
My actual code:
select director.LastName,director.FirstName,count(director.FirstName)as counter,film.title
from director,film
where film.Id_Director=director.id
group by director.LastName,director.FirstName,film.title
having count(Director.FirstName)>2
Baz Luhrmann 1 Paranormal activity 4
Baz Luhrmann 1 Struck by lightning
Baz Luhrmann 1 The big bang theory
Baz Luhrmann 1 The family
Baz Luhrmann 1 The Quarterback
Brad Falchuk 1 A Kitty or a Gaga
Brad Falchuk 1 All or nothing
Brad Falchuk 1 Bridesmaids
Brian Dan 1 All or nothing
I was expecting it to count exactly how many times 'Baz' appears in the table(this should be done for every name) and display only if the value of count > the 3 for example.
Group by the name and use a count()
select name, count(*) as name_count
from your_table
group by name
Aggregate functions like count are applied for each group.
To display only names that appear more than 1 time you can do
select name, count(*) as name_count
from your_table
group by name
having count(*) > 1
Having is like a where clause but for groups.
Edit
select d.LastName, d.FirstName, count(f.Id_Director) as counter
from director d
inner join film f on f.Id_Director = d.id
group by d.LastName, d.FirstName
having count(f.Id_Director) > 2
You had grouped by the film too. That won't work. You basically queried for directors that are more than 2 times part of a film.
The problem is you are grouping by film. Since there are a director/film is ill away count as 1.
You you want to keep the film names in that select result set I suggest you make a select movies and a subquery to count how many times that director can be joined to other movies.
Just writed a example at SQLFiddle
Example

Resources