Cakephp 3.x Insert data into two tables - cakephp

I've two tables: -
sales
CREATE TABLE `sales` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`title` varchar(255) DEFAULT NULL,
`description` text,
`quantity` int(10) DEFAULT NULL,
`price` decimal(18,2) DEFAULT NULL,
`payment_method_id` int(10) DEFAULT NULL,
`user_id` int(10) DEFAULT NULL,
`created` datetime DEFAULT NULL,
`modified` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
sale_details
CREATE TABLE `sale_details` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`sale_id` int(10) DEFAULT NULL,
`product_id` int(10) DEFAULT NULL,
`quantity` int(10) DEFAULT NULL,
`price` decimal(18,2) DEFAULT NULL,
`total_price` decimal(18,2) DEFAULT NULL,
`created` datetime DEFAULT NULL,
`modified` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
I need to insert data into these tables accordingly using ajax. First of all, I structured my array data into the following.
Here is my data,
[
'quantity' => 3,
'price' => 63,
'payment_method_id' => 1,
'user_id' => 1,
'sale_details' => [
0 => [
'product_id' => 1,
'quantity' => 2,
'price' => 24,
'total_price' => 48
],
1 => [
'product_id' => 49,
'quantity' => 1,
'price' => 15,
'total_price' => 15
]
]
]
SalesController.php
if ($this->request->is('ajax')) {
$sales = $this->Sales->newEntity(
$this->request->data(),
[
'validate' => 'create',
'associated' => [
'SaleDetails' => ['validate' => 'create']
]
]
);
if ($this->Sales->save($sales)) {
//code
}
}
I manage to insert data into those tables but primary key of both tables keep increasing with addition number of 2. I inserted the data for 3 times. Here is how the data recorded: -
sales
id title description quantity price payment_method_id user_id created modified
------ ------ ----------- -------- ------ ----------------- ------- ------------------- ---------------------
1 (NULL) (NULL) 3 63.00 1 1 2017-03-03 11:37:11 2017-03-03 11:37:11
sale_details
id sale_id product_id quantity price total_price created modified
------ ------- ---------- -------- ------ ----------- ------------------- ---------------------
1 1 1 2 24.00 48.00 2017-03-03 11:37:11 2017-03-03 11:37:11
2 1 49 1 15.00 15.00 2017-03-03 11:37:11 2017-03-03 11:37:11
As you can notice from the tables, sales table id increasing with the addition of 2 and it happened to sale_details table as well.
My questions are as following: -
1) I'm kinda new to cakephp 3, so is this the right method to save data into multiple tables? I removed the following lines and I'm still able to save data into those tables. What to do with 'associated' here?
SalesController.php
if ($this->request->is('ajax')) {
$sales = $this->Sales->newEntity(
$this->request->data(),
[
'validate' => 'create',
/*'associated' => [
'SaleDetails' => ['validate' => 'create']
]*/ // removed
]
);
if ($this->Sales->save($sales)) {
//code
}
}
2) I can't figure out why those id increasing with the addition of 2. I'm pretty sure I've set auto_increment = 1 for both table.
System variable auto_increment_increment has been set to 2. Not sure how could this happen though.
Thanks in advance.

Yes, that is the correct way to save associations.
Your associations are still being converted (and consequently saved) even without specifying them via the associated option, because first level associations are allowed by default, ie when removing the option, all first level associations can be converted, and when specifying it as shown in your example, only SaleDetails associations can be converted.
Quotes from the docs:
By default all the associations on this table will be hydrated. You can limit which associations are built, or include deeper associations using the options parameter
API > \Cake\ORM\Table::newEntity()
When you are saving an entity, you can also elect to save some or all of the associated entities. By default all first level entities will be saved.
Cookbook > Datbase Access & ORM > Saving Data > Saving Associations
See also
Cookbook > Datbase Access & ORM > Saving Data > Converting Request Data into Entities

Related

Using Goup By in Procedure - SQLServer

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,());

JSON Many to Many RelationShip Group By

