Using Goup By in Procedure - SQLServer - sql-server

I have two tables in my database:
/* Create class table */
CREATE TABLE Class
(
CId INT NOT NULL,
ClassName VARCHAR (50) NOT NULL,
ClassDescription VARCHAR (MAX) NULL,
ClassStatus VARCHAR (50) NOT NULL,
StartDate DATE NOT NULL,
EndDate DATE NOT NULL,
Degree VARCHAR (50) NOT NULL,
TeacherName VARCHAR (50) NOT NULL,
ClassTopic VARCHAR (50) NOT NULL,
CONSTRAINT CHK_Dates CHECK (EndDate > StartDate),
CONSTRAINT CHK_Status CHECK (ClassStatus = 'Active' Or ClassStatus = 'NAct' Or ClassStatus = 'Archive'),
CONSTRAINT CHK_Degree CHECK (Degree = '1st' Or
Degree = '2nd' Or
Degree = '3rd' Or
Degree = '4th' Or
Degree = '5th' Or
Degree = '6th' Or
Degree = '7th' Or
Degree = '8th' Or
Degree = '9th' Or
Degree = '10th' Or
Degree = '11th' Or
Degree = '12th'),
CONSTRAINT CHK_Topic CHECK (ClassTopic = 'Mth' Or
ClassTopic = 'Ph' Or
ClassTopic = 'Ch' Or
ClassTopic = 'Bio' Or
ClassTopic = 'Fr' Or
ClassTopic = 'En' Or
ClassTopic = 'Arb' Or
ClassTopic = 'Rgs'),
PRIMARY KEY (CId)
);
/* Create Student table*/
CREATE TABLE Student
(
_SId INT NOT NULL,
UserName VARCHAR (50) NOT NULL,
Bdate DATE NOT NULL,
SPassword VARCHAR (50) NOT NULL,
SName VARCHAR (50) NOT NULL,
SLastName VARCHAR (50) NOT NULL,
NationalCode VARCHAR (10) NOT NULL,
Email VARCHAR (MAX) NULL,
StudentClass INT NOT NULL,
HomePhone VARCHAR (8) NOT NULL,
CONSTRAINT CHK_Email CHECK (Email like '%_#__%.__%'),
PRIMARY KEY (_SId),
FOREIGN KEY (StudentClass) REFERENCES Class (CId) ON DELETE CASCADE
);
ALTER TABLE Student
ADD CONSTRAINT New_CHK_NCode CHECK (NationalCode LIKE '%[0-9]%');
ALTER TABLE Student
ADD CONSTRAINT New_CHK_Phone CHECK (HomePhone LIKE '%[0-9]%');
I have inserted the below records in each of them:
USE School
/* Inserting data into tables */
INSERT INTO dbo.Class (CId, ClassName, ClassDescription, ClassStatus, StartDate, EndDate, Degree, TeacherName, ClassTopic)
VALUES (1, 'aaa', NULL, 'Active', '20020907', '20030907', '1st','Eetemadi', 'Mth'),
(2, 'bbb', NULL, 'Active', '20020907', '20030907', '1st','Rahmani', 'Ph'),
(3, 'ccc', NULL, 'Active', '20020907', '20030907', '2nd','Entezari', 'Ch'),
(4, 'ddd', NULL, 'Active', '20020907', '20030907', '2nd','Beytollahi', 'Bio'),
(5, 'eee', NULL, 'Active', '20020907', '20030907', '3rd','Zahirpour', 'Fr');
INSERT INTO dbo.Student (_SId, UserName, Bdate, SPassword, SName, SLastName, NationalCode, Email, StudentClass, HomePhone)
VALUES (1, 'aaa', '20020807', '1234', 'maryam', 'vahdati', '1234567890', 'mar#gmail.com', 1, '12345678'),
(2, 'bbb', '20020707', '4321', 'marjan', 'vahdati', '1234578906', 'marj#gmail.com', 1, '12345678'),
(3, 'ccc', '20020607', '1342', 'masomeh', 'vahdati', '1234567809', 'mas#gmail.com', 2, '12345678'),
(4, 'ddd', '20020507', '1243', 'mohammad', 'vahdati', '1234568907', 'moh#gmail.com', 2, '12345678'),
(5, 'eee', '20020407', '1342', 'mahmod', 'vahdati', '1245678903', 'mah#gmail.com', 3, '12345678');
Now I have to write a query in a Procedure to group classes based on degrees, show the number of students in every grade, and also the total number of classes.
I have written the below query:
ALTER PROCEDURE dbo.FirstReport
AS
BEGIN
Select Degree, COUNT(Degree) as numberOfClasses, COUNT(StudentClass) as numberOfStudents
FROM Class C left outer join Student S ON C.CId = S.StudentClass
GROUP BY Degree
UNION ALL
SELECT 'SUM' Degree, COUNT(Degree), COUNT(StudentClass)
FROM Class C join Student S ON C.CId = S.StudentClass;
END
The output is:
| Degree| numberOfClasses | numberOfStudents|
|:-----------------------------------------:|
|1st | 4 | 4 |
|2nd | 2 | 1 |
|3rd | 1 | 0 |
|SUM | 5 | 5 |
But the numberOfClasses must be 2 when the Degree is 1st.
I do not know how to make it correct. I will be grateful for your help.

As I mentioned in the comments, you have a many to one join here, and thus the COUNT you get is correct, due said one to many join; you have 2 rows that each join to 2 other rows and 2 * (1 * 2) = 4.
Instead, use a DISTINCT on your first COUNT on the ID column. Also, there's no need for a UNION ALL; you can use GROUPING SETS or ROLLUP to get the "grand total" row:
SELECT ISNULL(C.Degree,'SUM') AS Degree,
COUNT(DISTINCT C.CId) AS NumberOfClasses,
COUNT(S.StudentClass) AS NumberOfStudents
FROM dbo.Class C
LEFT OUTER JOIN dbo.Student S ON C.CId = S.StudentClass
GROUP BY GROUPING SETS(Degree,());

Related

PostgreSQL ARRAY_AGG return separate arrays

The use case is this: each user can create their own games, and keep track in which country they played a game.
I would like to create one query where I can get a list of all games for that user and in which country that game was played. I am only interested in the country id.
I have 4 tables: users, games, countries and a games_countries_xref table.
CREATE SEQUENCE countries_id_seq INCREMENT 1 MINVALUE 1 MAXVALUE 2147483647 START 1 CACHE 1;
CREATE TABLE "public"."countries" (
"id" integer DEFAULT nextval('countries_id_seq') NOT NULL,
"name" character varying(200) NOT NULL,
CONSTRAINT "countries_pkey" PRIMARY KEY ("id")
) WITH (oids = false);
INSERT INTO "countries" ("id", "name") VALUES
(1, 'USA'),
(2, 'Japan'),
(3, 'Australia');
CREATE SEQUENCE games_id_seq INCREMENT 1 MINVALUE 1 MAXVALUE 2147483647 START 3 CACHE 1;
CREATE TABLE "public"."games" (
"id" integer DEFAULT nextval('games_id_seq') NOT NULL,
"user_id" integer NOT NULL,
"name" character varying(200) NOT NULL,
CONSTRAINT "games_pkey" PRIMARY KEY ("id")
) WITH (oids = false);
INSERT INTO "games" ("id", "user_id", "name") VALUES
(1, 1, 'Monopoly'),
(2, 1, 'Zelda'),
(3, 2, 'Hide & Seek');
CREATE TABLE "public"."games_countries_xref" (
"game_id" integer NOT NULL,
"country_id" integer NOT NULL
) WITH (oids = false);
INSERT INTO "games_countries_xref" ("game_id", "country_id") VALUES
(1, 1),
(1, 2),
(1, 3),
(2, 2),
(3, 1);
CREATE SEQUENCE users_id_seq INCREMENT 1 MINVALUE 1 MAXVALUE 2147483647 START 2 CACHE 1;
CREATE TABLE "public"."users" (
"id" integer DEFAULT nextval('users_id_seq') NOT NULL,
"name" character varying(200) NOT NULL,
CONSTRAINT "users_pkey" PRIMARY KEY ("id")
) WITH (oids = false);
INSERT INTO "users" ("id", "name") VALUES
(1, 'Jack'),
(2, 'Jason');
when querying the data, I tried using ARRAY_AGG:
WITH country_ids AS (
SELECT g.user_id, ARRAY_AGG(gcx.country_id) AS country_ids
FROM games AS g
LEFT JOIN games_countries_xref AS gcx ON g.id = gcx.game_id
GROUP BY g.user_id
)
SELECT g.name, country_ids
FROM games AS g
NATURAL LEFT JOIN country_ids
WHERE g.user_id = 1
but that gives me this output:
name | country_ids
------------------
Monopoly | {1,2,3,2}
Zelda | {1,2,3,2}
while I am looking for this:
name | country_ids
------------------
Monopoly | {1,2,3}
Zelda | {2}
I know I am likely doing something wrong in the subquery, but I can't figure out what.
Any ideas?
You are on the right track with ARRAY_AGG, but just a little over aggressive with the joins. You just need a simple join (1 left, 1 inner) on the 3 tables
select g.name,array_agg(gcx.country_id) as country_ids
from games g
join users u on u.id = g.user_id
left join games_countries_xref gcx on gcx.game_id = g.id
where u.id = 1
group by g.name;
+----------+-------------+
| name | country_ids |
+----------+-------------+
| Monopoly | {1,2,3} |
| Zelda | {2} |
+----------+-------------+