I'm trying to create an SQL query allowing me to do this:
I have 3 tables in SQL Server 2017:
CREATE TABLE [dbo].[PRODUCTCATEGORY]
(
[PROD_ID] [int] NOT NULL,
[CAT_ID] [int] NOT NULL
CONSTRAINT [PK_PRODUCTCATEGORY]
PRIMARY KEY CLUSTERED ([PROD_ID] ASC, [CAT_ID] ASC)
)
CREATE TABLE [dbo].[CATEGORY]
(
[CAT_ID] [int] IDENTITY(1,1) NOT NULL,
[CAT_TITLE] [varchar](50) NOT NULL
CONSTRAINT [PK_CATEGORY]
PRIMARY KEY CLUSTERED ([CAT_ID] ASC)
)
CREATE TABLE [dbo].[PRODUCT]
(
[PROD_ID] [int] IDENTITY(1,1) NOT NULL,
[PROD_TITLE] [varchar](50) NOT NULL
CONSTRAINT [PK_PRODUCT]
PRIMARY KEY CLUSTERED ([PROD_ID] ASC)
)
A product can have 1 to many categories
A category can have 1 to many products
PROD_ID
PROD_TITLE
1
Book 1
2
Book 2
CAT_ID
CAT_TITLE
1
Cat 1
2
Cat 2
3
Cat 3
PROD_ID
CAT_ID
1
1
1
2
2
1
2
3
I would like to retrieve this:
| CAT_ID |CAT_TITLE | PRODUCTS |
|:------- |:--------:|:------------------------------------------------------------------------|
| 1 | Cat 1 |[{"PROD_ID":1,"PROD_TITLE":"Book 1"},{"PROD_ID":2,"PROD_TITLE":"Book 2"}]|
| 2 | Cat 2 |[{"PROD_ID":1,"PROD_TITLE":"Book 1"}] |
| 3 | Cat 3 |[{"PROD_ID":2,"PROD_TITLE":"Book 2"}] |
Thanks for your help
I just found this, using FOR JSON:
https://learn.microsoft.com/en-us/sql/relational-databases/json/format-query-results-as-json-with-for-json-sql-server?view=sql-server-ver15
I think something like this might work:
SELECT c.CAT_ID, c.CAT_TITLE,
(
SELECT p.PROD_ID, p.PROD_TITLE
FROM PRODUCT p
JOIN PRODUCTCATEGORY pc ON pc.PROD_ID = p.PROD_ID
WHERE pc.CAT_ID = c.CAT_ID
FOR JSON PATH
) AS ProductsAsJson
FROM CATEGORY c

is it possible to add duplicate values to usn column? this is the table i have created,how to add data something like i have shown in the example?

CREATE TABLE [dbo].[studentdb] (
[usn] VARCHAR (15) NOT NULL,
[name] VARCHAR (50) NOT NULL,
[collegename] VARCHAR (50) NOT NULL,
[eventid] VARCHAR (15) NOT NULL,
[passwd] VARCHAR (50) NULL,
[email] VARCHAR (75) NULL,
CONSTRAINT [PK_studentdb] PRIMARY KEY CLUSTERED ([usn] ASC, [eventid] ASC),
FOREIGN KEY ([eventid]) REFERENCES [dbo].[eventdb] ([eventid])
);
is it possible to add duplicate values to usn column?
this is the table i have created,how to add data something like i have shown in the example?
USN EVENTID
1 100
2 100
3 200
1 200
3 100
4 100
5 100
5 200
Your PRIMARY KEY is compound key. It is based on two columns ([usn] ASC, [eventid] ASC) so as long as pair is unique you can insert it.
In your example:
1 100
2 100
3 200
1 200
3 100
4 100
5 100
5 200
every pair is unique.
For inserting data use INSERT INTO syntax like:
INSERT INTO [dbo].[studentdb](
[usn],
[name],
[collegename],
[eventid],
[passwd],
[email])
VALUES (1, 100, ...), -- rest of values
(2, 100, ...),
...;

How to implement referential integrity here?