Create 2 pair unique id data rows

I want create 2 data rows with 1 same findable unique id to each one by 1 query
2 difference is side column {1 buyer} {0 seller } and userId column {userID's}
id userId side price qty pairId
1 6 0 60 10 1
2 9 1 60 10 1
trying to visualize result table:
In SQL Server I tried SCOPE_IDENTITY()
insert into [dbo].[deals] (side, price, qty,pairId)
values (1, 60, 10 ,SCOPE_IDENTITY()),
(0, 60, 10 ,SCOPE_IDENTITY()),
create table command:
CREATE TABLE [demonstration].[dbo].[Deals](
[id] [bigint] IDENTITY(1,1) NOT NULL,
[userId] [int] NULL,
[side] [smallint] NULL,
[qty] [decimal](18, 4) NULL,
[price] [decimal](18, 4) NULL,
[pairId] [bigint] NULL
) ON [PRIMARY]
GO
Add an IDENTITY column to deals table(or alter one column to identity) then use your query:
insert into [dbo].[deals] (side, price, qty,pairId)
values (1, 60, 10 ,IDENT_CURRENT('deals')+1),
(0, 60, 10 ,IDENT_CURRENT('deals')+1)
added +1

Select all individuals who have certain properties on a given date

I use dynamic properties to manage the characteristics of individuals. I have a table IndividusDynPropValues with 1 line for each individual and each property For example:
ID StartDate ValueString ValueFloat ValueDate Individus_ID IndividusDynProp
14 2018-09-10 Outside NULL NULL 3 Out Status
13 2018-08-15 Dead NULL NULL 1 Out Status
12 2018-08-02 Male NULL NULL 3 Sex
11 2018-07-28 #DBNULL# NULL NULL 1 Out Status
10 2018-07-25 Sold NULL NULL 1 Out Status
9 2018-06-07 Unk NULL NULL 3 Sex
8 2018-06-07 Adult NULL NULL 3 Status
7 2018-06-06 Femal NULL NULL 2 Sex
6 2018-06-06 Adult NULL NULL 2 Status
5 2018-06-03 Male NULL NULL 1 Sex
4 2018-06-03 Adult NULL NULL 1 Status
3 2018-05-23 Egg NULL NULL 3 Status
2 2018-05-23 Egg NULL NULL 2 Status
1 2018-05-21 Egg NULL NULL 1 Status
'#DBNULL#' means that the individual is on site again. I want to create a function that takes a date for the input parameter and returns a table with the ID of each individual who belong to the groups “Male living on site” at the date. An individual is part of the group if Sex = 'Male' AND (Out Status IS NULL OR Out Status = '#DBNULL#')
So in the example, if the date enter in the function is “2018-07-20” it returns:
Individus_ID
1
if the date is “2018-08-10” it returns:
Individus_ID
3
1
if the date is “2018-08-17” it returns:
Individus_ID
3
I’ve tried that:
CREATE FUNCTION fn_Groupe_Individus_Male_Vivant (#daDate datetime)
RETURNS #TGrMalesVivants TABLE (
TList_Individual [varchar] (50) NOT NULL
)
AS
BEGIN
DECLARE #TtempGrMalesVivants TABLE (TList_Individual [varchar] (50) NOT NULL)
DECLARE #TtempGrMales TABLE (TList_MIndividual int NOT NULL)
INSERT INTO #TtempGrMales
SELECT DISTINCT Individus_ID
FROM IndividusDynPropValues
DECLARE #selectedOutStatus VARCHAR(50),
#sBirdId int,
#tmpSex VARCHAR(3)
WHILE EXISTS (SELECT * FROM #TtempGrMales)
BEGIN
SELECT TOP 1 #sBirdId = TList_MIndividual FROM #TtempGrMales
SELECT #selectedOutStatus =IDPV.ValueString
FROM [dbo].[IndividusDynPropValues] AS IDPV
WHERE NOT EXISTS (SELECT *
FROM [dbo].[IndividusDynPropValues] AS IDPV2
WHERE IDPV.Individus_ID=IDPV2.Individus_ID
AND IDPV.IndividusDynProp_ID=IDPV2.IndividusDynProp_ID
AND IDPV2.StartDate>IDPV.StartDate AND CONVERT(smalldatetime,IDPV2.StartDate,120 )<=#daDate)
AND CONVERT(smalldatetime,IDPV.StartDate,120 )<=#daDate
AND IDPV.Individus_ID =#sBirdId
AND IDPV.IndividusDynProp_ID='Out Status'
SELECT #tmpSex= IDPV.ValueString
FROM [dbo].[IndividusDynPropValues] AS IDPV
INNER JOIN TSaisie AS TS ON TS.TSai_PK_ID=IDPV.Saisie_ID
INNER JOIN TProtocole AS TP ON TP.TPro_PK_ID=TS.TSai_FK_TPro_ID
WHERE NOT EXISTS (SELECT *
FROM [dbo].[IndividusDynPropValues] AS IDPV2
WHERE IDPV.Individus_ID=IDPV2.Individus_ID
AND IDPV.IndividusDynProp_ID=IDPV2.IndividusDynProp_ID
AND IDPV2.StartDate>IDPV.StartDate AND CONVERT(smalldatetime,IDPV2.StartDate,120 )<=#daDate)
AND CONVERT(smalldatetime,IDPV.StartDate,120 )<=#daDate
AND IDPV.Individus_ID =#sBirdId
AND IDPV.IndividusDynProp_ID='Sex'
AND TPro_Importance = (SELECT Max(TP2.Tpro_Importance)
FROM IndividusDynPropValues IDPV3
INNER JOIN TSaisie AS TS2 ON TS2.TSai_PK_ID=IDPV3.Saisie_ID
INNER JOIN TProtocole AS TP2 ON TP2.TPro_PK_ID=TS2.TSai_FK_TPro_ID
WHERE IDPV3.Individus_ID=#sBirdId
AND IDPV3.IndividusDynProp_ID=4
AND IDPV3.StartDate>=IDPV.StartDate
AND CONVERT(smalldatetime,IDPV3.StartDate,120 )<=#daDate)
IF #tmpSex='Male' AND (#selectedOutStatus IS NULL OR #selectedOutStatus='#DBNULL#')
BEGIN
INSERT INTO #TtempGrMalesVivants (TList_Individual)
VALUES (#sBirdId)
END
DELETE TOP (1) FROM #TtempGrMales
END
INSERT #TGrMalesVivants
SELECT *
FROM #TtempGrMalesVivants
RETURN
END
it works but it takes 1:55 with all the table (1859732 lines) so it’s too long
I think I understand what you are trying to do. Notice how I posted sample data? This is a good example for what you should do in the future.
Some conditional aggregation should work here. You need this aggregation because your data is denormalized into an EAV and you need to reassemble this into a normalized table. This works for the sample data and your desired output. You can uncomment the set #MyDate line to see the second value working.
if OBJECT_ID('tempdb..#IndividusDynPropValues') is not null
drop table #IndividusDynPropValues
create table #IndividusDynPropValues
(
ID int
, StartDate date
, ValueString varchar(50)
, ValueFloat float
, ValueDate date
, Individus_ID int
, IndividusDynProp varchar(50)
)
insert #IndividusDynPropValues values
(14, '2018-09-10', 'Outside', NULL, NULL, 3, 'Out Status')
, (13, '2018-08-15', 'Dead', NULL, NULL, 1, 'Out Status')
, (12, '2018-08-02', 'Male', NULL, NULL, 3, 'Sex')
, (11, '2018-07-28', '#DBNULL#', NULL, NULL, 1, 'Out Status')
, (10, '2018-07-25', 'Sold', NULL, NULL, 1, 'Out Status')
, (9 , '2018-06-07', 'Unk' , NULL, NULL, 3, 'Sex')
, (8 , '2018-06-07', 'Adult' , NULL, NULL, 3, 'Status')
, (7 , '2018-06-06', 'Femal' , NULL, NULL, 2, 'Sex')
, (6 , '2018-06-06', 'Adult' , NULL, NULL, 2, 'Status')
, (5 , '2018-06-03', 'Male' , NULL, NULL, 1, 'Sex')
, (4 , '2018-06-03', 'Adult' , NULL, NULL, 1, 'Status')
, (3 , '2018-05-23', 'Egg' , NULL, NULL, 3, 'Status')
, (2 , '2018-05-23', 'Egg' , NULL, NULL, 2, 'Status')
, (1 , '2018-05-21', 'Egg' , NULL, NULL, 1, 'Status')
declare #MyDate date = '20180720' --returns Individus_ID 1
--set #MyDate = '20180810' --returns Individus_ID 1, 3
;
with MySortedData as
(
select i.*
, RowNum = ROW_NUMBER()over(partition by i.Individus_ID, i.IndividusDynProp order by i.StartDate desc)
from #IndividusDynPropValues i
where i.StartDate <= #MyDate
)
select s.Individus_ID
, OutStatus = max(case when IndividusDynProp = 'Out Status' then ValueString end)
, Status = max(case when IndividusDynProp = 'Status' then ValueString end)
, Sex = max(case when IndividusDynProp = 'Sex' then ValueString end)
from MySortedData s
where s.RowNum = 1
group by s.Individus_ID
having isnull(max(case when IndividusDynProp = 'Out Status' then ValueString end), '#DBNULL#') = '#DBNULL#'
and max(case when IndividusDynProp = 'Sex' then ValueString end) = 'Male'

How to combine multiple rows in T-SQL

Using SQL Server 2012: I have a simple table:
CREATE TABLE simpletable
(
[key] INT NOT NULL IDENTITY(1,1),
[id] INT,
[ca] INT,
[cp] INT,
[ct] INT
)
For various reasons this table only has a single row per [ca], [cp] and [ct] - like this:
INSERT INTO simpletable ([id], [ca], [cp], [ct])
VALUES (1, 10, null, null),
(1, null, 20, null),
(1, null, 120, null),
(1, null, null, 30),
(2, 11, null, null),
(2, null, 21, null),
(2, null, null, 31),
(2, null, null, 231)
What I would like to achieve (without cursors) is to explode out all the combinations into a temp table or a table-type variable; something like this:
id ca cp ct
---------------
1 10 20 30
1 10 120 30
2 11 21 31
2 11 21 231
I'd really appreciate your help on this.
Here is one (Cumbersome) way to do it:
SELECT dca.[id], dca.[ca], dcp.[cp], dct.[ct]
FROM
(
SELECT [id], [ca]
FROM simpletable
WHERE [ca] IS NOT NULL
) dca
INNER JOIN
(
SELECT [id], [cp]
FROM simpletable
WHERE [cp] IS NOT NULL
) dcp ON dca.id = dcp.id
INNER JOIN
(
SELECT [id], [ct]
FROM simpletable
WHERE [ct] IS NOT NULL
) dct ON dca.id = dct.id
See a live demo on rextester
Note this solution is based on the assumption that there must be at least one column other then id that is not null in every row.

T-SQL hierarchy - get breadcrumbs using query

I have virtual folder structure saved in database and I want to get the breadcrumbs from the current folder to the root. The data can be unsorted (but better will be sorted) and I want the parent folders of the current folder only.
The table definition is:
DECLARE Folders TABLE (
FOL_PK INT IDENTITY(1,1) NOT NULL,
FOL_Name VARCHAR(200) NOT NULL,
FOL_FOL_FK INT NULL -- Foreign key to parent
)
And this is my solution:
DECLARE #FOL_PK INT = 5 -- Current folder PK
DECLARE #breadcrumbs TABLE (
FOL_PK INT NOT NULL,
FOL_Name VARCHAR(200) NOT NULL,
FOL_FOL_FK INT NULL
)
DECLARE #isRoot BIT = 0
,#currentFolderPK INT
,#parentFK INT
-- Get current and parent folder PK
SELECT
#currentFolderPK = FOL_PK
FROM
Folder
WHERE
FOL_PK = #FOL_PK
-- Breadcrumb
WHILE (#isRoot = 0)
BEGIN
-- Save to breadcrumb
INSERT INTO #breadcrumbs
SELECT
FOL_PK,
FOL_Name,
FOL_FOL_FK
FROM
Folder
WHERE
FOL_PK = #currentFolderPK
-- Set parent as current
SET #currentFolderPK =
(
SELECT
FOL_FOL_FK
FROM
Folder
WHERE
FOL_PK = #currentFolderPK
)
-- Set flag for loop
SET #isRoot = CASE
WHEN ISNULL(#currentFolderPK, 0) = 0 THEN 1
ELSE 0
END
END
-- Return breadcrumbs
SELECT
FOL_PK AS PK,
FOL_Name AS Name,
FOL_FOL_FK AS ParentFK
FROM
#breadcrumbs
The problem is I am not very comfortable with the loop. Is there any other sophisticated solution how to do this?
Try this using a recursive Common Table Expression (CTE):
SQL Fiddle
MS SQL Server 2008 Schema Setup:
CREATE TABLE [Folders](
[FOL_PK] [int] IDENTITY(1,1) NOT NULL,
[FOL_Name] [varchar](200) NOT NULL,
[FOL_FOL_FK] [int] NULL,
CONSTRAINT [PK__Folders__FOL_PK] PRIMARY KEY CLUSTERED
(
[FOL_PK] ASC
))
ALTER TABLE [dbo].[Folders]
WITH CHECK ADD CONSTRAINT [FK_Folders_Folders] FOREIGN KEY([FOL_FOL_FK])
REFERENCES [dbo].[Folders] ([FOL_PK])
ALTER TABLE [dbo].[Folders] CHECK CONSTRAINT [FK_Folders_Folders]
INSERT INTO Folders(FOL_Name, FOL_FOL_FK)
VALUES ('Level 1', NULL),
('Level 1.1', 1),
('Level 1.2', 1),
('Level 1.3', 1),
('Level 1.2.1', 3),
('Level 1.2.2', 3),
('Level 1.2.3', 3),
('Level 1.2.2.1', 6),
('Level 1.2.2.2', 6),
('Level 1.2.2.3', 6),
('Level 1.3.1', 4),
('Level 1.3.2', 4)
Query 1:
DECLARE #FolderId Int = 9
;WITH CTE
AS
(
SELECT FOL_PK AS PK, FOL_NAME As Name, FOL_FOL_FK AS ParentFK
FROM Folders
WHERE FOL_PK = #FolderId
UNION ALL
SELECT F.FOL_PK AS PK, F.FOL_NAME AS Name, F.FOL_FOL_FK AS ParentFK
FROM Folders F
INNER JOIN CTE C
ON C.ParentFK = F.FOL_PK
)
SELECT *
FROM CTE
Results:
| PK | Name | ParentFK |
|----|---------------|----------|
| 9 | Level 1.2.2.2 | 6 |
| 6 | Level 1.2.2 | 3 |
| 3 | Level 1.2 | 1 |
| 1 | Level 1 | (null) |

Resources