I got the following structure - which I admit is not ideal, but so much is built on that, tat I want to minimize changes.
I am not sure about how to properly implement referential integrity between Documents and Delivery Adresses. Can it be done here without using triggers ? The problem is that the addressNum can sometimes be Null in the Documents.
CREATE TABLE [dbo].[Clients](
[IdClient] [varchar](10) NOT NULL,
[Nom] [varchar](40) NULL
CONSTRAINT PK_Clients PRIMARY KEY (IdClient))
GO
CREATE TABLE [dbo].[ClientsDelivAdr](
[IdClient] [varchar](10) NOT NULL,
[AdrNum] [tinyint] NOT NULL,
[Adresse] [varchar](200) NULL
CONSTRAINT [PK_ClientsAdrLivr] PRIMARY KEY (IdClient, AdrNum))
CREATE TABLE [dbo].[Documents](
[DocID] [int] IDENTITY(1,1) NOT NULL,
[NoDoc] [char](9) NULL,
[IdClient] [varchar](10) NULL,
[AdrNum] [tinyint] NULL,
[DateDoc] [smalldatetime] NOT NULL,
CONSTRAINT [PK_DocID] PRIMARY KEY (DocId))
Some Clients have several Delivery Adresses, some have none.
So data looks like this:
Clients
Id Name Address
--- ---- -------
AA ClientA addressA
BB ClientB qddressB
CC ClientC addressC
DeliveryAdresses
Client Adr Address
------ --- -------
AA 1 shop1
AA 2 shop2
CC 1 shopx
Documents
DocId Client Addr OrderDate
------- ------ ---- --------
1001 CC 1 5/5/2013
1002 AA 1 5/5/2013
1003 BB (Null) 5/5/2013
I think you can just use foreign keys as you would expect:
CREATE TABLE [dbo].[Documents](
[DocID] [int] IDENTITY(1,1) NOT NULL,
[NoDoc] [char](9) NULL,
[IdClient] [varchar](10) NULL,
[AdrNum] [tinyint] NULL,
[DateDoc] [smalldatetime] NOT NULL,
CONSTRAINT [PK_DocID] PRIMARY KEY (DocId),
CONSTRAINT FK_DOC_Clients FOREIGN KEY (IdClient)
references Clients (IdClient),
CONSTRAINT FK_Doc_Addresses FOREIGN KEY (IdClient,AdrNum)
references DeliveryAddresses (IdClient,AdrNum) )
If one or more column values in the referencing side of a foreign key is NULL, then the foreign key constraint is not checked. Conversely, there's no way to have NULL be a foreign key reference.

Identify data inconsistency in a Tuple-like table

I have a table that holds availability status for workers. Here is the structure:
CREATE TABLE [dbo].[Availability]
(
[OID] BIGINT IDENTITY (1, 1) NOT NULL,
[LocumID] BIGINT NOT NULL,
[AvailableDate] SMALLDATETIME NOT NULL,
[AvailabilityStatusID] INT NOT NULL,
[LastModifiedAt] TIMESTAMP NOT NULL,
CONSTRAINT [PK_Availability] PRIMARY KEY CLUSTERED ([OID] ASC) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY];
And here is a result:
OID LocumID AvailableDate AvailabilityStatusID LastModifiedAt
-------------------- -------------------- ----------------------- -------------------- ------------------
1 1 2009-03-02 00:00:00 1 0x0000000000201A8C
2 2 2009-03-04 00:00:00 1 0x0000000000201A8D
3 1 2009-03-05 00:00:00 1 0x0000000000201A8E
4 1 2009-03-06 00:00:00 1 0x0000000000201A8F
5 2 2009-03-07 00:00:00 1 0x0000000000201A90
6 7 2009-03-09 00:00:00 1 0x0000000000201A91
7 1 2009-03-11 00:00:00 1 0x0000000000201A92
8 1 2009-03-12 00:00:00 2 0x0000000000201A93
9 1 2009-03-14 00:00:00 1 0x0000000000201A94
10 1 2009-03-16 00:00:00 1 0x0000000000201A95
Now, the table has over 3mil record and I noticed that there are inconsistencies in my data. I need to somehow find rows where for any [AvailableDate], the [LocumID] (regardless of how many,) must be unique. So, basically, a worker can have one of these [AvailabilityStatusID] = 1, 2, 3, or 4 on one date. However, in this table, there are errors where a worker is entered twice or more against a [AvailableDate] with same [AvailabilityStatusID] or different [AvailabilityStatusID]
How can I detect these records?
Regards.
WITH x AS
(
SELECT LocumID, dt = AvailableDate
FROM dbo.Availability
GROUP BY LocumID, AvailableDate
HAVING COUNT(*) > 1
)
SELECT a.OID, a.LocumID, a.AvailableDate,
a.AvailabilityStatusID, a.LastModifiedAt
FROM x
INNER JOIN dbo.Availability AS a
ON x.LocumID = a.LocumID
AND x.dt = a.AvailableDate
ORDER BY a.LocumID, a.AvailableDate;
Once you clean this data up (not sure what your rule will be regarding which rows to keep), you should consider a unique constraint on (LocumID, AvailableDate). Here is how you would create the constraint (though you will not be able to create it until you have removed the duplicates):
ALTER TABLE dbo.Availability
ADD CONSTRAINT uq_l_ad
UNIQUE (LocumID, AvailableDate);
Of course now you will have new errors returned to your application (Msg 2627), since your current code clearly doesn't already check if a LocumID/AvailabilityDate combination already exists before adding a new one.

Resources